Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4649daef82 | ||
|
|
70d47ce622 | ||
|
|
46d40d752e | ||
|
|
a006b70c6f | ||
|
|
e726f6eb0d | ||
|
|
389233f599 | ||
|
|
4051caca0b | ||
|
|
914bc9aa7f | ||
|
|
8272ecb33d | ||
|
|
d13ce24507 | ||
|
|
b6a7d5f91d | ||
|
|
f6b3af4221 | ||
|
|
616431cc33 | ||
|
|
77ada09c11 | ||
|
|
389314cc2f | ||
|
|
56fb51e94f | ||
|
|
76432f9d36 | ||
|
|
f0468e04d9 | ||
|
|
052e7a3dc0 | ||
|
|
9ae73d9e20 | ||
|
|
11f4810884 | ||
|
|
fe51dea707 | ||
|
|
4afbe845ff | ||
|
|
0daf389aff | ||
|
|
5c19f647fa | ||
|
|
e07f01ece8 | ||
|
|
7d4458bb5f | ||
|
|
41a0c9da41 | ||
|
|
720bb83234 | ||
|
|
1325ec6a98 | ||
|
|
b0c4b7fe45 | ||
|
|
a2f02652e9 | ||
|
|
f9d3266777 | ||
|
|
70e4812fde | ||
|
|
d0c7382d7f | ||
|
|
e1255edb32 | ||
|
|
6f057b361b | ||
|
|
80aa420099 | ||
|
|
564349c914 | ||
|
|
0da27515a4 | ||
|
|
2448d8372e | ||
|
|
0eca2eebd2 | ||
|
|
8ba1dc1284 | ||
|
|
80a097953f | ||
|
|
d4b4f50674 | ||
|
|
a7aa31adc0 | ||
|
|
af33a2ab41 | ||
|
|
03d7be13d0 | ||
|
|
f0401acd2f | ||
|
|
2686c9fec0 | ||
|
|
8fc5e509ae | ||
|
|
9e9b39a37d | ||
|
|
e219103a25 | ||
|
|
98fbcc6585 | ||
|
|
26b0601f74 | ||
|
|
02e917cd08 | ||
|
|
81b16448f8 | ||
|
|
12d814182d | ||
|
|
2c48b1c019 | ||
|
|
6ee4bbfe3d | ||
|
|
ff1d1250d1 | ||
|
|
a6122cbd24 | ||
|
|
65a9ef5acf | ||
|
|
fbf6120876 | ||
|
|
1537c7e236 | ||
|
|
88d8c2eb1d | ||
|
|
260cb22f53 | ||
|
|
c1ceb472f1 | ||
|
|
92faaeed46 | ||
|
|
339b2ef923 | ||
|
|
a6903c2bb0 | ||
|
|
16f4870714 | ||
|
|
e77ed96957 | ||
|
|
46f6e76f91 | ||
|
|
38bbd764cc | ||
|
|
2931af58db | ||
|
|
08ea2da642 | ||
|
|
448e9369de | ||
|
|
ac6f911f15 | ||
|
|
c69bcc36f5 | ||
|
|
0ee93acd65 | ||
|
|
d83dd9ab8a | ||
|
|
ab32f938d7 | ||
|
|
c7753a9d5b | ||
|
|
b51812655a | ||
|
|
a910edc16b | ||
|
|
fb2075b774 | ||
|
|
4b78bf6a34 | ||
|
|
edce5b6d0f | ||
|
|
7217b11ba7 | ||
|
|
18c2b389a4 | ||
|
|
1912a9c871 | ||
|
|
0c6fa46871 | ||
|
|
df3b68df4c | ||
|
|
aebefded86 | ||
|
|
64e1e7de24 | ||
|
|
05fa147b48 | ||
|
|
bba534d694 | ||
|
|
4f0fe72add | ||
|
|
365bb66d2a | ||
|
|
0a41cf9353 | ||
|
|
d0b274249c | ||
|
|
bdbbb7cb9c | ||
|
|
531f28cbaf | ||
|
|
30b69d3ecf | ||
|
|
9d183e85d7 | ||
|
|
eebb99fa37 | ||
|
|
6aaf0ec26b | ||
|
|
86e586da26 | ||
|
|
069783a353 | ||
|
|
cc95e19ba4 | ||
|
|
c31133c20f | ||
|
|
69ae3656b5 | ||
|
|
43145bd478 | ||
|
|
b7c1e096c4 | ||
|
|
6707c4811e | ||
|
|
2092b8f48e | ||
|
|
20e73369fa | ||
|
|
abc0db8281 | ||
|
|
1beb95115f | ||
|
|
7fcfcdc365 | ||
|
|
2be216899a | ||
|
|
8876ce323b | ||
|
|
575174580e | ||
|
|
7fd4ec3172 | ||
|
|
a75d383001 | ||
|
|
7ddfd4f347 | ||
|
|
7e137cbfc5 | ||
|
|
d40762e69d | ||
|
|
ae19478007 | ||
|
|
11a8184c3e | ||
|
|
c3dc00de05 | ||
|
|
84cb541866 | ||
|
|
624f6aaca6 | ||
|
|
c5f0550953 | ||
|
|
7f0bc7fd24 | ||
|
|
96155b1065 | ||
|
|
23dac3287b | ||
|
|
0bbbd961aa | ||
|
|
dd19e1040a | ||
|
|
e45c16d490 | ||
|
|
a19959cea4 | ||
|
|
4f18d37832 | ||
|
|
a2bf242097 | ||
|
|
4e105c59ed | ||
|
|
59ca841ed5 | ||
|
|
5e8f044a18 | ||
|
|
4db1e39ac7 | ||
|
|
e202503c09 | ||
|
|
aed98abac6 | ||
|
|
250f94e9cd | ||
|
|
d7bf43001f | ||
|
|
868b357527 | ||
|
|
9a609c2053 | ||
|
|
f3acce99d8 | ||
|
|
374e6cd5ee | ||
|
|
84a0b5ffdd | ||
|
|
4cc3957723 | ||
|
|
a0a5469988 | ||
|
|
240920d642 | ||
|
|
764195bd55 | ||
|
|
7bfee67a29 | ||
|
|
c87604cf0c | ||
|
|
4096952c16 | ||
|
|
b4eff32213 | ||
|
|
d361526371 | ||
|
|
0d4e4c818f | ||
|
|
073bfa2b86 | ||
|
|
f0059f39f6 | ||
|
|
41bb4bacb2 | ||
|
|
9487c42985 | ||
|
|
55f7b67f9d | ||
|
|
e44d1e6051 | ||
|
|
baecaf4ca4 | ||
|
|
151d4a289f | ||
|
|
1b4d79b302 | ||
|
|
74833f8680 | ||
|
|
e77f8ced4c | ||
|
|
1cd83e493d | ||
|
|
40b6c66491 | ||
|
|
723fc2c7ec | ||
|
|
dd687ce175 | ||
|
|
8774d87d59 | ||
|
|
8361cf078f | ||
|
|
dbedab0c14 | ||
|
|
5dda8c8ab1 | ||
|
|
4c0c18d7c6 | ||
|
|
9c20455bf6 | ||
|
|
8175443da0 | ||
|
|
95a884974a | ||
|
|
153dd61994 | ||
|
|
263ec8a36e | ||
|
|
af46ba4d1a | ||
|
|
dd85904642 | ||
|
|
7a8fce224e | ||
|
|
9ca7c91a76 | ||
|
|
466354cd2c | ||
|
|
99504dab8f | ||
|
|
9b6603e2eb | ||
|
|
4fc476a125 | ||
|
|
583ed4b58a | ||
|
|
638e81b9ce | ||
|
|
9a0703d05d | ||
|
|
9de096f783 | ||
|
|
c54a99945d | ||
|
|
90a38cc15c | ||
|
|
76930ae6e8 | ||
|
|
d745cfb184 | ||
|
|
4be2bf38ff | ||
|
|
0a32f24f08 | ||
|
|
1c2bc274dd | ||
|
|
9b54794f6b | ||
|
|
42bec5a858 | ||
|
|
fa317c1167 | ||
|
|
cc17b9ee6e | ||
|
|
190ca1ff6a | ||
|
|
c51930121f | ||
|
|
c7a8d734d4 | ||
|
|
b446cf2183 | ||
|
|
f11c246276 | ||
|
|
6f91bd23de | ||
|
|
45c293df7d | ||
|
|
5acdb2b3c5 | ||
|
|
72c1f3e26c | ||
|
|
b3ccf82597 | ||
|
|
0c01a11755 | ||
|
|
7ce3f471bf | ||
|
|
97972acee8 | ||
|
|
a0bb11e1bd | ||
|
|
ba74fceed6 | ||
|
|
ab0faab396 | ||
|
|
765fd9e987 | ||
|
|
8f7b530613 | ||
|
|
30fe81d366 | ||
|
|
b54dd49426 | ||
|
|
26274fed52 | ||
|
|
aa555f792e | ||
|
|
0d50905754 | ||
|
|
b7132196fb | ||
|
|
eb54b99856 | ||
|
|
13b3d8679d | ||
|
|
1545a88e06 | ||
|
|
2b22999b94 | ||
|
|
bbb053aa4e | ||
|
|
7bac4ec634 | ||
|
|
4bc9e8b7b7 | ||
|
|
f381f0a2f7 | ||
|
|
553e4b6813 | ||
|
|
bdaa843f2d | ||
|
|
13f5c14599 | ||
|
|
b871eb3d54 | ||
|
|
12e3a895b3 | ||
|
|
8c5fbe3298 | ||
|
|
0512215007 | ||
|
|
80f4c7b9a7 | ||
|
|
8c51b175c4 | ||
|
|
8d991ad5a7 | ||
|
|
f19bfc613e | ||
|
|
a7e702899a | ||
|
|
deb192b04d | ||
|
|
fbe46f7bac | ||
|
|
fd93c1c553 | ||
|
|
b2f6a56843 | ||
|
|
658da31d46 | ||
|
|
77feaa857e | ||
|
|
5f204d798c | ||
|
|
5183bd53b5 | ||
|
|
fdd30a7b87 | ||
|
|
7ac4ac696f | ||
|
|
c203fda391 | ||
|
|
9a65ddbc43 | ||
|
|
54c252a354 | ||
|
|
e92abda187 | ||
|
|
3f7b7f4bb3 | ||
|
|
944b3f8280 | ||
|
|
b04fca8324 | ||
|
|
bfa0eee91e | ||
|
|
308e3d3417 | ||
|
|
42e48aa9b8 | ||
|
|
18d0193288 | ||
|
|
0ab552a6ef | ||
|
|
d9fef94bc6 | ||
|
|
ab5fb40f8f | ||
|
|
4baa4aa724 | ||
|
|
7a4a285935 | ||
|
|
f38c393d6f | ||
|
|
22c2a4abcc | ||
|
|
1a434e9ea2 | ||
|
|
25a5f1d07f | ||
|
|
e2f949c3a7 | ||
|
|
c3c59337ff | ||
|
|
fdf315ba7d | ||
|
|
f46bd56e8f | ||
|
|
b1efed0c0c | ||
|
|
524fb1ba68 | ||
|
|
3ff2ccf1b3 | ||
|
|
e915fd0f9a | ||
|
|
6f8b2377b1 | ||
|
|
c893a3f11e | ||
|
|
73c71e0198 | ||
|
|
bb305fdaad | ||
|
|
b4d4eb29af | ||
|
|
6d9fe45fb4 | ||
|
|
b292c8485b | ||
|
|
1e4da2c93b | ||
|
|
84e31d032b | ||
|
|
59c8c8c9db | ||
|
|
a76ceb94cc | ||
|
|
9a108af219 | ||
|
|
2f09dec1c5 | ||
|
|
4c43f125f9 | ||
|
|
b5cff49358 | ||
|
|
446a1c8326 | ||
|
|
8c20d19339 | ||
|
|
9dfffe5366 | ||
|
|
19dd81ce94 | ||
|
|
dd9eac3d6d | ||
|
|
916434251d | ||
|
|
62e8d82e16 | ||
|
|
d7be712959 | ||
|
|
490b1dadcb | ||
|
|
502c5960e3 | ||
|
|
d297e373b4 | ||
|
|
9f5d95f617 | ||
|
|
b14b4cc345 | ||
|
|
e41bb8753b | ||
|
|
565f169e94 | ||
|
|
7b7449ee15 | ||
|
|
95bb026fa9 | ||
|
|
4713d320f8 | ||
|
|
083c8e1481 | ||
|
|
6ff6976e85 | ||
|
|
7e87c90b8d | ||
|
|
6fdf0f8255 | ||
|
|
7e12f57eda | ||
|
|
776ec56a2d | ||
|
|
8ec31d83db | ||
|
|
1373911ca2 | ||
|
|
cf5d9dfc7d | ||
|
|
9df0dd48e4 | ||
|
|
d112e9b1dc | ||
|
|
206dc30e17 | ||
|
|
4c3a0afd7e | ||
|
|
8d9bf7d91f | ||
|
|
3ef537242f | ||
|
|
976e4b4217 | ||
|
|
e827a3dd1f | ||
|
|
778031c105 | ||
|
|
6c3654ef40 | ||
|
|
5048f6f34b | ||
|
|
c9dd6e2ba7 | ||
|
|
fc76af9d56 | ||
|
|
aed2285bcb | ||
|
|
1d11908af1 | ||
|
|
3ccc56fef9 | ||
|
|
39e0c974ee | ||
|
|
e52c69ca22 | ||
|
|
d7d53bd905 | ||
|
|
fb2d5016e5 | ||
|
|
015099b92e | ||
|
|
7e8356f0bb | ||
|
|
72b8c4ced8 | ||
|
|
2d22df404a | ||
|
|
0f13a32925 | ||
|
|
1ddb7a0014 | ||
|
|
f9376432c4 | ||
|
|
d195fb6990 | ||
|
|
e4be102a77 | ||
|
|
5f14ccdc34 | ||
|
|
7573459a48 | ||
|
|
01b68cf8e5 | ||
|
|
f3b1a07189 | ||
|
|
c70eb27653 | ||
|
|
4483ce0d79 | ||
|
|
1b23e9ec22 | ||
|
|
1f5692a0c7 | ||
|
|
0ac68dc179 | ||
|
|
d891a057c0 | ||
|
|
9e5bdf4124 | ||
|
|
e1ea295600 | ||
|
|
59171c5443 | ||
|
|
501244cb3c | ||
|
|
390c21f6d5 | ||
|
|
20d78832b0 | ||
|
|
adb3c5392c | ||
|
|
cd2f56d7c7 | ||
|
|
7c9bbc3f6c | ||
|
|
52e396b25d | ||
|
|
aeb2cfb65e | ||
|
|
7f5ea6e493 | ||
|
|
1be94aad78 | ||
|
|
6bb169ac01 | ||
|
|
ea7900d64f | ||
|
|
123abdff30 | ||
|
|
11247f1c01 | ||
|
|
f093c9196d | ||
|
|
81daa62071 | ||
|
|
f99b977888 | ||
|
|
b00ae2ff15 | ||
|
|
b4f9c501b3 | ||
|
|
1738c6f243 | ||
|
|
abea7441a4 | ||
|
|
3887ba27d2 | ||
|
|
76a3af84a1 | ||
|
|
be5d42ee19 | ||
|
|
790711c16e | ||
|
|
8c3127dee1 | ||
|
|
5ec87c9d76 | ||
|
|
2205ce639e | ||
|
|
2844d75607 | ||
|
|
a6810ea874 | ||
|
|
aaf1af8e41 | ||
|
|
b8dd43aeed | ||
|
|
e959efc5af | ||
|
|
73ce01bbde | ||
|
|
b30628f6db | ||
|
|
9289825d87 | ||
|
|
57cfbc4f81 | ||
|
|
3ab2026d62 | ||
|
|
e439c778a6 | ||
|
|
98413c917e | ||
|
|
540a5e53c4 | ||
|
|
27047f83a5 | ||
|
|
222b535c15 | ||
|
|
3f31af6b70 | ||
|
|
b6a054b766 | ||
|
|
3b7b692e80 | ||
|
|
c6788addeb | ||
|
|
5b38bc7f38 | ||
|
|
33bb12713d | ||
|
|
d175a7655e | ||
|
|
4afb38aae1 | ||
|
|
36b5670b3d | ||
|
|
7b61796506 | ||
|
|
684b687e42 | ||
|
|
7a9b9afe2e | ||
|
|
7d382279ad | ||
|
|
b4f5f18283 | ||
|
|
17e150393a | ||
|
|
b5ecfb9c5f | ||
|
|
31a0c9b10c | ||
|
|
e4d95e09cf | ||
|
|
d173be0db5 | ||
|
|
051989a396 | ||
|
|
795c77e943 | ||
|
|
1de6bbc9dc | ||
|
|
474c74b1f9 | ||
|
|
caaa57c915 | ||
|
|
588f736348 | ||
|
|
828302150a | ||
|
|
144723af18 | ||
|
|
087913ab3a | ||
|
|
64942cbb5e | ||
|
|
97430a8e42 | ||
|
|
1bdd200794 | ||
|
|
679035c3bf | ||
|
|
bd4352f041 | ||
|
|
05ffeb8e7f | ||
|
|
6c982a83f0 | ||
|
|
9860fbaec6 | ||
|
|
26c29d9c85 | ||
|
|
242b25eb1e | ||
|
|
c13b8d4fc6 | ||
|
|
12e9c08a1a | ||
|
|
98a0528806 | ||
|
|
015a6067fd | ||
|
|
e34105e754 | ||
|
|
a995866d5b | ||
|
|
a88347dfe6 | ||
|
|
4f404a41c6 | ||
|
|
88444dcf74 | ||
|
|
4f5ab9eb8c | ||
|
|
f51427b63c | ||
|
|
cdcb6a6007 | ||
|
|
ac7e17d0a6 | ||
|
|
402390866a | ||
|
|
cc362a1b7f | ||
|
|
1ca7973b77 | ||
|
|
9ecbea68ca | ||
|
|
e5bc602af3 | ||
|
|
a9aa406d29 | ||
|
|
c1ae6f14fc | ||
|
|
411b7a2446 | ||
|
|
869b457810 | ||
|
|
4330bae47f | ||
|
|
1c36c41050 | ||
|
|
bea4356aca | ||
|
|
32f51c612a | ||
|
|
355d00d951 | ||
|
|
bf56f3919f | ||
|
|
d876580ddf | ||
|
|
90f36198d2 | ||
|
|
e542f3c9d3 | ||
|
|
87c56d0248 | ||
|
|
8c928cb237 | ||
|
|
6030992af4 | ||
|
|
688da1a1bb | ||
|
|
ceecd51982 | ||
|
|
c77a700442 | ||
|
|
10ded26d60 | ||
|
|
74adaba8ac | ||
|
|
19c57b0e5e | ||
|
|
f561057311 | ||
|
|
0e6a7a39a0 | ||
|
|
13b905e762 | ||
|
|
b4c0cc5a27 | ||
|
|
a042c8bb3c | ||
|
|
286a642739 | ||
|
|
513a83c4b0 | ||
|
|
9b2c8826b1 | ||
|
|
413a3c27a4 | ||
|
|
c9328d98d7 | ||
|
|
f6c0751ce3 | ||
|
|
16c7ebef73 | ||
|
|
ec38ac7292 | ||
|
|
b8497a91a6 | ||
|
|
5115bcaf98 | ||
|
|
7077037bb7 | ||
|
|
11ba322b5f | ||
|
|
a39a37ec51 | ||
|
|
ec8fa0d1fe | ||
|
|
5efc4ffee3 | ||
|
|
c2d028df4f | ||
|
|
9b838df17d | ||
|
|
a20ff4645d | ||
|
|
850e3431da | ||
|
|
072a60516e | ||
|
|
9b3feb995a | ||
|
|
d359f7455f | ||
|
|
4987cd188a | ||
|
|
019e45572d | ||
|
|
d87e877a27 | ||
|
|
05c7da17b9 | ||
|
|
8c21f75e6d | ||
|
|
615a4e6e37 | ||
|
|
11ca1bf51e | ||
|
|
7fabfad65d | ||
|
|
ce4779f49d | ||
|
|
a88b919e60 | ||
|
|
cc4d035b63 | ||
|
|
12b78eb1d7 | ||
|
|
8a5267ab76 | ||
|
|
a189947617 | ||
|
|
f01b3999ff |
No files matched your search
@@ -0,0 +1 @@
|
||||
* text=auto eol=lf
|
||||
@@ -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!
|
||||
@@ -0,0 +1,2 @@
|
||||
github: [dmulloy2]
|
||||
custom: ["https://paypal.me/dmulloy2"]
|
||||
@@ -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:
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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: '21'
|
||||
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
|
||||
@@ -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: '21'
|
||||
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
|
||||
+11
-1
@@ -7,6 +7,10 @@
|
||||
.classpath
|
||||
.settings/
|
||||
bin/
|
||||
.idea/
|
||||
*.iml
|
||||
.ipr
|
||||
.iws
|
||||
|
||||
*.pydevproject
|
||||
.metadata
|
||||
@@ -171,4 +175,10 @@ pip-log.txt
|
||||
.DS_Store
|
||||
|
||||
# Log4J files
|
||||
logs/
|
||||
logs/
|
||||
|
||||
.gradle/
|
||||
build/
|
||||
logs/
|
||||
.java-version
|
||||
gradle.properties
|
||||
-13
@@ -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
|
||||
@@ -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
|
||||
@@ -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,155 +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 org.bukkit.plugin.Plugin;
|
||||
|
||||
import com.comphenix.protocol.async.AsyncListenerHandler;
|
||||
import com.comphenix.protocol.async.AsyncMarker;
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.events.PacketListener;
|
||||
|
||||
/**
|
||||
* Represents a asynchronous packet handler.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public interface AsynchronousManager {
|
||||
/**
|
||||
* Registers an asynchronous packet handler.
|
||||
* <p>
|
||||
* Use {@link AsyncMarker#incrementProcessingDelay()} to delay a packet until its ready to be transmitted.
|
||||
* <p>
|
||||
* To start listening asynchronously, pass the getListenerLoop() runnable to a different thread.
|
||||
* @param listener - the packet listener that will receive these asynchronous events.
|
||||
* @return An asynchronous handler.
|
||||
*/
|
||||
public abstract AsyncListenerHandler registerAsyncHandler(PacketListener listener);
|
||||
|
||||
/**
|
||||
* Unregisters and closes the given asynchronous handler.
|
||||
* @param handler - asynchronous handler.
|
||||
*/
|
||||
public abstract void unregisterAsyncHandler(AsyncListenerHandler handler);
|
||||
|
||||
/**
|
||||
* Unregisters and closes the first asynchronous handler associated with the given listener.
|
||||
* @param listener - asynchronous listener
|
||||
*/
|
||||
public abstract void unregisterAsyncHandler(PacketListener listener);
|
||||
|
||||
/**
|
||||
* Unregisters every asynchronous handler associated with this plugin.
|
||||
* @param plugin - the original plugin.
|
||||
*/
|
||||
public void unregisterAsyncHandlers(Plugin plugin);
|
||||
|
||||
/**
|
||||
* Retrieves a immutable set containing the ID of the sent server packets that will be
|
||||
* observed by the asynchronous listeners.
|
||||
* <p>
|
||||
* Deprecated: Use {@link #getSendingTypes()} instead.
|
||||
* @return Every filtered server packet.
|
||||
*/
|
||||
@Deprecated
|
||||
public abstract Set<Integer> getSendingFilters();
|
||||
|
||||
/**
|
||||
* Retrieves a immutable set containing the types of the sent server packets that will be
|
||||
* observed by the asynchronous listeners.
|
||||
* @return Every filtered server packet.
|
||||
*/
|
||||
public abstract Set<PacketType> getSendingTypes();
|
||||
|
||||
/**
|
||||
* Retrieves a immutable set containing the ID of the recieved client packets that will be
|
||||
* <p>
|
||||
* Deprecated: Use {@link #getReceivingTypes()} instead.
|
||||
* observed by the asynchronous listeners.
|
||||
* @return Every filtered client packet.
|
||||
*/
|
||||
@Deprecated
|
||||
public abstract Set<Integer> getReceivingFilters();
|
||||
|
||||
/**
|
||||
* Retrieves a immutable set containing the types of the received client packets that will be
|
||||
* observed by the asynchronous listeners.
|
||||
* @return Every filtered client packet.
|
||||
*/
|
||||
public abstract Set<PacketType> getReceivingTypes();
|
||||
|
||||
/**
|
||||
* Determine if a given synchronous packet has asynchronous listeners.
|
||||
* @param packet - packet to test.
|
||||
* @return TRUE if it does, FALSE otherwise.
|
||||
*/
|
||||
public abstract boolean hasAsynchronousListeners(PacketEvent packet);
|
||||
|
||||
/**
|
||||
* Retrieve the default packet stream.
|
||||
* @return Default packet stream.
|
||||
*/
|
||||
public abstract PacketStream getPacketStream();
|
||||
|
||||
/**
|
||||
* Retrieve the default error reporter.
|
||||
* @return Default reporter.
|
||||
*/
|
||||
public abstract ErrorReporter getErrorReporter();
|
||||
|
||||
/**
|
||||
* Remove listeners, close threads and transmit every delayed packet.
|
||||
*/
|
||||
public abstract void cleanupAll();
|
||||
|
||||
/**
|
||||
* Signal that a packet is ready to be transmitted.
|
||||
* <p>
|
||||
* This should only be called if {@link com.comphenix.protocol.async.AsyncMarker#incrementProcessingDelay() AsyncMarker.incrementProcessingDelay()}
|
||||
* has been called previously.
|
||||
* @param packet - packet to signal.
|
||||
*/
|
||||
public abstract void signalPacketTransmission(PacketEvent packet);
|
||||
|
||||
/**
|
||||
* Register a synchronous listener that handles packets when they time out.
|
||||
* @param listener - synchronous listener that will handle timed out packets.
|
||||
*/
|
||||
public abstract void registerTimeoutHandler(PacketListener listener);
|
||||
|
||||
/**
|
||||
* Unregisters a given timeout listener.
|
||||
* @param listener - the timeout listener to unregister.
|
||||
*/
|
||||
public abstract void unregisterTimeoutHandler(PacketListener listener);
|
||||
|
||||
/**
|
||||
* Get a immutable set of every registered timeout handler.
|
||||
* @return Set of every registered timeout handler.
|
||||
*/
|
||||
public abstract Set<PacketListener> getTimeoutHandlers();
|
||||
|
||||
/**
|
||||
* Get an immutable set of every registered asynchronous packet listener.
|
||||
* @return Set of every asynchronous packet listener.
|
||||
*/
|
||||
public abstract Set<PacketListener> getAsyncHandlers();
|
||||
}
|
||||
@@ -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,156 +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.ArrayDeque;
|
||||
import java.util.Arrays;
|
||||
import java.util.Deque;
|
||||
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.error.Report;
|
||||
import com.comphenix.protocol.error.ReportType;
|
||||
|
||||
/**
|
||||
* Base class for all our commands.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
abstract class CommandBase implements CommandExecutor {
|
||||
public static final ReportType REPORT_COMMAND_ERROR = new ReportType("Cannot execute command %s.");
|
||||
public static final ReportType REPORT_UNEXPECTED_COMMAND = new ReportType("Incorrect command assigned to %s.");
|
||||
|
||||
public static final String PERMISSION_ADMIN = "protocol.admin";
|
||||
|
||||
private String permission;
|
||||
private String name;
|
||||
private int minimumArgumentCount;
|
||||
|
||||
protected ErrorReporter reporter;
|
||||
|
||||
public CommandBase(ErrorReporter reporter, String permission, String name) {
|
||||
this(reporter, permission, name, 0);
|
||||
}
|
||||
|
||||
public CommandBase(ErrorReporter reporter, String permission, String name, int minimumArgumentCount) {
|
||||
this.reporter = reporter;
|
||||
this.name = name;
|
||||
this.permission = permission;
|
||||
this.minimumArgumentCount = minimumArgumentCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
try {
|
||||
// Make sure we're dealing with the correct command
|
||||
if (!command.getName().equalsIgnoreCase(name)) {
|
||||
reporter.reportWarning(this, Report.newBuilder(REPORT_UNEXPECTED_COMMAND).messageParam(this));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (permission != null && !sender.hasPermission(permission)) {
|
||||
sender.sendMessage(ChatColor.RED + "You haven't got permission to run this command.");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check argument length
|
||||
if (args != null && args.length >= minimumArgumentCount) {
|
||||
return handleCommand(sender, args);
|
||||
} else {
|
||||
sender.sendMessage(ChatColor.RED + "Insufficient arguments. You need at least " + minimumArgumentCount);
|
||||
return false;
|
||||
}
|
||||
} catch (Throwable ex) {
|
||||
reporter.reportDetailed(this,
|
||||
Report.newBuilder(REPORT_COMMAND_ERROR).error(ex).messageParam(name).callerParam(sender, label, args)
|
||||
);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a boolean value at the head of the queue.
|
||||
* @param arguments - the queue of arguments.
|
||||
* @param parameterName - the parameter name we will match.
|
||||
* @return The parsed boolean, or NULL if not valid.
|
||||
*/
|
||||
protected Boolean parseBoolean(Deque<String> arguments, String parameterName) {
|
||||
Boolean result = null;
|
||||
|
||||
if (!arguments.isEmpty()) {
|
||||
String arg = arguments.peek();
|
||||
|
||||
if (arg.equalsIgnoreCase("true") || arg.equalsIgnoreCase("on"))
|
||||
result = true;
|
||||
else if (arg.equalsIgnoreCase(parameterName))
|
||||
result = true;
|
||||
else if (arg.equalsIgnoreCase("false") || arg.equalsIgnoreCase("off"))
|
||||
result = false;
|
||||
}
|
||||
|
||||
if (result != null)
|
||||
arguments.poll();
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a queue from a sublist of a given array.
|
||||
* @param args - the source array.
|
||||
* @param start - the starting index.
|
||||
* @return A queue that contains every element in the array, starting at the given index.
|
||||
*/
|
||||
protected Deque<String> toQueue(String[] args, int start) {
|
||||
return new ArrayDeque<String>(Arrays.asList(args).subList(start, args.length));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the permission necessary to execute this command.
|
||||
* @return The permission, or NULL if not needed.
|
||||
*/
|
||||
public String getPermission() {
|
||||
return permission;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the primary name of this command.
|
||||
* @return Primary name.
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the error reporter.
|
||||
* @return Error reporter.
|
||||
*/
|
||||
protected ErrorReporter getReporter() {
|
||||
return reporter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Main implementation of this command.
|
||||
* @param sender - command sender.
|
||||
* @param args - input arguments.
|
||||
* @return TRUE if the command was successfully handled, FALSE otherwise.
|
||||
*/
|
||||
protected abstract boolean handleCommand(CommandSender sender, String[] args);
|
||||
}
|
||||
@@ -1,481 +0,0 @@
|
||||
package com.comphenix.protocol;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Deque;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.script.Invocable;
|
||||
import javax.script.ScriptEngine;
|
||||
import javax.script.ScriptEngineManager;
|
||||
import javax.script.ScriptException;
|
||||
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.conversations.Conversable;
|
||||
import org.bukkit.conversations.Conversation;
|
||||
import org.bukkit.conversations.ConversationAbandonedEvent;
|
||||
import org.bukkit.conversations.ConversationAbandonedListener;
|
||||
import org.bukkit.conversations.ConversationContext;
|
||||
import org.bukkit.conversations.ConversationFactory;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import com.comphenix.protocol.MultipleLinesPrompt.MultipleConversationCanceller;
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.error.Report;
|
||||
import com.comphenix.protocol.error.ReportType;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.google.common.collect.Sets;
|
||||
|
||||
/**
|
||||
* A command to apply JavaScript filtering to the packet command.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public class CommandFilter extends CommandBase {
|
||||
public static final ReportType REPORT_FALLBACK_ENGINE = new ReportType("Falling back to the Rhino engine.");
|
||||
public static final ReportType REPORT_CANNOT_LOAD_FALLBACK_ENGINE = new ReportType("Could not load Rhino either. Please upgrade your JVM or OS.");
|
||||
public static final ReportType REPORT_PACKAGES_UNSUPPORTED_IN_ENGINE = new ReportType("Unable to initialize packages for JavaScript engine.");
|
||||
public static final ReportType REPORT_FILTER_REMOVED_FOR_ERROR = new ReportType("Removing filter %s for causing %s.");
|
||||
public static final ReportType REPORT_CANNOT_HANDLE_CONVERSATION = new ReportType("Cannot handle conversation.");
|
||||
|
||||
public interface FilterFailedHandler{
|
||||
/**
|
||||
* Invoked when a given filter has failed.
|
||||
* @param event - the packet event.
|
||||
* @param filter - the filter that failed.
|
||||
* @param ex - the failure.
|
||||
* @return TRUE to keep processing this filter, FALSE to remove it.
|
||||
*/
|
||||
public boolean handle(PacketEvent event, Filter filter, Exception ex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Possible sub commands.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
private enum SubCommand {
|
||||
ADD, REMOVE;
|
||||
}
|
||||
|
||||
/**
|
||||
* A filter that will be used to process a packet event.
|
||||
* @author Kristian
|
||||
*/
|
||||
public static class Filter {
|
||||
private final String name;
|
||||
private final String predicate;
|
||||
|
||||
private final Set<PacketType> packets;
|
||||
|
||||
/**
|
||||
* Construct a new immutable filter.
|
||||
* @param name - the unique name of the filter.
|
||||
* @param predicate - the JavaScript predicate that will be used to filter packet events.
|
||||
* @param packets - a list of packet types this filter applies to.
|
||||
*/
|
||||
public Filter(String name, String predicate, Set<PacketType> packets) {
|
||||
this.name = name;
|
||||
this.predicate = predicate;
|
||||
this.packets = Sets.newHashSet(packets);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the unique name of the filter.
|
||||
* @return Unique name of the filter.
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the JavaScript predicate that will be used to filter packet events.
|
||||
* @return Predicate itself.
|
||||
*/
|
||||
public String getPredicate() {
|
||||
return predicate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a copy of the set of packets this filter applies to.
|
||||
* @return Set of packets this filter applies to.
|
||||
*/
|
||||
public Set<PacketType> getRanges() {
|
||||
return Sets.newHashSet(packets);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether or not a packet event needs to be passed to this filter.
|
||||
* @param event - the event to test.
|
||||
* @return TRUE if it does, FALSE otherwise.
|
||||
*/
|
||||
private boolean isApplicable(PacketEvent event) {
|
||||
return packets.contains(event.getPacketType());
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate the current filter using the provided ScriptEngine as context.
|
||||
* <p>
|
||||
* This context may be modified with additional code.
|
||||
* @param context - the current script context.
|
||||
* @param event - the packet event to evaluate.
|
||||
* @return TRUE to pass this packet event on to the debug listeners, FALSE otherwise.
|
||||
* @throws ScriptException If the compilation failed or the filter is not valid.
|
||||
*/
|
||||
public boolean evaluate(ScriptEngine context, PacketEvent event) throws ScriptException {
|
||||
if (!isApplicable(event))
|
||||
return true;
|
||||
// Ensure that the predicate has been compiled
|
||||
compile(context);
|
||||
|
||||
try {
|
||||
Object result = ((Invocable) context).invokeFunction(name, event, event.getPacket().getHandle());
|
||||
|
||||
if (result instanceof Boolean)
|
||||
return (Boolean) result;
|
||||
else
|
||||
throw new ScriptException("Filter result wasn't a boolean: " + result);
|
||||
|
||||
} catch (NoSuchMethodException e) {
|
||||
// Must be a fault with the script engine itself
|
||||
throw new IllegalStateException("Unable to compile " + name + " into current script engine.", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Force the compilation of a specific filter.
|
||||
* @param context - the current script context.
|
||||
* @throws ScriptException If the compilation failed.
|
||||
*/
|
||||
public void compile(ScriptEngine context) throws ScriptException {
|
||||
if (context.get(name) == null) {
|
||||
context.eval("var " + name + " = function(event, packet) {\n" + predicate);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up all associated code from this filter in the provided script engine.
|
||||
* @param context - the current script context.
|
||||
*/
|
||||
public void close(ScriptEngine context) {
|
||||
context.put(name, null);
|
||||
}
|
||||
}
|
||||
|
||||
private class CompilationSuccessCanceller implements MultipleConversationCanceller {
|
||||
@Override
|
||||
public boolean cancelBasedOnInput(ConversationContext context, String in) {
|
||||
throw new UnsupportedOperationException("Cannot cancel on the last line alone.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setConversation(Conversation conversation) {
|
||||
// Ignore
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean cancelBasedOnInput(ConversationContext context, String currentLine, StringBuilder lines, int lineCount) {
|
||||
try {
|
||||
engine.eval("function(event, packet) {\n" + lines.toString());
|
||||
|
||||
// It compiles - accept the filter!
|
||||
return true;
|
||||
} catch (ScriptException e) {
|
||||
// We also have the function() line
|
||||
int realLineCount = lineCount + 1;
|
||||
|
||||
// Only possible to recover from an error on the last line.
|
||||
return e.getLineNumber() < realLineCount;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompilationSuccessCanceller clone() {
|
||||
return new CompilationSuccessCanceller();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Name of this command.
|
||||
*/
|
||||
public static final String NAME = "filter";
|
||||
|
||||
// Default error handler
|
||||
private FilterFailedHandler defaultFailedHandler;
|
||||
|
||||
// Currently registered filters
|
||||
private List<Filter> filters = new ArrayList<Filter>();
|
||||
|
||||
// Owner plugin
|
||||
private final Plugin plugin;
|
||||
|
||||
// Whether or not the command is enabled
|
||||
private ProtocolConfig config;
|
||||
|
||||
// Script engine
|
||||
private ScriptEngine engine;
|
||||
private boolean uninitialized;
|
||||
|
||||
public CommandFilter(ErrorReporter reporter, Plugin plugin, ProtocolConfig config) {
|
||||
super(reporter, CommandBase.PERMISSION_ADMIN, NAME, 2);
|
||||
this.plugin = plugin;
|
||||
this.config = config;
|
||||
|
||||
// Tell the filter system to initialize the script first chance it gets
|
||||
this.uninitialized = true;
|
||||
}
|
||||
|
||||
private void initalizeScript() {
|
||||
try {
|
||||
// First attempt
|
||||
initializeEngine();
|
||||
|
||||
// Oh for ..
|
||||
if (!isInitialized()) {
|
||||
throw new ScriptException("A JavaScript engine could not be found.");
|
||||
} else {
|
||||
plugin.getLogger().info("Loaded command filter engine.");
|
||||
}
|
||||
} catch (ScriptException e1) {
|
||||
// It's not a huge deal
|
||||
printPackageWarning(e1);
|
||||
|
||||
if (!config.getScriptEngineName().equals("rhino")) {
|
||||
reporter.reportWarning(this, Report.newBuilder(REPORT_FALLBACK_ENGINE));
|
||||
config.setScriptEngineName("rhino");
|
||||
config.saveAll();
|
||||
|
||||
try {
|
||||
initializeEngine();
|
||||
|
||||
if (!isInitialized()) {
|
||||
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_LOAD_FALLBACK_ENGINE));
|
||||
}
|
||||
} catch (ScriptException e2) {
|
||||
// And again ..
|
||||
printPackageWarning(e2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void printPackageWarning(ScriptException e) {
|
||||
reporter.reportWarning(this, Report.newBuilder(REPORT_PACKAGES_UNSUPPORTED_IN_ENGINE).error(e));
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the current configured engine.
|
||||
* @throws ScriptException If we are unable to import packages.
|
||||
*/
|
||||
private void initializeEngine() throws ScriptException {
|
||||
ScriptEngineManager manager = new ScriptEngineManager();
|
||||
engine = manager.getEngineByName(config.getScriptEngineName());
|
||||
|
||||
// Import useful packages
|
||||
if (engine != null) {
|
||||
engine.eval("importPackage(org.bukkit);");
|
||||
engine.eval("importPackage(com.comphenix.protocol.reflect);");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the filter engine has been successfully initialized.
|
||||
* @return TRUE if it has, FALSE otherwise.
|
||||
*/
|
||||
public boolean isInitialized() {
|
||||
return engine != null;
|
||||
}
|
||||
|
||||
private FilterFailedHandler getDefaultErrorHandler() {
|
||||
// No need to create a new object every time
|
||||
if (defaultFailedHandler == null) {
|
||||
defaultFailedHandler = new FilterFailedHandler() {
|
||||
@Override
|
||||
public boolean handle(PacketEvent event, Filter filter, Exception ex) {
|
||||
reporter.reportMinimal(plugin, "filterEvent(PacketEvent)", ex, event);
|
||||
reporter.reportWarning(this,
|
||||
Report.newBuilder(REPORT_FILTER_REMOVED_FOR_ERROR).messageParam(filter.getName(), ex.getClass().getSimpleName())
|
||||
);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
}
|
||||
return defaultFailedHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether or not to pass the given packet event to the packet listeners.
|
||||
* <p>
|
||||
* Uses a default filter failure handler that simply prints the error message and removes the filter.
|
||||
* @param event - the event.
|
||||
* @return TRUE if we should, FALSE otherwise.
|
||||
*/
|
||||
public boolean filterEvent(PacketEvent event) {
|
||||
return filterEvent(event, getDefaultErrorHandler());
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether or not to pass the given packet event to the packet listeners.
|
||||
* @param event - the event.
|
||||
* @param handler - failure handler.
|
||||
* @return TRUE if we should, FALSE otherwise.
|
||||
*/
|
||||
public boolean filterEvent(PacketEvent event, FilterFailedHandler handler) {
|
||||
for (Iterator<Filter> it = filters.iterator(); it.hasNext(); ) {
|
||||
Filter filter = it.next();
|
||||
|
||||
try {
|
||||
if (!filter.evaluate(engine, event)) {
|
||||
return false;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
if (!handler.handle(event, filter, ex)) {
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
// Pass!
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the script engine if necessary.
|
||||
*/
|
||||
private void checkScriptStatus() {
|
||||
// Start the engine
|
||||
if (uninitialized) {
|
||||
uninitialized = false;
|
||||
initalizeScript();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Description: Adds or removes a simple packet filter.
|
||||
Usage: /<command> add|remove name [packet IDs]
|
||||
*/
|
||||
@Override
|
||||
protected boolean handleCommand(CommandSender sender, String[] args) {
|
||||
checkScriptStatus();
|
||||
|
||||
if (!config.isDebug()) {
|
||||
sender.sendMessage(ChatColor.RED + "Debug mode must be enabled in the configuration first!");
|
||||
return true;
|
||||
}
|
||||
if (!isInitialized()) {
|
||||
sender.sendMessage(ChatColor.RED + "JavaScript engine was not present. Filter system is disabled.");
|
||||
return true;
|
||||
}
|
||||
|
||||
final SubCommand command = parseCommand(args, 0);
|
||||
final String name = args[1];
|
||||
|
||||
switch (command) {
|
||||
case ADD:
|
||||
// Never overwrite an existing filter
|
||||
if (findFilter(name) != null) {
|
||||
sender.sendMessage(ChatColor.RED + "Filter " + name + " already exists. Remove it first.");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Prepare the input to the packet type parser
|
||||
Deque<String> rangeArguments = toQueue(args, 2);
|
||||
|
||||
final PacketTypeParser parser = new PacketTypeParser();
|
||||
final Set<PacketType> packets = parser.parseTypes(rangeArguments, PacketTypeParser.DEFAULT_MAX_RANGE);
|
||||
sender.sendMessage("Enter filter program ('}' to complete or CANCEL):");
|
||||
|
||||
// Make sure we can use the conversable interface
|
||||
if (sender instanceof Conversable) {
|
||||
final MultipleLinesPrompt prompt =
|
||||
new MultipleLinesPrompt(new CompilationSuccessCanceller(), "function(event, packet) {");
|
||||
|
||||
new ConversationFactory(plugin).
|
||||
withFirstPrompt(prompt).
|
||||
withEscapeSequence("CANCEL").
|
||||
withLocalEcho(false).
|
||||
addConversationAbandonedListener(new ConversationAbandonedListener() {
|
||||
@Override
|
||||
public void conversationAbandoned(ConversationAbandonedEvent event) {
|
||||
try {
|
||||
final Conversable whom = event.getContext().getForWhom();
|
||||
|
||||
if (event.gracefulExit()) {
|
||||
String predicate = prompt.removeAccumulatedInput(event.getContext());
|
||||
Filter filter = new Filter(name, predicate, packets);
|
||||
|
||||
// Print the last line as well
|
||||
whom.sendRawMessage(prompt.getPromptText(event.getContext()));
|
||||
|
||||
try {
|
||||
// Force early compilation
|
||||
filter.compile(engine);
|
||||
|
||||
filters.add(filter);
|
||||
whom.sendRawMessage(ChatColor.GOLD + "Added filter " + name);
|
||||
} catch (ScriptException e) {
|
||||
e.printStackTrace();
|
||||
whom.sendRawMessage(ChatColor.GOLD + "Compilation error: " + e.getMessage());
|
||||
}
|
||||
} else {
|
||||
// Too bad
|
||||
whom.sendRawMessage(ChatColor.RED + "Cancelled filter.");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
reporter.reportDetailed(this,
|
||||
Report.newBuilder(REPORT_CANNOT_HANDLE_CONVERSATION).error(e).callerParam(event)
|
||||
);
|
||||
}
|
||||
}
|
||||
}).
|
||||
buildConversation((Conversable) sender).
|
||||
begin();
|
||||
} else {
|
||||
sender.sendMessage(ChatColor.RED + "Only console and players are supported!");
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case REMOVE:
|
||||
Filter filter = findFilter(name);
|
||||
|
||||
// See if it exists before we remove it
|
||||
if (filter != null) {
|
||||
filter.close(engine);
|
||||
filters.remove(filter);
|
||||
sender.sendMessage(ChatColor.GOLD + "Removed filter " + name);
|
||||
} else {
|
||||
sender.sendMessage(ChatColor.RED + "Unable to find a filter by the name " + name);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup a filter by its name.
|
||||
* @param name - the filter name.
|
||||
* @return The filter, or NULL if not found.
|
||||
*/
|
||||
private Filter findFilter(String name) {
|
||||
// We'll just use a linear scan for now - we don't expect that many filters
|
||||
for (Filter filter : filters) {
|
||||
if (filter.getName().equalsIgnoreCase(name)) {
|
||||
return filter;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private SubCommand parseCommand(String[] args, int index) {
|
||||
String text = args[index].toUpperCase();
|
||||
|
||||
try {
|
||||
return SubCommand.valueOf(text);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new IllegalArgumentException(text + " is not a valid sub command. Must be add or remove.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,547 +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 java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Deque;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.WeakHashMap;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import net.sf.cglib.proxy.Factory;
|
||||
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import com.comphenix.protocol.PacketType.Sender;
|
||||
import com.comphenix.protocol.concurrency.PacketTypeSet;
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.error.Report;
|
||||
import com.comphenix.protocol.error.ReportType;
|
||||
import com.comphenix.protocol.events.ListeningWhitelist;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.events.PacketListener;
|
||||
import com.comphenix.protocol.reflect.EquivalentConverter;
|
||||
import com.comphenix.protocol.reflect.PrettyPrinter;
|
||||
import com.comphenix.protocol.reflect.PrettyPrinter.ObjectPrinter;
|
||||
import com.comphenix.protocol.utility.ChatExtensions;
|
||||
import com.comphenix.protocol.utility.HexDumper;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
import com.comphenix.protocol.wrappers.BukkitConverters;
|
||||
import com.google.common.collect.MapMaker;
|
||||
import com.google.common.collect.Sets;
|
||||
|
||||
/**
|
||||
* Handles the "packet" debug command.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
class CommandPacket extends CommandBase {
|
||||
public static final ReportType REPORT_CANNOT_SEND_MESSAGE = new ReportType("Cannot send chat message.");
|
||||
|
||||
private enum SubCommand {
|
||||
ADD, REMOVE, NAMES, PAGE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Name of this command.
|
||||
*/
|
||||
public static final String NAME = "packet";
|
||||
|
||||
/**
|
||||
* Number of lines per page.
|
||||
*/
|
||||
public static final int PAGE_LINE_COUNT = 9;
|
||||
|
||||
/**
|
||||
* Number of bytes before we do a hex dump.
|
||||
*/
|
||||
private static final int HEX_DUMP_THRESHOLD = 256;
|
||||
|
||||
private Plugin plugin;
|
||||
private Logger logger;
|
||||
private ProtocolManager manager;
|
||||
|
||||
private ChatExtensions chatter;
|
||||
|
||||
// The main parser
|
||||
private PacketTypeParser typeParser = new PacketTypeParser();
|
||||
|
||||
// Paged message
|
||||
private Map<CommandSender, List<String>> pagedMessage = new WeakHashMap<CommandSender, List<String>>();
|
||||
|
||||
// Current registered packet types
|
||||
private PacketTypeSet packetTypes = new PacketTypeSet();
|
||||
private PacketTypeSet extendedTypes = new PacketTypeSet();
|
||||
|
||||
// Compare listeners
|
||||
private PacketTypeSet compareTypes = new PacketTypeSet();
|
||||
private Map<PacketEvent, String> originalPackets = new MapMaker().weakKeys().makeMap();
|
||||
|
||||
// The packet listener
|
||||
private PacketListener listener;
|
||||
|
||||
// Compare listener
|
||||
private PacketListener compareListener;
|
||||
|
||||
// Filter packet events
|
||||
private CommandFilter filter;
|
||||
|
||||
public CommandPacket(ErrorReporter reporter, Plugin plugin, Logger logger, CommandFilter filter, ProtocolManager manager) {
|
||||
super(reporter, CommandBase.PERMISSION_ADMIN, NAME, 1);
|
||||
this.plugin = plugin;
|
||||
this.logger = logger;
|
||||
this.manager = manager;
|
||||
this.filter = filter;
|
||||
this.chatter = new ChatExtensions(manager);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a message without invoking the packet listeners.
|
||||
* @param receiver - the player to send it to.
|
||||
* @param message - the message to send.
|
||||
* @return TRUE if the message was sent successfully, FALSE otherwise.
|
||||
*/
|
||||
public void sendMessageSilently(CommandSender receiver, String message) {
|
||||
try {
|
||||
chatter.sendMessageSilently(receiver, message);
|
||||
} catch (InvocationTargetException e) {
|
||||
reporter.reportDetailed(this,
|
||||
Report.newBuilder(REPORT_CANNOT_SEND_MESSAGE).error(e).callerParam(receiver, message)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast a message without invoking any packet listeners.
|
||||
* @param message - message to send.
|
||||
* @param permission - permission required to receieve the message. NULL to target everyone.
|
||||
*/
|
||||
public void broadcastMessageSilently(String message, String permission) {
|
||||
try {
|
||||
chatter.broadcastMessageSilently(message, permission);
|
||||
} catch (InvocationTargetException e) {
|
||||
reporter.reportDetailed(this,
|
||||
Report.newBuilder(REPORT_CANNOT_SEND_MESSAGE).error(e).callerParam(message, permission)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private void printPage(CommandSender sender, int pageIndex) {
|
||||
List<String> paged = pagedMessage.get(sender);
|
||||
|
||||
// Make sure the player has any pages
|
||||
if (paged != null) {
|
||||
int lastPage = ((paged.size() - 1) / PAGE_LINE_COUNT) + 1;
|
||||
|
||||
for (int i = PAGE_LINE_COUNT * (pageIndex - 1); i < PAGE_LINE_COUNT * pageIndex; i++) {
|
||||
if (i < paged.size()) {
|
||||
sendMessageSilently(sender, " " + paged.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
// More data?
|
||||
if (pageIndex < lastPage) {
|
||||
sendMessageSilently(sender, "Send /packet page " + (pageIndex + 1) + " for the next page.");
|
||||
}
|
||||
|
||||
} else {
|
||||
sendMessageSilently(sender, ChatColor.RED + "No pages found.");
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Description: Adds or removes a simple packet listener.
|
||||
Usage: /<command> add|remove client|server|both [ID start] [ID stop] [detailed]
|
||||
*/
|
||||
@Override
|
||||
protected boolean handleCommand(CommandSender sender, String[] args) {
|
||||
try {
|
||||
Deque<String> arguments = new ArrayDeque<String>(Arrays.asList(args));
|
||||
SubCommand subCommand = parseCommand(arguments);
|
||||
|
||||
// Commands with different parameters
|
||||
if (subCommand == SubCommand.PAGE) {
|
||||
if (args.length <= 1) {
|
||||
sendMessageSilently(sender, ChatColor.RED + "Must specify a page index.");
|
||||
return true;
|
||||
}
|
||||
|
||||
int page = Integer.parseInt(args[1]);
|
||||
|
||||
if (page > 0)
|
||||
printPage(sender, page);
|
||||
else
|
||||
sendMessageSilently(sender, ChatColor.RED + "Page index must be greater than zero.");
|
||||
return true;
|
||||
}
|
||||
|
||||
Set<PacketType> types = typeParser.parseTypes(arguments, PacketTypeParser.DEFAULT_MAX_RANGE);
|
||||
Boolean detailed = parseBoolean(arguments, "detailed");
|
||||
Boolean compare = parseBoolean(arguments, "compare");
|
||||
|
||||
// Notify user
|
||||
if (typeParser.getLastProtocol() == null) {
|
||||
sender.sendMessage(ChatColor.YELLOW + "Warning: Missing protocol (PLAY, etc) - assuming legacy IDs.");
|
||||
}
|
||||
if (arguments.size() > 0) {
|
||||
throw new IllegalArgumentException("Cannot parse " + arguments);
|
||||
}
|
||||
|
||||
// The last elements are optional
|
||||
if (detailed == null) {
|
||||
detailed = false;
|
||||
}
|
||||
if (compare == null) {
|
||||
compare = false;
|
||||
} else {
|
||||
// This is implied - we cannot compare content without going detailed
|
||||
detailed = true;
|
||||
}
|
||||
|
||||
// Perform commands
|
||||
if (subCommand == SubCommand.ADD) {
|
||||
// The add command is dangerous - don't default on the connection side
|
||||
if (args.length == 1) {
|
||||
sender.sendMessage(ChatColor.RED + "Please specify a connection side.");
|
||||
return false;
|
||||
}
|
||||
|
||||
executeAddCommand(sender, types, detailed, compare);
|
||||
} else if (subCommand == SubCommand.REMOVE) {
|
||||
executeRemoveCommand(sender, types);
|
||||
} else if (subCommand == SubCommand.NAMES) {
|
||||
executeNamesCommand(sender, types);
|
||||
}
|
||||
|
||||
} catch (NumberFormatException e) {
|
||||
sendMessageSilently(sender, ChatColor.RED + "Cannot parse number: " + e.getMessage());
|
||||
} catch (IllegalArgumentException e) {
|
||||
sendMessageSilently(sender, ChatColor.RED + e.getMessage());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void executeAddCommand(CommandSender sender, Set<PacketType> addition, boolean detailed, boolean compare) {
|
||||
packetTypes.addAll(addition);
|
||||
|
||||
// Also mark these types as "detailed"
|
||||
if (detailed) {
|
||||
extendedTypes.addAll(addition);
|
||||
}
|
||||
// Whether or not to compare the packet with the initial state
|
||||
if (compare) {
|
||||
compareTypes.addAll(addition);
|
||||
}
|
||||
|
||||
updatePacketListener();
|
||||
sendMessageSilently(sender, ChatColor.YELLOW + "Added listener " + getWhitelistInfo(listener));
|
||||
}
|
||||
|
||||
private void executeRemoveCommand(CommandSender sender, Set<PacketType> removal) {
|
||||
packetTypes.removeAll(removal);
|
||||
extendedTypes.removeAll(removal);
|
||||
compareTypes.removeAll(removal);
|
||||
updatePacketListener();
|
||||
sendMessageSilently(sender, ChatColor.YELLOW + "Removing packet types.");
|
||||
}
|
||||
|
||||
private void executeNamesCommand(CommandSender sender, Set<PacketType> types) {
|
||||
List<String> messages = new ArrayList<String>();
|
||||
|
||||
// Print the equivalent name of every given ID
|
||||
for (PacketType type : types) {
|
||||
messages.add(ChatColor.YELLOW + type.toString());
|
||||
}
|
||||
|
||||
if (sender instanceof Player && messages.size() > 0 && messages.size() > PAGE_LINE_COUNT) {
|
||||
// Divide the messages into chuncks
|
||||
pagedMessage.put(sender, messages);
|
||||
printPage(sender, 1);
|
||||
|
||||
} else {
|
||||
// Just print the damn thing
|
||||
for (String message : messages) {
|
||||
sendMessageSilently(sender, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve whitelist information about a given listener.
|
||||
* @param listener - the given listener.
|
||||
* @return Whitelist information.
|
||||
*/
|
||||
private String getWhitelistInfo(PacketListener listener) {
|
||||
boolean sendingEmpty = ListeningWhitelist.isEmpty(listener.getSendingWhitelist());
|
||||
boolean receivingEmpty = ListeningWhitelist.isEmpty(listener.getReceivingWhitelist());
|
||||
|
||||
if (!sendingEmpty && !receivingEmpty)
|
||||
return String.format("Sending: %s, Receiving: %s", listener.getSendingWhitelist(), listener.getReceivingWhitelist());
|
||||
else if (!sendingEmpty)
|
||||
return listener.getSendingWhitelist().toString();
|
||||
else if (!receivingEmpty)
|
||||
return listener.getReceivingWhitelist().toString();
|
||||
else
|
||||
return "[None]";
|
||||
}
|
||||
|
||||
private Set<PacketType> filterTypes(Set<PacketType> types, Sender sender) {
|
||||
Set<PacketType> result = Sets.newHashSet();
|
||||
|
||||
for (PacketType type : types) {
|
||||
if (type.getSender() == sender) {
|
||||
result.add(type);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public PacketListener createPacketListener(Set<PacketType> type) {
|
||||
final ListeningWhitelist serverList = ListeningWhitelist.newBuilder().
|
||||
types(filterTypes(type, Sender.SERVER)).
|
||||
gamePhaseBoth().
|
||||
monitor().
|
||||
build();
|
||||
|
||||
final ListeningWhitelist clientList = ListeningWhitelist.newBuilder(serverList).
|
||||
types(filterTypes(type, Sender.CLIENT)).
|
||||
monitor().
|
||||
build();
|
||||
|
||||
return new PacketListener() {
|
||||
@Override
|
||||
public void onPacketSending(PacketEvent event) {
|
||||
if (filter.filterEvent(event)) {
|
||||
printInformation(event);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketReceiving(PacketEvent event) {
|
||||
if (filter.filterEvent(event)) {
|
||||
printInformation(event);
|
||||
}
|
||||
}
|
||||
|
||||
private void printInformation(PacketEvent event) {
|
||||
String verb = event.isServerPacket() ? "Sent" : "Received";
|
||||
String format = event.isServerPacket() ?
|
||||
"%s %s to %s" :
|
||||
"%s %s from %s";
|
||||
|
||||
String shortDescription = String.format(format,
|
||||
event.isCancelled() ? "Cancelled" : verb,
|
||||
event.getPacketType(),
|
||||
event.getPlayer().getName()
|
||||
);
|
||||
|
||||
// Detailed will print the packet's content too
|
||||
if (extendedTypes.contains(event.getPacketType())) {
|
||||
try {
|
||||
String original = originalPackets.remove(event);
|
||||
|
||||
// Also print original
|
||||
if (original != null) {
|
||||
logger.info("Initial packet:\n" + original + " -> ");
|
||||
}
|
||||
|
||||
logger.info(shortDescription + ":\n" + getPacketDescription(
|
||||
event.getPacket())
|
||||
);
|
||||
} catch (IllegalAccessException e) {
|
||||
logger.log(Level.WARNING, "Unable to use reflection.", e);
|
||||
}
|
||||
} else {
|
||||
logger.info(shortDescription + ".");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListeningWhitelist getSendingWhitelist() {
|
||||
return serverList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListeningWhitelist getReceivingWhitelist() {
|
||||
return clientList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Plugin getPlugin() {
|
||||
return plugin;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public PacketListener createCompareListener(Set<PacketType> type) {
|
||||
final ListeningWhitelist serverList = ListeningWhitelist.newBuilder().
|
||||
types(filterTypes(type, Sender.SERVER)).
|
||||
gamePhaseBoth().
|
||||
lowest().
|
||||
build();
|
||||
|
||||
final ListeningWhitelist clientList = ListeningWhitelist.newBuilder(serverList).
|
||||
types(filterTypes(type, Sender.CLIENT)).
|
||||
lowest().
|
||||
build();
|
||||
|
||||
return new PacketListener() {
|
||||
@Override
|
||||
public void onPacketSending(PacketEvent event) {
|
||||
savePacketState(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketReceiving(PacketEvent event) {
|
||||
savePacketState(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the original value.
|
||||
* @param event - the event with the packet to save.
|
||||
*/
|
||||
private void savePacketState(PacketEvent event) {
|
||||
try {
|
||||
originalPackets.put(event, getPacketDescription(event.getPacket()));
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException("Cannot read packet.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListeningWhitelist getSendingWhitelist() {
|
||||
return serverList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListeningWhitelist getReceivingWhitelist() {
|
||||
return clientList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Plugin getPlugin() {
|
||||
return plugin;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a detailed string representation of the given packet.
|
||||
* @param packetContainer - the packet to describe.
|
||||
* @return The detailed description.
|
||||
* @throws IllegalAccessException An error occured.
|
||||
*/
|
||||
public String getPacketDescription(PacketContainer packetContainer) throws IllegalAccessException {
|
||||
Object packet = packetContainer.getHandle();
|
||||
Class<?> clazz = packet.getClass();
|
||||
|
||||
// Get the first Minecraft super class
|
||||
while (clazz != null && clazz != Object.class &&
|
||||
(!MinecraftReflection.isMinecraftClass(clazz) ||
|
||||
Factory.class.isAssignableFrom(clazz))) {
|
||||
clazz = clazz.getSuperclass();
|
||||
}
|
||||
|
||||
return PrettyPrinter.printObject(packet, clazz, MinecraftReflection.getPacketClass(), PrettyPrinter.RECURSE_DEPTH, new ObjectPrinter() {
|
||||
@Override
|
||||
public boolean print(StringBuilder output, Object value) {
|
||||
// Special case
|
||||
if (value instanceof byte[]) {
|
||||
byte[] data = (byte[]) value;
|
||||
|
||||
if (data.length > HEX_DUMP_THRESHOLD) {
|
||||
output.append("[");
|
||||
HexDumper.defaultDumper().appendTo(output, data);
|
||||
output.append("]");
|
||||
return true;
|
||||
}
|
||||
} else if (value != null) {
|
||||
EquivalentConverter<Object> converter = findConverter(value.getClass());
|
||||
|
||||
if (converter != null) {
|
||||
output.append(converter.getSpecific(value));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the closest equivalent converter to a specific class.
|
||||
* @param clazz - the class.
|
||||
* @return The closest converter, or NULL if not found,
|
||||
*/
|
||||
private EquivalentConverter<Object> findConverter(Class<?> clazz) {
|
||||
Map<Class<?>, EquivalentConverter<Object>> converters = BukkitConverters.getConvertersForGeneric();
|
||||
|
||||
while (clazz != null) {
|
||||
EquivalentConverter<Object> result = converters.get(clazz);
|
||||
|
||||
if (result != null)
|
||||
return result;
|
||||
else
|
||||
clazz = clazz.getSuperclass();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public PacketListener updatePacketListener() {
|
||||
if (listener != null) {
|
||||
manager.removePacketListener(listener);
|
||||
}
|
||||
if (compareListener != null) {
|
||||
manager.removePacketListener(compareListener);
|
||||
}
|
||||
|
||||
// Register the new listeners
|
||||
listener = createPacketListener(packetTypes.values());
|
||||
compareListener = createCompareListener(compareTypes.values());
|
||||
manager.addPacketListener(listener);
|
||||
manager.addPacketListener(compareListener);
|
||||
return listener;
|
||||
}
|
||||
|
||||
private SubCommand parseCommand(Deque<String> arguments) {
|
||||
String text = arguments.poll().toLowerCase();
|
||||
|
||||
// Parse this too
|
||||
if ("add".startsWith(text))
|
||||
return SubCommand.ADD;
|
||||
else if ("remove".startsWith(text))
|
||||
return SubCommand.REMOVE;
|
||||
else if ("names".startsWith(text))
|
||||
return SubCommand.NAMES;
|
||||
else if ("page".startsWith(text))
|
||||
return SubCommand.PAGE;
|
||||
else
|
||||
throw new IllegalArgumentException(text + " is not a valid sub command. Must be add or remove.");
|
||||
}
|
||||
}
|
||||
@@ -1,272 +0,0 @@
|
||||
/**
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
package com.comphenix.protocol;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.bukkit.plugin.PluginDescriptionFile;
|
||||
|
||||
import com.comphenix.protocol.error.DetailedErrorReporter;
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.events.PacketListener;
|
||||
import com.comphenix.protocol.timing.TimedListenerManager;
|
||||
import com.comphenix.protocol.timing.TimingReportGenerator;
|
||||
import com.comphenix.protocol.updater.Updater;
|
||||
import com.comphenix.protocol.updater.Updater.UpdateType;
|
||||
import com.comphenix.protocol.utility.Closer;
|
||||
|
||||
/**
|
||||
* Handles the "protocol" administration command.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
class CommandProtocol extends CommandBase {
|
||||
/**
|
||||
* Name of this command.
|
||||
*/
|
||||
public static final String NAME = "protocol";
|
||||
|
||||
private Plugin plugin;
|
||||
private Updater updater;
|
||||
private ProtocolConfig config;
|
||||
|
||||
public CommandProtocol(ErrorReporter reporter, Plugin plugin, Updater updater, ProtocolConfig config) {
|
||||
super(reporter, CommandBase.PERMISSION_ADMIN, NAME, 1);
|
||||
this.plugin = plugin;
|
||||
this.updater = updater;
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean handleCommand(CommandSender sender, String[] args) {
|
||||
String subCommand = args[0];
|
||||
|
||||
// Only return TRUE if we executed the correct command
|
||||
if (subCommand.equalsIgnoreCase("config") || subCommand.equalsIgnoreCase("reload")) {
|
||||
reloadConfiguration(sender);
|
||||
} else if (subCommand.equalsIgnoreCase("check")) {
|
||||
checkVersion(sender, true);
|
||||
} else if (subCommand.equalsIgnoreCase("update")) {
|
||||
updateVersion(sender, true);
|
||||
} else if (subCommand.equalsIgnoreCase("timings")) {
|
||||
toggleTimings(sender, args);
|
||||
} else if (subCommand.equalsIgnoreCase("listeners")) {
|
||||
printListeners(sender);
|
||||
} else if (subCommand.equalsIgnoreCase("version")) {
|
||||
printVersion(sender);
|
||||
} else if (subCommand.equalsIgnoreCase("dump")) {
|
||||
dump(sender);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void checkVersion(final CommandSender sender, boolean command) {
|
||||
performUpdate(sender, UpdateType.NO_DOWNLOAD, command);
|
||||
}
|
||||
|
||||
public void updateVersion(final CommandSender sender, boolean command) {
|
||||
performUpdate(sender, UpdateType.DEFAULT, command);
|
||||
}
|
||||
|
||||
// Display every listener on the server
|
||||
private void printListeners(final CommandSender sender) {
|
||||
ProtocolManager manager = ProtocolLibrary.getProtocolManager();
|
||||
|
||||
sender.sendMessage(ChatColor.GOLD + "Packet listeners:");
|
||||
for (PacketListener listener : manager.getPacketListeners()) {
|
||||
sender.sendMessage(ChatColor.GOLD + " - " + listener);
|
||||
}
|
||||
|
||||
// Along with every asynchronous listener
|
||||
sender.sendMessage(ChatColor.GOLD + "Asynchronous listeners:");
|
||||
for (PacketListener listener : manager.getAsynchronousManager().getAsyncHandlers()) {
|
||||
sender.sendMessage(ChatColor.GOLD + " - " + listener);
|
||||
}
|
||||
}
|
||||
|
||||
private void performUpdate(final CommandSender sender, UpdateType type, final boolean command) {
|
||||
if (updater.isChecking()) {
|
||||
sender.sendMessage(ChatColor.RED + "Already checking for an update.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Perform on an async thread
|
||||
Runnable notify = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (command) {
|
||||
sender.sendMessage(ChatColor.YELLOW + "[ProtocolLib] " + updater.getResult());
|
||||
String remoteVersion = updater.getRemoteVersion();
|
||||
if (remoteVersion != null) {
|
||||
sender.sendMessage(ChatColor.YELLOW + "Remote version: " + remoteVersion);
|
||||
sender.sendMessage(ChatColor.YELLOW + "Current version: " + plugin.getDescription().getVersion());
|
||||
}
|
||||
} else if (updater.shouldNotify() || config.isDebug()) {
|
||||
sender.sendMessage(ChatColor.YELLOW + "[ProtocolLib] " + updater.getResult());
|
||||
}
|
||||
|
||||
updater.removeListener(this);
|
||||
updateFinished();
|
||||
}
|
||||
};
|
||||
updater.start(type);
|
||||
updater.addListener(notify);
|
||||
}
|
||||
|
||||
private void toggleTimings(CommandSender sender, String[] args) {
|
||||
TimedListenerManager manager = TimedListenerManager.getInstance();
|
||||
boolean state = !manager.isTiming(); // toggle
|
||||
|
||||
// Parse the boolean parameter
|
||||
if (args.length == 2) {
|
||||
Boolean parsed = parseBoolean(toQueue(args, 2), "start");
|
||||
|
||||
if (parsed != null) {
|
||||
state = parsed;
|
||||
} else {
|
||||
sender.sendMessage(ChatColor.RED + "Specify a state: ON or OFF.");
|
||||
return;
|
||||
}
|
||||
} else if (args.length > 2) {
|
||||
sender.sendMessage(ChatColor.RED + "Too many parameters.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Now change the state
|
||||
if (state) {
|
||||
if (manager.startTiming())
|
||||
sender.sendMessage(ChatColor.GOLD + "Started timing packet listeners.");
|
||||
else
|
||||
sender.sendMessage(ChatColor.RED + "Packet timing already started.");
|
||||
} else {
|
||||
if (manager.stopTiming()) {
|
||||
saveTimings(manager);
|
||||
sender.sendMessage(ChatColor.GOLD + "Stopped and saved result in plugin folder.");
|
||||
} else {
|
||||
sender.sendMessage(ChatColor.RED + "Packet timing already stopped.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void saveTimings(TimedListenerManager manager) {
|
||||
try {
|
||||
File destination = new File(plugin.getDataFolder(), "Timings - " + System.currentTimeMillis() + ".txt");
|
||||
TimingReportGenerator generator = new TimingReportGenerator();
|
||||
|
||||
// Print to a text file
|
||||
generator.saveTo(destination, manager);
|
||||
manager.clear();
|
||||
|
||||
} catch (IOException e) {
|
||||
reporter.reportMinimal(plugin, "saveTimings()", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent further automatic updates until the next delay.
|
||||
*/
|
||||
public void updateFinished() {
|
||||
long currentTime = System.currentTimeMillis() / ProtocolLibrary.MILLI_PER_SECOND;
|
||||
|
||||
config.setAutoLastTime(currentTime);
|
||||
config.saveAll();
|
||||
}
|
||||
|
||||
public void reloadConfiguration(CommandSender sender) {
|
||||
plugin.reloadConfig();
|
||||
sender.sendMessage(ChatColor.YELLOW + "Reloaded configuration!");
|
||||
}
|
||||
|
||||
private void printVersion(CommandSender sender) {
|
||||
PluginDescriptionFile desc = plugin.getDescription();
|
||||
|
||||
sender.sendMessage(ChatColor.GREEN + desc.getName() + ChatColor.WHITE + " v" + ChatColor.GREEN + desc.getVersion());
|
||||
sender.sendMessage(ChatColor.WHITE + "Authors: " + ChatColor.GREEN + "dmulloy2" + ChatColor.WHITE + " and " + ChatColor.GREEN + "Comphenix");
|
||||
sender.sendMessage(ChatColor.WHITE + "Issues: " + ChatColor.GREEN + "https://github.com/dmulloy2/ProtocolLib/issues");
|
||||
}
|
||||
|
||||
private static SimpleDateFormat FILE_FORMAT;
|
||||
private static SimpleDateFormat TIMESTAMP_FORMAT;
|
||||
|
||||
private void dump(CommandSender sender) {
|
||||
Closer closer = Closer.create();
|
||||
|
||||
if (FILE_FORMAT == null)
|
||||
FILE_FORMAT = new SimpleDateFormat("yyyy-MM-dd_HH.mm.ss");
|
||||
if (TIMESTAMP_FORMAT == null)
|
||||
TIMESTAMP_FORMAT = new SimpleDateFormat("MM/dd/yy HH:mm:ss");
|
||||
|
||||
try {
|
||||
Date date = new Date();
|
||||
File file = new File(plugin.getDataFolder(), "dump-" + FILE_FORMAT.format(date) + ".txt");
|
||||
if (file.exists()) {
|
||||
file.delete();
|
||||
}
|
||||
|
||||
file.createNewFile();
|
||||
|
||||
FileWriter fw = closer.register(new FileWriter(file));
|
||||
PrintWriter pw = closer.register(new PrintWriter(fw));
|
||||
|
||||
pw.println("ProtocolLib Dump");
|
||||
pw.println("Timestamp: " + TIMESTAMP_FORMAT.format(date));
|
||||
pw.println();
|
||||
|
||||
pw.println("ProtocolLib Version: " + plugin.toString());
|
||||
pw.println("Bukkit Version: " + plugin.getServer().getBukkitVersion());
|
||||
pw.println("Server Version: " + plugin.getServer().getVersion());
|
||||
pw.println("Java Version: " + System.getProperty("java.version"));
|
||||
pw.println();
|
||||
|
||||
ProtocolManager manager = ProtocolLibrary.getProtocolManager();
|
||||
pw.println("ProtocolLib: " + DetailedErrorReporter.getStringDescription(plugin));
|
||||
pw.println("Manager: " + DetailedErrorReporter.getStringDescription(manager));
|
||||
pw.println();
|
||||
|
||||
Set<PacketListener> listeners = manager.getPacketListeners();
|
||||
if (listeners.size() > 0) {
|
||||
pw.println("Listeners:");
|
||||
|
||||
for (PacketListener listener : listeners) {
|
||||
pw.println(DetailedErrorReporter.getStringDescription(listener));
|
||||
}
|
||||
} else {
|
||||
pw.println("No listeners");
|
||||
}
|
||||
|
||||
sender.sendMessage("Data dump written to " + file.getAbsolutePath());
|
||||
} catch (IOException ex) {
|
||||
ProtocolLibrary.log(Level.SEVERE, "Failed to create dump:", ex);
|
||||
sender.sendMessage(ChatColor.RED + "Failed to create dump! Check console!");
|
||||
} finally {
|
||||
closer.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,163 +0,0 @@
|
||||
package com.comphenix.protocol;
|
||||
|
||||
import org.bukkit.conversations.Conversation;
|
||||
import org.bukkit.conversations.ConversationCanceller;
|
||||
import org.bukkit.conversations.ConversationContext;
|
||||
import org.bukkit.conversations.ExactMatchConversationCanceller;
|
||||
import org.bukkit.conversations.Prompt;
|
||||
import org.bukkit.conversations.StringPrompt;
|
||||
|
||||
/**
|
||||
* Represents a conversation prompt that accepts a list of lines.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
class MultipleLinesPrompt extends StringPrompt {
|
||||
/**
|
||||
* Represents a canceller that determines if the multiple lines prompt is finished.
|
||||
* @author Kristian
|
||||
*/
|
||||
public static interface MultipleConversationCanceller extends ConversationCanceller {
|
||||
@Override
|
||||
public boolean cancelBasedOnInput(ConversationContext context, String currentLine);
|
||||
|
||||
/**
|
||||
* Determine if the current prompt is done based on the context, last
|
||||
* line and collected lines.
|
||||
*
|
||||
* @param context - current context.
|
||||
* @param currentLine - current (last) line.
|
||||
* @param lines - collected lines.
|
||||
* @param lineCount - number of lines.
|
||||
* @return TRUE if we are done, FALSE otherwise.
|
||||
*/
|
||||
public boolean cancelBasedOnInput(ConversationContext context, String currentLine,
|
||||
StringBuilder lines, int lineCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* A wrapper class for turning a ConversationCanceller into a MultipleConversationCanceller.
|
||||
* @author Kristian
|
||||
*/
|
||||
private static class MultipleWrapper implements MultipleConversationCanceller {
|
||||
private ConversationCanceller canceller;
|
||||
|
||||
public MultipleWrapper(ConversationCanceller canceller) {
|
||||
this.canceller = canceller;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean cancelBasedOnInput(ConversationContext context, String currentLine) {
|
||||
return canceller.cancelBasedOnInput(context, currentLine);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean cancelBasedOnInput(ConversationContext context, String currentLine,
|
||||
StringBuilder lines, int lineCount) {
|
||||
return cancelBasedOnInput(context, currentLine);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setConversation(Conversation conversation) {
|
||||
canceller.setConversation(conversation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MultipleWrapper clone() {
|
||||
return new MultipleWrapper(canceller.clone());
|
||||
}
|
||||
}
|
||||
|
||||
// Feels a bit like Android
|
||||
private static final String KEY = "multiple_lines_prompt";
|
||||
private static final String KEY_LAST = KEY + ".last_line";
|
||||
private static final String KEY_LINES = KEY + ".linecount";
|
||||
|
||||
private final MultipleConversationCanceller endMarker;
|
||||
private final String initialPrompt;
|
||||
|
||||
/**
|
||||
* Retrieve and remove the current accumulated input.
|
||||
*
|
||||
* @param context
|
||||
* - conversation context.
|
||||
* @return The accumulated input, or NULL if not found.
|
||||
*/
|
||||
public String removeAccumulatedInput(ConversationContext context) {
|
||||
Object result = context.getSessionData(KEY);
|
||||
|
||||
if (result instanceof StringBuilder) {
|
||||
context.setSessionData(KEY, null);
|
||||
context.setSessionData(KEY_LINES, null);
|
||||
return ((StringBuilder) result).toString();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a multiple lines input prompt with a specific end marker.
|
||||
* <p>
|
||||
* This is usually an empty string.
|
||||
*
|
||||
* @param endMarker - the end marker.
|
||||
*/
|
||||
public MultipleLinesPrompt(String endMarker, String initialPrompt) {
|
||||
this(new ExactMatchConversationCanceller(endMarker), initialPrompt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a multiple lines input prompt with a specific end marker implementation.
|
||||
* <p>
|
||||
* Note: Use {@link #MultipleLinesPrompt(MultipleConversationCanceller, String)} if implementing a custom canceller.
|
||||
* @param endMarker - the end marker.
|
||||
* @param initialPrompt - the initial prompt text.
|
||||
*/
|
||||
public MultipleLinesPrompt(ConversationCanceller endMarker, String initialPrompt) {
|
||||
this.endMarker = new MultipleWrapper(endMarker);
|
||||
this.initialPrompt = initialPrompt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a multiple lines input prompt with a specific end marker implementation.
|
||||
* @param endMarker - the end marker.
|
||||
* @param initialPrompt - the initial prompt text.
|
||||
*/
|
||||
public MultipleLinesPrompt(MultipleConversationCanceller endMarker, String initialPrompt) {
|
||||
this.endMarker = endMarker;
|
||||
this.initialPrompt = initialPrompt;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Prompt acceptInput(ConversationContext context, String in) {
|
||||
StringBuilder result = (StringBuilder) context.getSessionData(KEY);
|
||||
Integer count = (Integer) context.getSessionData(KEY_LINES);
|
||||
|
||||
// Handle first run
|
||||
if (result == null)
|
||||
context.setSessionData(KEY, result = new StringBuilder());
|
||||
if (count == null)
|
||||
count = 0;
|
||||
|
||||
// Save the last line as well
|
||||
context.setSessionData(KEY_LAST, in);
|
||||
context.setSessionData(KEY_LINES, ++count);
|
||||
result.append(in + "\n");
|
||||
|
||||
// And we're done
|
||||
if (endMarker.cancelBasedOnInput(context, in, result, count))
|
||||
return Prompt.END_OF_CONVERSATION;
|
||||
else
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPromptText(ConversationContext context) {
|
||||
Object last = context.getSessionData(KEY_LAST);
|
||||
|
||||
if (last instanceof String)
|
||||
return (String) last;
|
||||
else
|
||||
return initialPrompt;
|
||||
}
|
||||
}
|
||||
@@ -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,187 +0,0 @@
|
||||
package com.comphenix.protocol;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import com.comphenix.protocol.PacketType.Protocol;
|
||||
import com.comphenix.protocol.PacketType.Sender;
|
||||
import com.comphenix.protocol.collections.IntegerMap;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.HashMultimap;
|
||||
import com.google.common.collect.Multimap;
|
||||
|
||||
/**
|
||||
* Retrieve a packet type based on its version and ID, optionally with protocol and sender too.
|
||||
* @author Kristian
|
||||
*/
|
||||
class PacketTypeLookup {
|
||||
public static class ProtocolSenderLookup {
|
||||
// Unroll lookup for performance reasons
|
||||
public final IntegerMap<PacketType> HANDSHAKE_CLIENT = IntegerMap.newMap();
|
||||
public final IntegerMap<PacketType> HANDSHAKE_SERVER = IntegerMap.newMap();
|
||||
public final IntegerMap<PacketType> GAME_CLIENT = IntegerMap.newMap();
|
||||
public final IntegerMap<PacketType> GAME_SERVER = IntegerMap.newMap();
|
||||
public final IntegerMap<PacketType> STATUS_CLIENT = IntegerMap.newMap();
|
||||
public final IntegerMap<PacketType> STATUS_SERVER = IntegerMap.newMap();
|
||||
public final IntegerMap<PacketType> LOGIN_CLIENT = IntegerMap.newMap();
|
||||
public final IntegerMap<PacketType> LOGIN_SERVER = IntegerMap.newMap();
|
||||
|
||||
/**
|
||||
* Retrieve the correct integer map for a specific protocol and sender.
|
||||
* @param protocol - the protocol.
|
||||
* @param sender - the sender.
|
||||
* @return The integer map of packets.
|
||||
*/
|
||||
public IntegerMap<PacketType> getMap(Protocol protocol, Sender sender) {
|
||||
switch (protocol) {
|
||||
case HANDSHAKING:
|
||||
return sender == Sender.CLIENT ? HANDSHAKE_CLIENT : HANDSHAKE_SERVER;
|
||||
case PLAY:
|
||||
return sender == Sender.CLIENT ? GAME_CLIENT : GAME_SERVER;
|
||||
case STATUS:
|
||||
return sender == Sender.CLIENT ? STATUS_CLIENT : STATUS_SERVER;
|
||||
case LOGIN:
|
||||
return sender == Sender.CLIENT ? LOGIN_CLIENT : LOGIN_SERVER;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unable to find protocol " + protocol);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class ClassLookup {
|
||||
// Unroll lookup for performance reasons
|
||||
public final Map<String, PacketType> HANDSHAKE_CLIENT = new ConcurrentHashMap<String, PacketType>();
|
||||
public final Map<String, PacketType> HANDSHAKE_SERVER = new ConcurrentHashMap<String, PacketType>();
|
||||
public final Map<String, PacketType> GAME_CLIENT = new ConcurrentHashMap<String, PacketType>();
|
||||
public final Map<String, PacketType> GAME_SERVER = new ConcurrentHashMap<String, PacketType>();
|
||||
public final Map<String, PacketType> STATUS_CLIENT = new ConcurrentHashMap<String, PacketType>();
|
||||
public final Map<String, PacketType> STATUS_SERVER = new ConcurrentHashMap<String, PacketType>();
|
||||
public final Map<String, PacketType> LOGIN_CLIENT = new ConcurrentHashMap<String, PacketType>();
|
||||
public final Map<String, PacketType> LOGIN_SERVER = new ConcurrentHashMap<String, PacketType>();
|
||||
|
||||
/**
|
||||
* Retrieve the correct integer map for a specific protocol and sender.
|
||||
* @param protocol - the protocol.
|
||||
* @param sender - the sender.
|
||||
* @return The integer map of packets.
|
||||
*/
|
||||
public Map<String, PacketType> getMap(Protocol protocol, Sender sender) {
|
||||
switch (protocol) {
|
||||
case HANDSHAKING:
|
||||
return sender == Sender.CLIENT ? HANDSHAKE_CLIENT : HANDSHAKE_SERVER;
|
||||
case PLAY:
|
||||
return sender == Sender.CLIENT ? GAME_CLIENT : GAME_SERVER;
|
||||
case STATUS:
|
||||
return sender == Sender.CLIENT ? STATUS_CLIENT : STATUS_SERVER;
|
||||
case LOGIN:
|
||||
return sender == Sender.CLIENT ? LOGIN_CLIENT : LOGIN_SERVER;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unable to find protocol " + protocol);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Packet IDs from 1.6.4 and below
|
||||
private final IntegerMap<PacketType> legacyLookup = new IntegerMap<PacketType>();
|
||||
private final IntegerMap<PacketType> serverLookup = new IntegerMap<PacketType>();
|
||||
private final IntegerMap<PacketType> clientLookup = new IntegerMap<PacketType>();
|
||||
|
||||
// Packets for 1.7.2
|
||||
private final ProtocolSenderLookup idLookup = new ProtocolSenderLookup();
|
||||
|
||||
// Packets for 1.8+
|
||||
private final ClassLookup classLookup = new ClassLookup();
|
||||
|
||||
// Packets based on name
|
||||
private final Multimap<String, PacketType> nameLookup = HashMultimap.create();
|
||||
|
||||
/**
|
||||
* Add a collection of packet types to the lookup.
|
||||
* @param types - the types to add.
|
||||
*/
|
||||
public PacketTypeLookup addPacketTypes(Iterable<? extends PacketType> types) {
|
||||
Preconditions.checkNotNull(types, "types cannot be NULL");
|
||||
|
||||
for (PacketType type : types) {
|
||||
int legacy = type.getLegacyId();
|
||||
|
||||
// Skip unknown legacy packets
|
||||
if (legacy != PacketType.UNKNOWN_PACKET) {
|
||||
if (type.isServer())
|
||||
serverLookup.put(type.getLegacyId(), type);
|
||||
if (type.isClient())
|
||||
clientLookup.put(type.getLegacyId(), type);
|
||||
legacyLookup.put(type.getLegacyId(), type);
|
||||
}
|
||||
// Skip unknown current packets
|
||||
if (type.getCurrentId() != PacketType.UNKNOWN_PACKET) {
|
||||
idLookup.getMap(type.getProtocol(), type.getSender()).put(type.getCurrentId(), type);
|
||||
classLookup.getMap(type.getProtocol(), type.getSender()).put(type.getClassNames()[0], type);
|
||||
}
|
||||
nameLookup.put(type.name(), type);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a packet type from a legacy (1.6.4 and below) packet ID.
|
||||
* @param packetId - the legacy packet ID.
|
||||
* @return The corresponding packet type, or NULL if not found.
|
||||
*/
|
||||
public PacketType getFromLegacy(int packetId) {
|
||||
return legacyLookup.get(packetId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve an unmodifiable view of all the packet types with this name.
|
||||
* @param name - the name.
|
||||
* @return The packet types, usually one.
|
||||
*/
|
||||
public Collection<PacketType> getFromName(String name) {
|
||||
return Collections.unmodifiableCollection(nameLookup.get(name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a packet type from a legacy (1.6.4 and below) packet ID.
|
||||
* @param packetId - the legacy packet ID.
|
||||
* @param preference - which packet type to look for first.
|
||||
* @return The corresponding packet type, or NULL if not found.
|
||||
*/
|
||||
public PacketType getFromLegacy(int packetId, Sender preference) {
|
||||
if (preference == Sender.CLIENT)
|
||||
return getFirst(packetId, clientLookup, serverLookup);
|
||||
else
|
||||
return getFirst(packetId, serverLookup, clientLookup);
|
||||
}
|
||||
|
||||
// Helper method for looking up in two sets
|
||||
private <T> T getFirst(int packetId, IntegerMap<T> first, IntegerMap<T> second) {
|
||||
if (first.containsKey(packetId))
|
||||
return first.get(packetId);
|
||||
else
|
||||
return second.get(packetId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a packet type from a protocol, sender and packet ID.
|
||||
* @param protocol - the current protocol.
|
||||
* @param sender - the sender.
|
||||
* @param packetId - the packet ID.
|
||||
* @return The corresponding packet type, or NULL if not found.
|
||||
* @deprecated IDs are no longer reliable
|
||||
*/
|
||||
@Deprecated
|
||||
public PacketType getFromCurrent(Protocol protocol, Sender sender, int packetId) {
|
||||
return idLookup.getMap(protocol, sender).get(packetId);
|
||||
}
|
||||
|
||||
public PacketType getFromCurrent(Protocol protocol, Sender sender, String name) {
|
||||
return classLookup.getMap(protocol, sender).get(name);
|
||||
}
|
||||
|
||||
public ClassLookup getClassLookup() {
|
||||
return classLookup;
|
||||
}
|
||||
}
|
||||
@@ -1,146 +0,0 @@
|
||||
package com.comphenix.protocol;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Deque;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import com.comphenix.protocol.PacketType.Protocol;
|
||||
import com.comphenix.protocol.PacketType.Sender;
|
||||
import com.comphenix.protocol.compat.guava.Guava;
|
||||
import com.comphenix.protocol.events.ConnectionSide;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Range;
|
||||
import com.google.common.collect.Sets;
|
||||
|
||||
class PacketTypeParser {
|
||||
public final static Range<Integer> DEFAULT_MAX_RANGE = Guava.closedRange(0, 255);
|
||||
|
||||
private Sender side = null;
|
||||
private Protocol protocol = null;
|
||||
|
||||
public Set<PacketType> parseTypes(Deque<String> arguments, Range<Integer> defaultRange) {
|
||||
Set<PacketType> result = Sets.newHashSet();
|
||||
side = null;
|
||||
protocol = null;
|
||||
|
||||
// Find these first
|
||||
while (side == null) {
|
||||
String arg = arguments.poll();
|
||||
|
||||
// Attempt to parse a side or protocol first
|
||||
if (side == null) {
|
||||
ConnectionSide connection = parseSide(arg);
|
||||
|
||||
if (connection != null) {
|
||||
side = connection.getSender();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (protocol == null) {
|
||||
if ((protocol = parseProtocol(arg)) != null) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Specify connection side (CLIENT or SERVER).");
|
||||
}
|
||||
|
||||
// Then we move on to parsing IDs (named packet types soon to come)
|
||||
List<Range<Integer>> ranges = RangeParser.getRanges(arguments, DEFAULT_MAX_RANGE);
|
||||
|
||||
// And finally, parse packet names if we have a protocol
|
||||
if (protocol != null) {
|
||||
for (Iterator<String> it = arguments.iterator(); it.hasNext(); ) {
|
||||
String name = it.next().toUpperCase();
|
||||
Collection<PacketType> names = PacketType.fromName(name);
|
||||
|
||||
for (PacketType type : names) {
|
||||
if (type.getProtocol() == protocol && type.getSender() == side) {
|
||||
result.add(type);
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Supply a default integer range
|
||||
if (ranges.isEmpty() && result.isEmpty()) {
|
||||
ranges = Lists.newArrayList();
|
||||
ranges.add(defaultRange);
|
||||
}
|
||||
|
||||
for (Range<Integer> range : ranges) {
|
||||
for (Integer id : Guava.toSet(range)) {
|
||||
// Deprecated packets
|
||||
if (protocol == null) {
|
||||
if (PacketType.hasLegacy(id)) {
|
||||
result.add(PacketType.findLegacy(id, side));
|
||||
}
|
||||
} else {
|
||||
if (PacketType.hasCurrent(protocol, side, id)) {
|
||||
result.add(PacketType.findCurrent(protocol, side, id));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the last parsed protocol.
|
||||
* @return Last protocol.
|
||||
*/
|
||||
public Protocol getLastProtocol() {
|
||||
return protocol;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the last sender.
|
||||
* @return Last sender.
|
||||
*/
|
||||
public Sender getLastSide() {
|
||||
return side;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a connection sides from a string.
|
||||
* @param text - the possible connection side.
|
||||
* @return The connection side, or NULL if not found.
|
||||
*/
|
||||
public ConnectionSide parseSide(String text) {
|
||||
if (text == null)
|
||||
return null;
|
||||
String candidate = text.toLowerCase();
|
||||
|
||||
// Parse the side gracefully
|
||||
if ("client".startsWith(candidate))
|
||||
return ConnectionSide.CLIENT_SIDE;
|
||||
else if ("server".startsWith(candidate))
|
||||
return ConnectionSide.SERVER_SIDE;
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a protocol from a string.
|
||||
* @param text - the possible protocol.
|
||||
* @return The protocol, or NULL if not found.
|
||||
*/
|
||||
public Protocol parseProtocol(String text) {
|
||||
if (text == null)
|
||||
return null;
|
||||
String candidate = text.toLowerCase();
|
||||
|
||||
if ("handshake".equals(candidate) || "handshaking".equals(candidate))
|
||||
return Protocol.HANDSHAKING;
|
||||
else if ("login".equals(candidate))
|
||||
return Protocol.LOGIN;
|
||||
else if ("play".equals(candidate) || "game".equals(candidate))
|
||||
return Protocol.PLAY;
|
||||
else if ("status".equals(candidate))
|
||||
return Protocol.STATUS;
|
||||
else
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -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,500 +0,0 @@
|
||||
/**
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
package com.comphenix.protocol;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.bukkit.configuration.Configuration;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks;
|
||||
import com.google.common.base.Charsets;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.io.Files;
|
||||
|
||||
/**
|
||||
* Represents the configuration of ProtocolLib.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public class ProtocolConfig {
|
||||
private static final String LAST_UPDATE_FILE = "lastupdate";
|
||||
|
||||
private static final String SECTION_GLOBAL = "global";
|
||||
private static final String SECTION_AUTOUPDATER = "auto updater";
|
||||
|
||||
private static final String METRICS_ENABLED = "metrics";
|
||||
|
||||
private static final String IGNORE_VERSION_CHECK = "ignore version check";
|
||||
private static final String BACKGROUND_COMPILER_ENABLED = "background compiler";
|
||||
|
||||
private static final String DEBUG_MODE_ENABLED = "debug";
|
||||
private static final String DETAILED_ERROR = "detailed error";
|
||||
private static final String INJECTION_METHOD = "injection method";
|
||||
|
||||
private static final String SCRIPT_ENGINE_NAME = "script engine";
|
||||
private static final String SUPPRESSED_REPORTS = "suppressed reports";
|
||||
|
||||
private static final String UPDATER_NOTIFY = "notify";
|
||||
private static final String UPDATER_DOWNLAD = "download";
|
||||
private static final String UPDATER_DELAY = "delay";
|
||||
|
||||
// Defaults
|
||||
private static final long DEFAULT_UPDATER_DELAY = 43200;
|
||||
|
||||
private Plugin plugin;
|
||||
private Configuration config;
|
||||
private boolean loadingSections;
|
||||
|
||||
private ConfigurationSection global;
|
||||
private ConfigurationSection updater;
|
||||
|
||||
// Last update time
|
||||
private long lastUpdateTime;
|
||||
private boolean configChanged;
|
||||
private boolean valuesChanged;
|
||||
|
||||
// Modifications
|
||||
private int modCount;
|
||||
|
||||
public ProtocolConfig(Plugin plugin) {
|
||||
this.plugin = plugin;
|
||||
reloadConfig();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reload configuration file.
|
||||
*/
|
||||
public void reloadConfig() {
|
||||
// Reset
|
||||
configChanged = false;
|
||||
valuesChanged = false;
|
||||
modCount++;
|
||||
|
||||
this.config = plugin.getConfig();
|
||||
this.lastUpdateTime = loadLastUpdate();
|
||||
loadSections(!loadingSections);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the last update time stamp from the file system.
|
||||
*
|
||||
* @return Last update time stamp.
|
||||
*/
|
||||
private long loadLastUpdate() {
|
||||
File dataFile = getLastUpdateFile();
|
||||
|
||||
if (dataFile.exists()) {
|
||||
try {
|
||||
return Long.parseLong(Files.toString(dataFile, Charsets.UTF_8));
|
||||
} catch (NumberFormatException e) {
|
||||
plugin.getLogger().warning("Cannot parse " + dataFile + " as a number.");
|
||||
} catch (IOException e) {
|
||||
plugin.getLogger().warning("Cannot read " + dataFile);
|
||||
}
|
||||
}
|
||||
// Default last update
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the given time stamp.
|
||||
*
|
||||
* @param value - time stamp to store.
|
||||
*/
|
||||
private void saveLastUpdate(long value) {
|
||||
File dataFile = getLastUpdateFile();
|
||||
|
||||
// The data folder must exist
|
||||
dataFile.getParentFile().mkdirs();
|
||||
|
||||
if (dataFile.exists())
|
||||
dataFile.delete();
|
||||
|
||||
try {
|
||||
Files.write(Long.toString(value), dataFile, Charsets.UTF_8);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Cannot write " + dataFile, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the file that is used to store the update time stamp.
|
||||
*
|
||||
* @return File storing the update time stamp.
|
||||
*/
|
||||
private File getLastUpdateFile() {
|
||||
return new File(plugin.getDataFolder(), LAST_UPDATE_FILE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load data sections.
|
||||
*
|
||||
* @param copyDefaults - whether or not to copy configuration defaults.
|
||||
*/
|
||||
private void loadSections(boolean copyDefaults) {
|
||||
if (config != null) {
|
||||
global = config.getConfigurationSection(SECTION_GLOBAL);
|
||||
}
|
||||
if (global != null) {
|
||||
updater = global.getConfigurationSection(SECTION_AUTOUPDATER);
|
||||
}
|
||||
|
||||
// Automatically copy defaults
|
||||
if (copyDefaults && (!getFile().exists() || global == null || updater == null)) {
|
||||
loadingSections = true;
|
||||
|
||||
if (config != null)
|
||||
config.options().copyDefaults(true);
|
||||
plugin.saveDefaultConfig();
|
||||
plugin.reloadConfig();
|
||||
loadingSections = false;
|
||||
|
||||
// Inform the user
|
||||
plugin.getLogger().info("Created default configuration.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a particular configuration key value pair.
|
||||
*
|
||||
* @param section - the configuration root.
|
||||
* @param path - the path to the key.
|
||||
* @param value - the value to set.
|
||||
*/
|
||||
private void setConfig(ConfigurationSection section, String path, Object value) {
|
||||
configChanged = true;
|
||||
section.set(path, value);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> T getGlobalValue(String path, T def) {
|
||||
try {
|
||||
return (T) global.get(path, def);
|
||||
} catch (Throwable ex) {
|
||||
return def;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> T getUpdaterValue(String path, T def) {
|
||||
try {
|
||||
return (T) updater.get(path, def);
|
||||
} catch (Throwable ex) {
|
||||
return def;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a reference to the configuration file.
|
||||
*
|
||||
* @return Configuration file on disk.
|
||||
*/
|
||||
public File getFile() {
|
||||
return new File(plugin.getDataFolder(), "config.yml");
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if detailed error reporting is enabled. Default FALSE.
|
||||
*
|
||||
* @return TRUE if it is enabled, FALSE otherwise.
|
||||
*/
|
||||
public boolean isDetailedErrorReporting() {
|
||||
return getGlobalValue(DETAILED_ERROR, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether or not detailed error reporting is enabled.
|
||||
*
|
||||
* @param value - TRUE if it is enabled, FALSE otherwise.
|
||||
*/
|
||||
public void setDetailedErrorReporting(boolean value) {
|
||||
global.set(DETAILED_ERROR, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve whether or not ProtocolLib should determine if a new version has been released.
|
||||
*
|
||||
* @return TRUE if it should do this automatically, FALSE otherwise.
|
||||
*/
|
||||
public boolean isAutoNotify() {
|
||||
return getUpdaterValue(UPDATER_NOTIFY, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether or not ProtocolLib should determine if a new version has been released.
|
||||
*
|
||||
* @param value - TRUE to do this automatically, FALSE otherwise.
|
||||
*/
|
||||
public void setAutoNotify(boolean value) {
|
||||
setConfig(updater, UPDATER_NOTIFY, value);
|
||||
modCount++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve whether or not ProtocolLib should automatically download the new version.
|
||||
*
|
||||
* @return TRUE if it should, FALSE otherwise.
|
||||
*/
|
||||
public boolean isAutoDownload() {
|
||||
return updater != null && getUpdaterValue(UPDATER_DOWNLAD, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether or not ProtocolLib should automatically download the new version.
|
||||
*
|
||||
* @param value - TRUE if it should. FALSE otherwise.
|
||||
*/
|
||||
public void setAutoDownload(boolean value) {
|
||||
setConfig(updater, UPDATER_DOWNLAD, value);
|
||||
modCount++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether or not debug mode is enabled.
|
||||
* <p>
|
||||
* This grants access to the filter command.
|
||||
*
|
||||
* @return TRUE if it is, FALSE otherwise.
|
||||
*/
|
||||
public boolean isDebug() {
|
||||
return getGlobalValue(DEBUG_MODE_ENABLED, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether or not debug mode is enabled.
|
||||
*
|
||||
* @param value - TRUE if it is enabled, FALSE otherwise.
|
||||
*/
|
||||
public void setDebug(boolean value) {
|
||||
setConfig(global, DEBUG_MODE_ENABLED, value);
|
||||
modCount++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve an immutable list of every suppressed report type.
|
||||
*
|
||||
* @return Every suppressed report type.
|
||||
*/
|
||||
public ImmutableList<String> getSuppressedReports() {
|
||||
return ImmutableList.copyOf(getGlobalValue(SUPPRESSED_REPORTS, new ArrayList<String>()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the list of suppressed report types,
|
||||
*
|
||||
* @param reports - suppressed report types.
|
||||
*/
|
||||
public void setSuppressedReports(List<String> reports) {
|
||||
global.set(SUPPRESSED_REPORTS, Lists.newArrayList(reports));
|
||||
modCount++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the amount of time to wait until checking for a new update.
|
||||
*
|
||||
* @return The amount of time to wait.
|
||||
*/
|
||||
public long getAutoDelay() {
|
||||
// Note that the delay must be greater than 59 seconds
|
||||
return Math.max(getUpdaterValue(UPDATER_DELAY, 0), DEFAULT_UPDATER_DELAY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the amount of time to wait until checking for a new update.
|
||||
* <p>
|
||||
* This time must be greater than 59 seconds.
|
||||
*
|
||||
* @param delaySeconds - the amount of time to wait.
|
||||
*/
|
||||
public void setAutoDelay(long delaySeconds) {
|
||||
// Silently fix the delay
|
||||
if (delaySeconds < DEFAULT_UPDATER_DELAY)
|
||||
delaySeconds = DEFAULT_UPDATER_DELAY;
|
||||
setConfig(updater, UPDATER_DELAY, delaySeconds);
|
||||
modCount++;
|
||||
}
|
||||
|
||||
/**
|
||||
* The version of Minecraft to ignore the built-in safety feature.
|
||||
*
|
||||
* @return The version to ignore ProtocolLib's satefy.
|
||||
*/
|
||||
public String getIgnoreVersionCheck() {
|
||||
return getGlobalValue(IGNORE_VERSION_CHECK, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets under which version of Minecraft the version safety feature will be ignored.
|
||||
* <p>
|
||||
* This is useful if a server operator has tested and verified that a version of ProtocolLib works, but doesn't want or can't upgrade to a newer version.
|
||||
*
|
||||
* @param ignoreVersion - the version of Minecraft where the satefy will be disabled.
|
||||
*/
|
||||
public void setIgnoreVersionCheck(String ignoreVersion) {
|
||||
setConfig(global, IGNORE_VERSION_CHECK, ignoreVersion);
|
||||
modCount++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve whether or not metrics is enabled.
|
||||
*
|
||||
* @return TRUE if metrics is enabled, FALSE otherwise.
|
||||
*/
|
||||
public boolean isMetricsEnabled() {
|
||||
return getGlobalValue(METRICS_ENABLED, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether or not metrics is enabled.
|
||||
* <p>
|
||||
* This setting will take effect next time ProtocolLib is started.
|
||||
*
|
||||
* @param enabled - whether or not metrics is enabled.
|
||||
*/
|
||||
public void setMetricsEnabled(boolean enabled) {
|
||||
setConfig(global, METRICS_ENABLED, enabled);
|
||||
modCount++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve whether or not the background compiler for structure modifiers is enabled or not.
|
||||
*
|
||||
* @return TRUE if it is enabled, FALSE otherwise.
|
||||
*/
|
||||
public boolean isBackgroundCompilerEnabled() {
|
||||
return getGlobalValue(BACKGROUND_COMPILER_ENABLED, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether or not the background compiler for structure modifiers is enabled or not.
|
||||
* <p>
|
||||
* This setting will take effect next time ProtocolLib is started.
|
||||
*
|
||||
* @param enabled - TRUE if is enabled/running, FALSE otherwise.
|
||||
*/
|
||||
public void setBackgroundCompilerEnabled(boolean enabled) {
|
||||
setConfig(global, BACKGROUND_COMPILER_ENABLED, enabled);
|
||||
modCount++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the last time we updated, in seconds since 1970.01.01 00:00.
|
||||
*
|
||||
* @return Last update time.
|
||||
*/
|
||||
public long getAutoLastTime() {
|
||||
return lastUpdateTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the last time we updated, in seconds since 1970.01.01 00:00.
|
||||
* <p>
|
||||
* Note that this is not considered to modify the configuration, so the modification count will not be incremented.
|
||||
*
|
||||
* @param lastTimeSeconds - new last update time.
|
||||
*/
|
||||
public void setAutoLastTime(long lastTimeSeconds) {
|
||||
this.valuesChanged = true;
|
||||
this.lastUpdateTime = lastTimeSeconds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the unique name of the script engine to use for filtering.
|
||||
*
|
||||
* @return Script engine to use.
|
||||
*/
|
||||
public String getScriptEngineName() {
|
||||
return getGlobalValue(SCRIPT_ENGINE_NAME, "JavaScript");
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the unique name of the script engine to use for filtering.
|
||||
* <p>
|
||||
* This setting will take effect next time ProtocolLib is started.
|
||||
*
|
||||
* @param name - name of the script engine to use.
|
||||
*/
|
||||
public void setScriptEngineName(String name) {
|
||||
setConfig(global, SCRIPT_ENGINE_NAME, name);
|
||||
modCount++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the default injection method.
|
||||
*
|
||||
* @return Default method.
|
||||
*/
|
||||
public PlayerInjectHooks getDefaultMethod() {
|
||||
return PlayerInjectHooks.NETWORK_SERVER_OBJECT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the injection method that has been set in the configuration, or use a default value.
|
||||
*
|
||||
* @return Injection method to use.
|
||||
* @throws IllegalArgumentException If the configuration option is malformed.
|
||||
*/
|
||||
public PlayerInjectHooks getInjectionMethod() throws IllegalArgumentException {
|
||||
String text = global.getString(INJECTION_METHOD);
|
||||
|
||||
// Default hook if nothing has been set
|
||||
PlayerInjectHooks hook = getDefaultMethod();
|
||||
|
||||
if (text != null)
|
||||
hook = PlayerInjectHooks.valueOf(text.toUpperCase().replace(" ", "_"));
|
||||
return hook;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the starting injection method to use.
|
||||
*
|
||||
* @return Injection method.
|
||||
*/
|
||||
public void setInjectionMethod(PlayerInjectHooks hook) {
|
||||
setConfig(global, INJECTION_METHOD, hook.name());
|
||||
modCount++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the number of modifications made to this configuration.
|
||||
*
|
||||
* @return The number of modifications.
|
||||
*/
|
||||
public int getModificationCount() {
|
||||
return modCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the current configuration file.
|
||||
*/
|
||||
public void saveAll() {
|
||||
if (valuesChanged)
|
||||
saveLastUpdate(lastUpdateTime);
|
||||
if (configChanged)
|
||||
plugin.saveConfig();
|
||||
|
||||
// And we're done
|
||||
valuesChanged = false;
|
||||
configChanged = false;
|
||||
}
|
||||
}
|
||||
@@ -1,761 +0,0 @@
|
||||
/**
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
package com.comphenix.protocol;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Handler;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.LogRecord;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.PluginCommand;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.bukkit.plugin.PluginManager;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import com.comphenix.executors.BukkitExecutors;
|
||||
import com.comphenix.protocol.async.AsyncFilterManager;
|
||||
import com.comphenix.protocol.error.BasicErrorReporter;
|
||||
import com.comphenix.protocol.error.DelegatedErrorReporter;
|
||||
import com.comphenix.protocol.error.DetailedErrorReporter;
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.error.Report;
|
||||
import com.comphenix.protocol.error.ReportType;
|
||||
import com.comphenix.protocol.injector.DelayedSingleTask;
|
||||
import com.comphenix.protocol.injector.InternalManager;
|
||||
import com.comphenix.protocol.injector.PacketFilterManager;
|
||||
import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks;
|
||||
import com.comphenix.protocol.metrics.Statistics;
|
||||
import com.comphenix.protocol.reflect.compiler.BackgroundCompiler;
|
||||
import com.comphenix.protocol.updater.Updater;
|
||||
import com.comphenix.protocol.updater.Updater.UpdateType;
|
||||
import com.comphenix.protocol.utility.ChatExtensions;
|
||||
import com.comphenix.protocol.utility.EnhancerFactory;
|
||||
import com.comphenix.protocol.utility.MinecraftVersion;
|
||||
import com.google.common.base.Splitter;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.common.util.concurrent.ListeningScheduledExecutorService;
|
||||
|
||||
/**
|
||||
* The main entry point for ProtocolLib.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public class ProtocolLibrary extends JavaPlugin {
|
||||
// Every possible error or warning report type
|
||||
public static final ReportType REPORT_CANNOT_LOAD_CONFIG = new ReportType("Cannot load configuration");
|
||||
public static final ReportType REPORT_CANNOT_DELETE_CONFIG = new ReportType("Cannot delete old ProtocolLib configuration.");
|
||||
public static final ReportType REPORT_CANNOT_PARSE_INJECTION_METHOD = new ReportType("Cannot parse injection method. Using default.");
|
||||
|
||||
public static final ReportType REPORT_PLUGIN_LOAD_ERROR = new ReportType("Cannot load ProtocolLib.");
|
||||
public static final ReportType REPORT_PLUGIN_ENABLE_ERROR = new ReportType("Cannot enable ProtocolLib.");
|
||||
|
||||
public static final ReportType REPORT_METRICS_IO_ERROR = new ReportType("Unable to enable metrics due to network problems.");
|
||||
public static final ReportType REPORT_METRICS_GENERIC_ERROR = new ReportType("Unable to enable metrics due to network problems.");
|
||||
|
||||
public static final ReportType REPORT_CANNOT_PARSE_MINECRAFT_VERSION = new ReportType("Unable to retrieve current Minecraft version. Assuming %s");
|
||||
public static final ReportType REPORT_CANNOT_DETECT_CONFLICTING_PLUGINS = new ReportType("Unable to detect conflicting plugin versions.");
|
||||
public static final ReportType REPORT_CANNOT_REGISTER_COMMAND = new ReportType("Cannot register command %s: %s");
|
||||
|
||||
public static final ReportType REPORT_CANNOT_CREATE_TIMEOUT_TASK = new ReportType("Unable to create packet timeout task.");
|
||||
public static final ReportType REPORT_CANNOT_UPDATE_PLUGIN = new ReportType("Cannot perform automatic updates.");
|
||||
|
||||
/**
|
||||
* The minimum version ProtocolLib has been tested with.
|
||||
*/
|
||||
public static final String MINIMUM_MINECRAFT_VERSION = "1.0";
|
||||
|
||||
/**
|
||||
* The maximum version ProtocolLib has been tested with,
|
||||
*/
|
||||
public static final String MAXIMUM_MINECRAFT_VERSION = "1.8.8";
|
||||
|
||||
/**
|
||||
* The date (with ISO 8601 or YYYY-MM-DD) when the most recent version (1.8.8) was released.
|
||||
*/
|
||||
public static final String MINECRAFT_LAST_RELEASE_DATE = "2015-07-27";
|
||||
|
||||
// Update information
|
||||
static final String BUKKIT_DEV_SLUG = "protocollib";
|
||||
static final int BUKKIT_DEV_ID = 45564;
|
||||
|
||||
// Different commands
|
||||
private enum ProtocolCommand {
|
||||
FILTER,
|
||||
PACKET,
|
||||
PROTOCOL
|
||||
}
|
||||
|
||||
/**
|
||||
* The number of milliseconds per second.
|
||||
*/
|
||||
static final long MILLI_PER_SECOND = 1000;
|
||||
|
||||
private static final String PERMISSION_INFO = "protocol.info";
|
||||
|
||||
// There should only be one protocol manager, so we'll make it static
|
||||
private static InternalManager protocolManager;
|
||||
|
||||
// Error reporter
|
||||
private static ErrorReporter reporter = new BasicErrorReporter();
|
||||
|
||||
// Strongly typed configuration
|
||||
private static ProtocolConfig config;
|
||||
|
||||
// Metrics and statistics
|
||||
private Statistics statistics;
|
||||
|
||||
// Executors
|
||||
private static ListeningScheduledExecutorService executorAsync;
|
||||
private static ListeningScheduledExecutorService executorSync;
|
||||
|
||||
// Structure compiler
|
||||
private BackgroundCompiler backgroundCompiler;
|
||||
|
||||
// Used to clean up server packets that have expired, but mostly required to simulate
|
||||
// recieving client packets.
|
||||
private int packetTask = -1;
|
||||
private int tickCounter = 0;
|
||||
private static final int ASYNC_MANAGER_DELAY = 1;
|
||||
|
||||
// Used to unhook players after a delay
|
||||
private DelayedSingleTask unhookTask;
|
||||
|
||||
// Settings/options
|
||||
private int configExpectedMod = -1;
|
||||
|
||||
// Updater
|
||||
private Updater updater;
|
||||
private static boolean UPDATES_DISABLED;
|
||||
|
||||
// Logger
|
||||
private static Logger logger;
|
||||
private Handler redirectHandler;
|
||||
|
||||
// Commands
|
||||
private CommandProtocol commandProtocol;
|
||||
private CommandPacket commandPacket;
|
||||
private CommandFilter commandFilter;
|
||||
|
||||
// Whether or not disable is not needed
|
||||
private boolean skipDisable;
|
||||
|
||||
@Override
|
||||
public void onLoad() {
|
||||
// Load configuration
|
||||
logger = getLoggerSafely();
|
||||
Application.registerPrimaryThread();
|
||||
|
||||
// Initialize enhancer factory
|
||||
EnhancerFactory.getInstance().setClassLoader(getClassLoader());
|
||||
|
||||
// Initialize executors
|
||||
executorAsync = BukkitExecutors.newAsynchronous(this);
|
||||
executorSync = BukkitExecutors.newSynchronous(this);
|
||||
|
||||
// Add global parameters
|
||||
DetailedErrorReporter detailedReporter = new DetailedErrorReporter(this);
|
||||
reporter = getFilteredReporter(detailedReporter);
|
||||
|
||||
// Configuration
|
||||
saveDefaultConfig();
|
||||
reloadConfig();
|
||||
|
||||
try {
|
||||
config = new ProtocolConfig(this);
|
||||
} catch (Exception e) {
|
||||
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_LOAD_CONFIG).error(e));
|
||||
|
||||
// Load it again
|
||||
if (deleteConfig()) {
|
||||
config = new ProtocolConfig(this);
|
||||
} else {
|
||||
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_DELETE_CONFIG));
|
||||
}
|
||||
}
|
||||
|
||||
// Print the state of the debug mode
|
||||
if (config.isDebug()) {
|
||||
logger.warning("Debug mode is enabled!");
|
||||
}
|
||||
// And the state of the error reporter
|
||||
if (config.isDetailedErrorReporting()) {
|
||||
detailedReporter.setDetailedReporting(true);
|
||||
logger.warning("Detailed error reporting enabled!");
|
||||
}
|
||||
|
||||
try {
|
||||
// Check for other versions
|
||||
checkConflictingVersions();
|
||||
|
||||
// Handle unexpected Minecraft versions
|
||||
MinecraftVersion version = verifyMinecraftVersion();
|
||||
|
||||
// Set updater - this will not perform any update automatically
|
||||
updater = Updater.create(this, BUKKIT_DEV_ID, getFile(), UpdateType.NO_DOWNLOAD, true);
|
||||
|
||||
unhookTask = new DelayedSingleTask(this);
|
||||
protocolManager = PacketFilterManager.newBuilder()
|
||||
.classLoader(getClassLoader())
|
||||
.server(getServer())
|
||||
.library(this)
|
||||
.minecraftVersion(version)
|
||||
.unhookTask(unhookTask)
|
||||
.reporter(reporter)
|
||||
.build();
|
||||
|
||||
// Setup error reporter
|
||||
detailedReporter.addGlobalParameter("manager", protocolManager);
|
||||
|
||||
// Update injection hook
|
||||
try {
|
||||
PlayerInjectHooks hook = config.getInjectionMethod();
|
||||
|
||||
// Only update the hook if it's different
|
||||
if (!protocolManager.getPlayerHook().equals(hook)) {
|
||||
logger.info("Changing player hook from " + protocolManager.getPlayerHook() + " to " + hook);
|
||||
protocolManager.setPlayerHook(hook);
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
reporter.reportWarning(config, Report.newBuilder(REPORT_CANNOT_PARSE_INJECTION_METHOD).error(e));
|
||||
}
|
||||
|
||||
// Send logging information to player listeners too
|
||||
initializeCommands();
|
||||
setupBroadcastUsers(PERMISSION_INFO);
|
||||
|
||||
} catch (OutOfMemoryError e) {
|
||||
throw e;
|
||||
} catch (ThreadDeath e) {
|
||||
throw e;
|
||||
} catch (Throwable e) {
|
||||
reporter.reportDetailed(this, Report.newBuilder(REPORT_PLUGIN_LOAD_ERROR).error(e).callerParam(protocolManager));
|
||||
disablePlugin();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize all command handlers.
|
||||
*/
|
||||
private void initializeCommands() {
|
||||
// Initialize command handlers
|
||||
for (ProtocolCommand command : ProtocolCommand.values()) {
|
||||
try {
|
||||
switch (command) {
|
||||
case PROTOCOL:
|
||||
commandProtocol = new CommandProtocol(reporter, this, updater, config);
|
||||
break;
|
||||
case FILTER:
|
||||
commandFilter = new CommandFilter(reporter, this, config);
|
||||
break;
|
||||
case PACKET:
|
||||
commandPacket = new CommandPacket(reporter, this, logger, commandFilter, protocolManager);
|
||||
break;
|
||||
}
|
||||
} catch (OutOfMemoryError e) {
|
||||
throw e;
|
||||
} catch (ThreadDeath e) {
|
||||
throw e;
|
||||
} catch (LinkageError e) {
|
||||
logger.warning("Failed to register command " + command.name() + ": " + e);
|
||||
} catch (Throwable e) {
|
||||
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_REGISTER_COMMAND)
|
||||
.messageParam(command.name(), e.getMessage()).error(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a error reporter that may be filtered by the configuration.
|
||||
* @return The new default error reporter.
|
||||
*/
|
||||
private ErrorReporter getFilteredReporter(ErrorReporter reporter) {
|
||||
return new DelegatedErrorReporter(reporter) {
|
||||
private int lastModCount = -1;
|
||||
private Set<String> reports = Sets.newHashSet();
|
||||
|
||||
@Override
|
||||
protected Report filterReport(Object sender, Report report, boolean detailed) {
|
||||
try {
|
||||
String canonicalName = ReportType.getReportName(sender, report.getType());
|
||||
String reportName = Iterables.getLast(Splitter.on("#").split(canonicalName)).toUpperCase();
|
||||
|
||||
if (config != null && config.getModificationCount() != lastModCount) {
|
||||
// Update our cached set again
|
||||
reports = Sets.newHashSet(config.getSuppressedReports());
|
||||
lastModCount = config.getModificationCount();
|
||||
}
|
||||
|
||||
// Cancel reports either on the full canonical name, or just the report name
|
||||
if (reports.contains(canonicalName) || reports.contains(reportName))
|
||||
return null;
|
||||
|
||||
} catch (Exception e) {
|
||||
// Only report this with a minor message
|
||||
logger.warning("Error filtering reports: " + e.toString());
|
||||
}
|
||||
// Don't filter anything
|
||||
return report;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private boolean deleteConfig() {
|
||||
return config.getFile().delete();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reloadConfig() {
|
||||
super.reloadConfig();
|
||||
|
||||
// Reload configuration
|
||||
if (config != null) {
|
||||
config.reloadConfig();
|
||||
}
|
||||
}
|
||||
|
||||
private void setupBroadcastUsers(final String permission) {
|
||||
// Guard against multiple calls
|
||||
if (redirectHandler != null)
|
||||
return;
|
||||
|
||||
// Broadcast information to every user too
|
||||
redirectHandler = new Handler() {
|
||||
@Override
|
||||
public void publish(LogRecord record) {
|
||||
// Only display warnings and above
|
||||
if (record.getLevel().intValue() >= Level.WARNING.intValue()) {
|
||||
commandPacket.broadcastMessageSilently(record.getMessage(), permission);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() {
|
||||
// Not needed.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws SecurityException {
|
||||
// Do nothing.
|
||||
}
|
||||
};
|
||||
|
||||
logger.addHandler(redirectHandler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
try {
|
||||
Server server = getServer();
|
||||
PluginManager manager = server.getPluginManager();
|
||||
|
||||
// Don't do anything else!
|
||||
if (manager == null)
|
||||
return;
|
||||
|
||||
// Silly plugin reloaders!
|
||||
if (protocolManager == null) {
|
||||
Logger directLogging = Logger.getLogger("Minecraft");
|
||||
String[] message = new String[] {
|
||||
" ProtocolLib does not support plugin reloaders! ", " Please use the built-in reload command! "
|
||||
};
|
||||
|
||||
// Print as severe
|
||||
for (String line : ChatExtensions.toFlowerBox(message, "*", 3, 1)) {
|
||||
directLogging.severe(line);
|
||||
}
|
||||
|
||||
disablePlugin();
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for incompatible plugins
|
||||
checkForIncompatibility(manager);
|
||||
|
||||
// Initialize background compiler
|
||||
if (backgroundCompiler == null && config.isBackgroundCompilerEnabled()) {
|
||||
backgroundCompiler = new BackgroundCompiler(getClassLoader(), reporter);
|
||||
BackgroundCompiler.setInstance(backgroundCompiler);
|
||||
|
||||
logger.info("Started structure compiler thread.");
|
||||
} else {
|
||||
logger.info("Structure compiler thread has been disabled.");
|
||||
}
|
||||
|
||||
// Set up command handlers
|
||||
registerCommand(CommandProtocol.NAME, commandProtocol);
|
||||
registerCommand(CommandPacket.NAME, commandPacket);
|
||||
registerCommand(CommandFilter.NAME, commandFilter);
|
||||
|
||||
// Player login and logout events
|
||||
protocolManager.registerEvents(manager, this);
|
||||
|
||||
// Worker that ensures that async packets are eventually sent
|
||||
// It also performs the update check.
|
||||
createPacketTask(server);
|
||||
} catch (OutOfMemoryError e) {
|
||||
throw e;
|
||||
} catch (ThreadDeath e) {
|
||||
throw e;
|
||||
} catch (Throwable e) {
|
||||
reporter.reportDetailed(this, Report.newBuilder(REPORT_PLUGIN_ENABLE_ERROR).error(e));
|
||||
disablePlugin();
|
||||
return;
|
||||
}
|
||||
|
||||
// Try to enable statistics
|
||||
try {
|
||||
if (config.isMetricsEnabled()) {
|
||||
statistics = new Statistics(this);
|
||||
}
|
||||
} catch (OutOfMemoryError e) {
|
||||
throw e;
|
||||
} catch (ThreadDeath e) {
|
||||
throw e;
|
||||
} catch (IOException e) {
|
||||
reporter.reportDetailed(this, Report.newBuilder(REPORT_METRICS_IO_ERROR).error(e).callerParam(statistics));
|
||||
} catch (Throwable e) {
|
||||
reporter.reportDetailed(this, Report.newBuilder(REPORT_METRICS_GENERIC_ERROR).error(e).callerParam(statistics));
|
||||
}
|
||||
}
|
||||
|
||||
// Plugin authors: Notify me to remove these
|
||||
public static List<String> INCOMPATIBLE = Arrays.asList("TagAPI");
|
||||
|
||||
private void checkForIncompatibility(PluginManager manager) {
|
||||
for (String plugin : INCOMPATIBLE) {
|
||||
if (manager.getPlugin(plugin) != null) {
|
||||
// Special case for TagAPI and iTag
|
||||
if (plugin.equals("TagAPI")) {
|
||||
Plugin iTag = manager.getPlugin("iTag");
|
||||
if (iTag == null || iTag.getDescription().getVersion().startsWith("1.0")) {
|
||||
logger.severe("Detected incompatible plugin: TagAPI");
|
||||
}
|
||||
} else {
|
||||
logger.severe("Detected incompatible plugin: " + plugin);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Used to check Minecraft version
|
||||
private MinecraftVersion verifyMinecraftVersion() {
|
||||
MinecraftVersion minimum = new MinecraftVersion(MINIMUM_MINECRAFT_VERSION);
|
||||
MinecraftVersion maximum = new MinecraftVersion(MAXIMUM_MINECRAFT_VERSION);
|
||||
|
||||
try {
|
||||
MinecraftVersion current = new MinecraftVersion(getServer());
|
||||
|
||||
// Skip certain versions
|
||||
if (!config.getIgnoreVersionCheck().equals(current.getVersion())) {
|
||||
// We'll just warn the user for now
|
||||
if (current.compareTo(minimum) < 0)
|
||||
logger.warning("Version " + current + " is lower than the minimum " + minimum);
|
||||
if (current.compareTo(maximum) > 0)
|
||||
logger.warning("Version " + current + " has not yet been tested! Proceed with caution.");
|
||||
}
|
||||
|
||||
return current;
|
||||
} catch (Exception e) {
|
||||
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_PARSE_MINECRAFT_VERSION).error(e).messageParam(maximum));
|
||||
|
||||
// Unknown version - just assume it is the latest
|
||||
return maximum;
|
||||
}
|
||||
}
|
||||
|
||||
private void checkConflictingVersions() {
|
||||
Pattern ourPlugin = Pattern.compile("ProtocolLib-(.*)\\.jar");
|
||||
MinecraftVersion currentVersion = new MinecraftVersion(getDescription().getVersion());
|
||||
MinecraftVersion newestVersion = null;
|
||||
|
||||
// Skip the file that contains this current instance however
|
||||
File loadedFile = getFile();
|
||||
|
||||
try {
|
||||
// Scan the plugin folder for newer versions of ProtocolLib
|
||||
// The plugin folder isn't always plugins/
|
||||
File pluginFolder = getDataFolder().getParentFile();
|
||||
|
||||
File[] candidates = pluginFolder.listFiles();
|
||||
if (candidates != null) {
|
||||
for (File candidate : pluginFolder.listFiles()) {
|
||||
if (candidate.isFile() && !candidate.equals(loadedFile)) {
|
||||
Matcher match = ourPlugin.matcher(candidate.getName());
|
||||
if (match.matches()) {
|
||||
MinecraftVersion version = new MinecraftVersion(match.group(1));
|
||||
|
||||
if (candidate.length() == 0) {
|
||||
// Delete and inform the user
|
||||
logger.info((candidate.delete() ? "Deleted " : "Could not delete ") + candidate);
|
||||
} else if (newestVersion == null || newestVersion.compareTo(version) < 0) {
|
||||
newestVersion = version;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// TODO This shows [ProtocolLib] and [ProtocolLibrary] in the message
|
||||
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_DETECT_CONFLICTING_PLUGINS).error(e));
|
||||
}
|
||||
|
||||
// See if the newest version is actually higher
|
||||
if (newestVersion != null && currentVersion.compareTo(newestVersion) < 0) {
|
||||
// We don't need to set internal classes or instances to NULL - that would break the other loaded plugin
|
||||
skipDisable = true;
|
||||
|
||||
throw new IllegalStateException(String.format(
|
||||
"Detected a newer version of ProtocolLib (%s) in plugin folder than the current (%s). Disabling.",
|
||||
newestVersion.getVersion(), currentVersion.getVersion()));
|
||||
}
|
||||
}
|
||||
|
||||
private void registerCommand(String name, CommandExecutor executor) {
|
||||
try {
|
||||
// Ignore these - they must have printed an error already
|
||||
if (executor == null)
|
||||
return;
|
||||
|
||||
PluginCommand command = getCommand(name);
|
||||
|
||||
// Try to load the command
|
||||
if (command != null) {
|
||||
command.setExecutor(executor);
|
||||
} else {
|
||||
throw new RuntimeException("plugin.yml might be corrupt.");
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_REGISTER_COMMAND).messageParam(name, e.getMessage()).error(e));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable the current plugin.
|
||||
*/
|
||||
private void disablePlugin() {
|
||||
getServer().getPluginManager().disablePlugin(this);
|
||||
}
|
||||
|
||||
private void createPacketTask(Server server) {
|
||||
try {
|
||||
if (packetTask >= 0)
|
||||
throw new IllegalStateException("Packet task has already been created");
|
||||
|
||||
// Attempt to create task
|
||||
packetTask = server.getScheduler().scheduleSyncRepeatingTask(this, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
AsyncFilterManager manager = (AsyncFilterManager) protocolManager.getAsynchronousManager();
|
||||
|
||||
// We KNOW we're on the main thread at the moment
|
||||
manager.sendProcessedPackets(tickCounter++, true);
|
||||
|
||||
// House keeping
|
||||
updateConfiguration();
|
||||
|
||||
// Check for updates too
|
||||
if (!UPDATES_DISABLED && (tickCounter % 20) == 0) {
|
||||
checkUpdates();
|
||||
}
|
||||
}
|
||||
}, ASYNC_MANAGER_DELAY, ASYNC_MANAGER_DELAY);
|
||||
} catch (OutOfMemoryError e) {
|
||||
throw e;
|
||||
} catch (ThreadDeath e) {
|
||||
throw e;
|
||||
} catch (Throwable e) {
|
||||
if (packetTask == -1) {
|
||||
reporter.reportDetailed(this, Report.newBuilder(REPORT_CANNOT_CREATE_TIMEOUT_TASK).error(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateConfiguration() {
|
||||
if (config != null && config.getModificationCount() != configExpectedMod) {
|
||||
configExpectedMod = config.getModificationCount();
|
||||
|
||||
// Update the debug flag
|
||||
protocolManager.setDebug(config.isDebug());
|
||||
}
|
||||
}
|
||||
|
||||
private void checkUpdates() {
|
||||
// Ignore milliseconds - it's pointless
|
||||
long currentTime = System.currentTimeMillis() / MILLI_PER_SECOND;
|
||||
|
||||
try {
|
||||
long updateTime = config.getAutoLastTime() + config.getAutoDelay();
|
||||
|
||||
// Should we update?
|
||||
if (currentTime > updateTime && !updater.isChecking()) {
|
||||
// Initiate the update as if it came from the console
|
||||
if (config.isAutoDownload())
|
||||
commandProtocol.updateVersion(getServer().getConsoleSender(), false);
|
||||
else if (config.isAutoNotify())
|
||||
commandProtocol.checkVersion(getServer().getConsoleSender(), false);
|
||||
else
|
||||
commandProtocol.updateFinished();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
reporter.reportDetailed(this, Report.newBuilder(REPORT_CANNOT_UPDATE_PLUGIN).error(e));
|
||||
UPDATES_DISABLED = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
if (skipDisable) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Disable compiler
|
||||
if (backgroundCompiler != null) {
|
||||
backgroundCompiler.shutdownAll();
|
||||
backgroundCompiler = null;
|
||||
BackgroundCompiler.setInstance(null);
|
||||
}
|
||||
|
||||
// Clean up
|
||||
if (packetTask >= 0) {
|
||||
getServer().getScheduler().cancelTask(packetTask);
|
||||
packetTask = -1;
|
||||
}
|
||||
|
||||
// And redirect handler too
|
||||
if (redirectHandler != null) {
|
||||
logger.removeHandler(redirectHandler);
|
||||
}
|
||||
if (protocolManager != null)
|
||||
protocolManager.close();
|
||||
else
|
||||
return; // Plugin reloaders!
|
||||
|
||||
if (unhookTask != null)
|
||||
unhookTask.close();
|
||||
protocolManager = null;
|
||||
statistics = null;
|
||||
|
||||
// To clean up global parameters
|
||||
reporter = new BasicErrorReporter();
|
||||
}
|
||||
|
||||
// Get the Bukkit logger first, before we try to create our own
|
||||
private Logger getLoggerSafely() {
|
||||
Logger log = null;
|
||||
|
||||
try {
|
||||
log = getLogger();
|
||||
} catch (OutOfMemoryError e) {
|
||||
throw e;
|
||||
} catch (ThreadDeath e) {
|
||||
throw e;
|
||||
} catch (Throwable e) {
|
||||
// Ignore
|
||||
}
|
||||
|
||||
// Use the default logger instead
|
||||
if (log == null)
|
||||
log = Logger.getLogger("Minecraft");
|
||||
return log;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the current error reporter.
|
||||
* <p>
|
||||
* This is guaranteed to not be NULL in 2.5.0 and later.
|
||||
* @return Current error reporter.
|
||||
*/
|
||||
public static ErrorReporter getErrorReporter() {
|
||||
return reporter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the current strongly typed configuration.
|
||||
* @return The configuration, or NULL if ProtocolLib hasn't loaded yet.
|
||||
*/
|
||||
public static ProtocolConfig getConfiguration() {
|
||||
return config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the packet protocol manager.
|
||||
* @return Packet protocol manager, or NULL if it has been disabled.
|
||||
*/
|
||||
public static ProtocolManager getProtocolManager() {
|
||||
return protocolManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the metrics instance used to measure users of this library.
|
||||
* <p>
|
||||
* Note that this method may return NULL when the server is reloading or shutting down. It is also
|
||||
* NULL if metrics has been disabled.
|
||||
* @return Metrics instance container.
|
||||
*/
|
||||
public Statistics getStatistics() {
|
||||
return statistics;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve an executor service for performing asynchronous tasks on the behalf of ProtocolLib.
|
||||
* <p>
|
||||
* Note that this service is NULL if ProtocolLib has not been initialized yet.
|
||||
* @return The executor service, or NULL.
|
||||
*/
|
||||
public static ListeningScheduledExecutorService getExecutorAsync() {
|
||||
return executorAsync;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve an executor service for performing synchronous tasks (main thread) on the behalf of ProtocolLib.
|
||||
* <p>
|
||||
* Note that this service is NULL if ProtocolLib has not been initialized yet.
|
||||
* @return The executor service, or NULL.
|
||||
*/
|
||||
public static ListeningScheduledExecutorService getExecutorSync() {
|
||||
return executorSync;
|
||||
}
|
||||
|
||||
// ---- Logging Methods
|
||||
|
||||
public static void log(Level level, String message, Object... args) {
|
||||
logger.log(level, MessageFormat.format(message, args));
|
||||
}
|
||||
|
||||
public static void log(String message, Object... args) {
|
||||
log(Level.INFO, message, args);
|
||||
}
|
||||
|
||||
public static void log(Level level, String message, Throwable ex) {
|
||||
logger.log(level, message, ex);
|
||||
}
|
||||
|
||||
public void disableUpdates() {
|
||||
UPDATES_DISABLED = true;
|
||||
}
|
||||
}
|
||||
@@ -1,282 +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 java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import com.comphenix.protocol.async.AsyncMarker;
|
||||
import com.comphenix.protocol.events.ListenerPriority;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.events.PacketListener;
|
||||
import com.comphenix.protocol.injector.PacketConstructor;
|
||||
import com.comphenix.protocol.reflect.FieldAccessException;
|
||||
import com.comphenix.protocol.utility.MinecraftVersion;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
|
||||
/**
|
||||
* Represents an API for accessing the Minecraft protocol.
|
||||
* @author Kristian
|
||||
*/
|
||||
public interface ProtocolManager extends PacketStream {
|
||||
/**
|
||||
* Retrieve the protocol version of a given player.
|
||||
* <p>
|
||||
* This only really makes sense of a server that support clients of multiple Minecraft versions, such as Spigot #1628.
|
||||
* @param player - the player.
|
||||
* @return The associated protocol version, or {@link Integer#MIN_VALUE} if unknown.
|
||||
*/
|
||||
public int getProtocolVersion(Player player);
|
||||
|
||||
/**
|
||||
* Send a packet to the given player.
|
||||
* <p>
|
||||
* Re-sending a previously cancelled packet is discouraged. Use {@link AsyncMarker#incrementProcessingDelay()}
|
||||
* to delay a packet until a certain condition has been met.
|
||||
*
|
||||
* @param receiver - the receiver.
|
||||
* @param packet - packet to send.
|
||||
* @param filters - whether or not to invoke any packet filters below {@link ListenerPriority#MONITOR}.
|
||||
* @throws InvocationTargetException - if an error occurred when sending the packet.
|
||||
*/
|
||||
@Override
|
||||
public void sendServerPacket(Player receiver, PacketContainer packet, boolean filters)
|
||||
throws InvocationTargetException;
|
||||
|
||||
/**
|
||||
* Simulate receiving a certain packet from a given player.
|
||||
* <p>
|
||||
* Receiving a previously cancelled packet is discouraged. Use {@link AsyncMarker#incrementProcessingDelay()}
|
||||
* to delay a packet until a certain condition has been met.
|
||||
*
|
||||
* @param sender - the sender.
|
||||
* @param packet - the packet that was sent.
|
||||
* @param filters - whether or not to invoke any packet filters below {@link ListenerPriority#MONITOR}.
|
||||
* @throws InvocationTargetException If the reflection machinery failed.
|
||||
* @throws IllegalAccessException If the underlying method caused an error.
|
||||
*/
|
||||
@Override
|
||||
public void recieveClientPacket(Player sender, PacketContainer packet, boolean filters)
|
||||
throws IllegalAccessException, InvocationTargetException;
|
||||
|
||||
/**
|
||||
* Broadcast a given packet to every connected player on the server.
|
||||
* @param packet - the packet to broadcast.
|
||||
* @throws FieldAccessException If we were unable to send the packet due to reflection problems.
|
||||
*/
|
||||
public void broadcastServerPacket(PacketContainer packet);
|
||||
|
||||
/**
|
||||
* Broadcast a packet to every player that is receiving information about a given entity.
|
||||
* <p>
|
||||
* This is usually every player in the same world within an observable distance. If the entity is a
|
||||
* player, it will only be included if <i>includeTracker</i> is TRUE.
|
||||
* @param packet - the packet to broadcast.
|
||||
* @param entity - the entity whose trackers we will inform.
|
||||
* @param includeTracker - whether or not to also transmit the packet to the entity, if it is a tracker.
|
||||
* @throws FieldAccessException If we were unable to send the packet due to reflection problems.
|
||||
*/
|
||||
public void broadcastServerPacket(PacketContainer packet, Entity entity, boolean includeTracker);
|
||||
|
||||
/**
|
||||
* Broadcast a packet to every player within the given maximum observer distance.
|
||||
* @param packet - the packet to broadcast.
|
||||
* @param origin - the origin to consider when calculating the distance to each observer.
|
||||
* @param maxObserverDistance - the maximum distance to the origin.
|
||||
*/
|
||||
public void broadcastServerPacket(PacketContainer packet, Location origin, int maxObserverDistance);
|
||||
|
||||
/**
|
||||
* Retrieves a list of every registered packet listener.
|
||||
* @return Every registered packet listener.
|
||||
*/
|
||||
public ImmutableSet<PacketListener> getPacketListeners();
|
||||
|
||||
/**
|
||||
* Adds a packet listener.
|
||||
* <p>
|
||||
* Adding an already registered listener has no effect. If you need to change the packets
|
||||
* the current listener is observing, you must first remove the packet listener before you
|
||||
* can register it again.
|
||||
* @param listener - new packet listener.
|
||||
*/
|
||||
public void addPacketListener(PacketListener listener);
|
||||
|
||||
/**
|
||||
* Removes a given packet listener.
|
||||
* <p>
|
||||
* Attempting to remove a listener that doesn't exist has no effect.
|
||||
* @param listener - the packet listener to remove.
|
||||
*/
|
||||
public void removePacketListener(PacketListener listener);
|
||||
|
||||
/**
|
||||
* Removes every listener associated with the given plugin.
|
||||
* @param plugin - the plugin to unload.
|
||||
*/
|
||||
public void removePacketListeners(Plugin plugin);
|
||||
|
||||
/**
|
||||
* Constructs a new encapsulated Minecraft packet with the given ID.
|
||||
* <p>
|
||||
* Deprecated: Use {@link #createPacket(PacketType)} instead.
|
||||
* @param id - packet ID.
|
||||
* @return New encapsulated Minecraft packet.
|
||||
*/
|
||||
@Deprecated
|
||||
public PacketContainer createPacket(int id);
|
||||
|
||||
/**
|
||||
* Constructs a new encapsulated Minecraft packet with the given ID.
|
||||
* @param type - packet type.
|
||||
* @return New encapsulated Minecraft packet.
|
||||
*/
|
||||
public PacketContainer createPacket(PacketType type);
|
||||
|
||||
/**
|
||||
* Constructs a new encapsulated Minecraft packet with the given ID.
|
||||
* <p>
|
||||
* If set to true, the <i>forceDefaults</i> option will force the system to automatically
|
||||
* give non-primitive fields in the packet sensible default values. For instance, certain
|
||||
* packets - like Packet60Explosion - require a List or Set to be non-null. If the
|
||||
* forceDefaults option is true, the List or Set will be automatically created.
|
||||
* <p>
|
||||
* Deprecated: Use {@link #createPacket(PacketType, boolean)} instead.
|
||||
*
|
||||
* @param id - packet ID.
|
||||
* @param forceDefaults - TRUE to use sensible defaults in most fields, FALSE otherwise.
|
||||
* @return New encapsulated Minecraft packet.
|
||||
*/
|
||||
@Deprecated
|
||||
public PacketContainer createPacket(int id, boolean forceDefaults);
|
||||
|
||||
/**
|
||||
* Constructs a new encapsulated Minecraft packet with the given ID.
|
||||
* <p>
|
||||
* If set to true, the <i>forceDefaults</i> option will force the system to automatically
|
||||
* give non-primitive fields in the packet sensible default values. For instance, certain
|
||||
* packets - like Packet60Explosion - require a List or Set to be non-null. If the
|
||||
* forceDefaults option is true, the List or Set will be automatically created.
|
||||
*
|
||||
* @param type - packet type.
|
||||
* @param forceDefaults - TRUE to use sensible defaults in most fields, FALSE otherwise.
|
||||
* @return New encapsulated Minecraft packet.
|
||||
*/
|
||||
public PacketContainer createPacket(PacketType type, boolean forceDefaults);
|
||||
|
||||
/**
|
||||
* Construct a packet using the special builtin Minecraft constructors.
|
||||
* <p>
|
||||
* Deprecated: Use {@link #createPacketConstructor(PacketType, Object...)} instead.
|
||||
* @param id - the packet ID.
|
||||
* @param arguments - arguments that will be passed to the constructor.
|
||||
* @return The packet constructor.
|
||||
*/
|
||||
@Deprecated
|
||||
public PacketConstructor createPacketConstructor(int id, Object... arguments);
|
||||
|
||||
/**
|
||||
* Construct a packet using the special builtin Minecraft constructors.
|
||||
* @param type - the packet type.
|
||||
* @param arguments - arguments that will be passed to the constructor.
|
||||
* @return The packet constructor.
|
||||
*/
|
||||
public PacketConstructor createPacketConstructor(PacketType type, Object... arguments);
|
||||
|
||||
/**
|
||||
* Completely resend an entity to a list of clients.
|
||||
* <p>
|
||||
* Note that this method is NOT thread safe. If you call this method from anything
|
||||
* but the main thread, it will throw an exception.
|
||||
* @param entity - entity to refresh.
|
||||
* @param observers - the clients to update.
|
||||
*/
|
||||
public void updateEntity(Entity entity, List<Player> observers) throws FieldAccessException;
|
||||
|
||||
/**
|
||||
* Retrieve the associated entity.
|
||||
* @param container - the world the entity belongs to.
|
||||
* @param id - the unique ID of the entity.
|
||||
* @return The associated entity.
|
||||
* @throws FieldAccessException Reflection failed.
|
||||
*/
|
||||
public Entity getEntityFromID(World container, int id) throws FieldAccessException;
|
||||
|
||||
/**
|
||||
* Retrieve every client that is receiving information about a given entity.
|
||||
* @param entity - the entity that is being tracked.
|
||||
* @return Every client/player that is tracking the given entity.
|
||||
* @throws FieldAccessException If reflection failed.
|
||||
*/
|
||||
public List<Player> getEntityTrackers(Entity entity) throws FieldAccessException;
|
||||
|
||||
/**
|
||||
* Retrieves a immutable set containing the ID of the sent server packets that will be observed by listeners.
|
||||
* <p>
|
||||
* Deprecated: Use {@link #getSendingFilterTypes()} instead.
|
||||
* @return Every filtered server packet.
|
||||
*/
|
||||
@Deprecated
|
||||
public Set<Integer> getSendingFilters();
|
||||
|
||||
/**
|
||||
* Retrieves a immutable set containing the type of the sent server packets that will be observed by listeners.
|
||||
* @return Every filtered server packet.
|
||||
*/
|
||||
public Set<PacketType> getSendingFilterTypes();
|
||||
|
||||
/**
|
||||
* Retrieves a immutable set containing the ID of the received client packets that will be observed by listeners.
|
||||
* <p>
|
||||
* Deprecated: Use {@link #getReceivingFilterTypes()} instead.
|
||||
* @return Every filtered client packet.
|
||||
*/
|
||||
@Deprecated
|
||||
public Set<Integer> getReceivingFilters();
|
||||
|
||||
/**
|
||||
* Retrieves a immutable set containing the type of the received client packets that will be observed by listeners.
|
||||
* @return Every filtered client packet.
|
||||
*/
|
||||
public Set<PacketType> getReceivingFilterTypes();
|
||||
|
||||
/**
|
||||
* Retrieve the current Minecraft version.
|
||||
* @return The current version.
|
||||
*/
|
||||
public MinecraftVersion getMinecraftVersion();
|
||||
|
||||
/**
|
||||
* Determines whether or not this protocol manager has been disabled.
|
||||
* @return TRUE if it has, FALSE otherwise.
|
||||
*/
|
||||
public boolean isClosed();
|
||||
|
||||
/**
|
||||
* Retrieve the current asynchronous packet manager.
|
||||
* @return Asynchronous packet manager.
|
||||
*/
|
||||
public AsynchronousManager getAsynchronousManager();
|
||||
}
|
||||
@@ -1,163 +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.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Deque;
|
||||
import java.util.List;
|
||||
|
||||
import com.comphenix.protocol.compat.guava.Guava;
|
||||
import com.google.common.collect.Range;
|
||||
|
||||
/**
|
||||
* Used to parse ranges in CommandPacket.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
class RangeParser {
|
||||
/**
|
||||
* Parse a range from a given text.
|
||||
* @param text - the text.
|
||||
* @param legalRange - range of legal values.
|
||||
* @return The parsed ranges.
|
||||
*/
|
||||
public static List<Range<Integer>> getRanges(String text, Range<Integer> legalRange) {
|
||||
return getRanges(new ArrayDeque<String>(Arrays.asList(text)), legalRange);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse ranges from an array of elements.
|
||||
* @param args - array of elements.
|
||||
* @param offset - beginning offset.
|
||||
* @param lastIndex - the last index of the array to read.
|
||||
* @param legalRange - range of legal values.
|
||||
* @return The parsed ranges.
|
||||
*/
|
||||
public static List<Range<Integer>> getRanges(Deque<String> input, Range<Integer> legalRange) {
|
||||
List<String> tokens = tokenizeInput(input);
|
||||
List<Range<Integer>> ranges = new ArrayList<Range<Integer>>();
|
||||
|
||||
for (int i = 0; i < tokens.size(); i++) {
|
||||
Range<Integer> range;
|
||||
String current = tokens.get(i);
|
||||
String next = i + 1 < tokens.size() ? tokens.get(i + 1) : null;
|
||||
|
||||
// Yoda equality is done for null-safety
|
||||
if ("-".equals(current)) {
|
||||
throw new IllegalArgumentException("A hyphen must appear between two numbers.");
|
||||
} else if ("-".equals(next)) {
|
||||
if (i + 2 >= tokens.size())
|
||||
throw new IllegalArgumentException("Cannot form a range without a upper limit.");
|
||||
|
||||
// This is a proper range
|
||||
range = Guava.closedRange(Integer.parseInt(current), Integer.parseInt(tokens.get(i + 2)));
|
||||
ranges.add(range);
|
||||
|
||||
// Skip the two next tokens
|
||||
i += 2;
|
||||
|
||||
} else {
|
||||
// Just a single number
|
||||
range = Guava.singleton(Integer.parseInt(current));
|
||||
ranges.add(range);
|
||||
}
|
||||
|
||||
// Validate ranges
|
||||
if (!legalRange.encloses(range)) {
|
||||
throw new IllegalArgumentException(range + " is not in the range " + range.toString());
|
||||
}
|
||||
}
|
||||
|
||||
return simplify(ranges, legalRange.upperEndpoint());
|
||||
}
|
||||
|
||||
/**
|
||||
* Simplify a list of ranges by assuming a maximum value.
|
||||
* @param ranges - the list of ranges to simplify.
|
||||
* @param maximum - the maximum value (minimum value is always 0).
|
||||
* @return A simplified list of ranges.
|
||||
*/
|
||||
private static List<Range<Integer>> simplify(List<Range<Integer>> ranges, int maximum) {
|
||||
List<Range<Integer>> result = new ArrayList<Range<Integer>>();
|
||||
boolean[] set = new boolean[maximum + 1];
|
||||
int start = -1;
|
||||
|
||||
// Set every ID
|
||||
for (Range<Integer> range : ranges) {
|
||||
for (int id : Guava.toSet(range)) {
|
||||
set[id] = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Generate ranges from this set
|
||||
for (int i = 0; i <= set.length; i++) {
|
||||
if (i < set.length && set[i]) {
|
||||
if (start < 0) {
|
||||
start = i;
|
||||
}
|
||||
} else {
|
||||
if (start >= 0) {
|
||||
result.add(Guava.closedRange(start, i - 1));
|
||||
start = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static List<String> tokenizeInput(Deque<String> input) {
|
||||
List<String> tokens = new ArrayList<String>();
|
||||
|
||||
// Tokenize the input
|
||||
while (!input.isEmpty()) {
|
||||
StringBuilder number = new StringBuilder();
|
||||
String text = input.peek();
|
||||
|
||||
for (int j = 0; j < text.length(); j++) {
|
||||
char current = text.charAt(j);
|
||||
|
||||
if (Character.isDigit(current)) {
|
||||
number.append(current);
|
||||
} else if (Character.isWhitespace(current)) {
|
||||
// That's ok
|
||||
} else if (current == '-') {
|
||||
// Add the number token first
|
||||
if (number.length() > 0) {
|
||||
tokens.add(number.toString());
|
||||
number.setLength(0);
|
||||
}
|
||||
|
||||
tokens.add(Character.toString(current));
|
||||
} else {
|
||||
// We're no longer dealing with integers - quit
|
||||
return tokens;
|
||||
}
|
||||
}
|
||||
|
||||
// Add the number token, if it hasn't already
|
||||
if (number.length() > 0)
|
||||
tokens.add(number.toString());
|
||||
input.poll();
|
||||
}
|
||||
|
||||
return tokens;
|
||||
}
|
||||
}
|
||||
@@ -1,494 +0,0 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol.async;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.bukkit.scheduler.BukkitScheduler;
|
||||
|
||||
import com.comphenix.protocol.AsynchronousManager;
|
||||
import com.comphenix.protocol.PacketStream;
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.ProtocolManager;
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.events.ListeningWhitelist;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.events.PacketListener;
|
||||
import com.comphenix.protocol.injector.PacketFilterManager;
|
||||
import com.comphenix.protocol.injector.PrioritizedListener;
|
||||
import com.comphenix.protocol.injector.SortedPacketListenerList;
|
||||
import com.comphenix.protocol.injector.packet.PacketRegistry;
|
||||
import com.google.common.base.Objects;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Sets;
|
||||
|
||||
/**
|
||||
* Represents a filter manager for asynchronous packets.
|
||||
* <p>
|
||||
* By using {@link AsyncMarker#incrementProcessingDelay()}, a packet can be delayed without having to block the
|
||||
* processing thread.
|
||||
* @author Kristian
|
||||
*/
|
||||
public class AsyncFilterManager implements AsynchronousManager {
|
||||
|
||||
private SortedPacketListenerList serverTimeoutListeners;
|
||||
private SortedPacketListenerList clientTimeoutListeners;
|
||||
private Set<PacketListener> timeoutListeners;
|
||||
|
||||
private PacketProcessingQueue serverProcessingQueue;
|
||||
private PacketProcessingQueue clientProcessingQueue;
|
||||
|
||||
// Sending queues
|
||||
private final PlayerSendingHandler playerSendingHandler;
|
||||
|
||||
// Report exceptions
|
||||
private final ErrorReporter reporter;
|
||||
|
||||
// The likely main thread
|
||||
private final Thread mainThread;
|
||||
|
||||
// Default scheduler
|
||||
private final BukkitScheduler scheduler;
|
||||
|
||||
// Current packet index
|
||||
private final AtomicInteger currentSendingIndex = new AtomicInteger();
|
||||
|
||||
// Our protocol manager
|
||||
private ProtocolManager manager;
|
||||
|
||||
/**
|
||||
* Initialize a asynchronous filter manager.
|
||||
* <p>
|
||||
* <b>Internal method</b>. Retrieve the global asynchronous manager from the protocol manager instead.
|
||||
* @param reporter - desired error reporter.
|
||||
* @param scheduler - task scheduler.
|
||||
*/
|
||||
public AsyncFilterManager(ErrorReporter reporter, BukkitScheduler scheduler) {
|
||||
// Initialize timeout listeners
|
||||
this.serverTimeoutListeners = new SortedPacketListenerList();
|
||||
this.clientTimeoutListeners = new SortedPacketListenerList();
|
||||
this.timeoutListeners = Sets.newSetFromMap(new ConcurrentHashMap<PacketListener, Boolean>());
|
||||
|
||||
this.playerSendingHandler = new PlayerSendingHandler(reporter, serverTimeoutListeners, clientTimeoutListeners);
|
||||
this.serverProcessingQueue = new PacketProcessingQueue(playerSendingHandler);
|
||||
this.clientProcessingQueue = new PacketProcessingQueue(playerSendingHandler);
|
||||
this.playerSendingHandler.initializeScheduler();
|
||||
|
||||
this.scheduler = scheduler;
|
||||
this.reporter = reporter;
|
||||
this.mainThread = Thread.currentThread();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the protocol manager.
|
||||
* @return The protocol manager.
|
||||
*/
|
||||
public ProtocolManager getManager() {
|
||||
return manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the associated protocol manager.
|
||||
* @param manager - the new manager.
|
||||
*/
|
||||
public void setManager(ProtocolManager manager) {
|
||||
this.manager = manager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AsyncListenerHandler registerAsyncHandler(PacketListener listener) {
|
||||
return registerAsyncHandler(listener, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerTimeoutHandler(PacketListener listener) {
|
||||
if (listener == null)
|
||||
throw new IllegalArgumentException("listener cannot be NULL.");
|
||||
if (!timeoutListeners.add(listener))
|
||||
return;
|
||||
|
||||
ListeningWhitelist sending = listener.getSendingWhitelist();
|
||||
ListeningWhitelist receiving = listener.getReceivingWhitelist();
|
||||
|
||||
if (!ListeningWhitelist.isEmpty(sending))
|
||||
serverTimeoutListeners.addListener(listener, sending);
|
||||
if (!ListeningWhitelist.isEmpty(receiving))
|
||||
serverTimeoutListeners.addListener(listener, receiving);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<PacketListener> getTimeoutHandlers() {
|
||||
return ImmutableSet.copyOf(timeoutListeners);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<PacketListener> getAsyncHandlers() {
|
||||
ImmutableSet.Builder<PacketListener> builder = ImmutableSet.builder();
|
||||
|
||||
// Add every asynchronous packet listener
|
||||
for (PrioritizedListener<AsyncListenerHandler> handler :
|
||||
Iterables.concat(serverProcessingQueue.values(), clientProcessingQueue.values())) {
|
||||
builder.add(handler.getListener().getAsyncListener());
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers an asynchronous packet handler.
|
||||
* <p>
|
||||
* Use {@link AsyncMarker#incrementProcessingDelay()} to delay a packet until its ready to be transmitted.
|
||||
* <p>
|
||||
* To start listening asynchronously, pass the getListenerLoop() runnable to a different thread.
|
||||
* <p>
|
||||
* Asynchronous events will only be executed if a synchronous listener with the same packets is registered.
|
||||
* If you already have a synchronous event, call this method with autoInject set to FALSE.
|
||||
*
|
||||
* @param listener - the packet listener that will receive these asynchronous events.
|
||||
* @param autoInject - whether or not to automatically create the corresponding synchronous listener,
|
||||
* @return An asynchronous handler.
|
||||
*/
|
||||
public AsyncListenerHandler registerAsyncHandler(PacketListener listener, boolean autoInject) {
|
||||
AsyncListenerHandler handler = new AsyncListenerHandler(mainThread, this, listener);
|
||||
|
||||
ListeningWhitelist sendingWhitelist = listener.getSendingWhitelist();
|
||||
ListeningWhitelist receivingWhitelist = listener.getReceivingWhitelist();
|
||||
|
||||
if (!hasValidWhitelist(sendingWhitelist) && !hasValidWhitelist(receivingWhitelist)) {
|
||||
throw new IllegalArgumentException("Listener has an empty sending and receiving whitelist.");
|
||||
}
|
||||
|
||||
// Add listener to either or both processing queue
|
||||
if (hasValidWhitelist(sendingWhitelist)) {
|
||||
PacketFilterManager.verifyWhitelist(listener, sendingWhitelist);
|
||||
serverProcessingQueue.addListener(handler, sendingWhitelist);
|
||||
}
|
||||
if (hasValidWhitelist(receivingWhitelist)) {
|
||||
PacketFilterManager.verifyWhitelist(listener, receivingWhitelist);
|
||||
clientProcessingQueue.addListener(handler, receivingWhitelist);
|
||||
}
|
||||
|
||||
// We need a synchronized listener to get the ball rolling
|
||||
if (autoInject) {
|
||||
handler.setNullPacketListener(new NullPacketListener(listener));
|
||||
manager.addPacketListener(handler.getNullPacketListener());
|
||||
}
|
||||
return handler;
|
||||
}
|
||||
|
||||
private boolean hasValidWhitelist(ListeningWhitelist whitelist) {
|
||||
return whitelist != null && whitelist.getTypes().size() > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterTimeoutHandler(PacketListener listener) {
|
||||
if (listener == null)
|
||||
throw new IllegalArgumentException("listener cannot be NULL.");
|
||||
|
||||
ListeningWhitelist sending = listener.getSendingWhitelist();
|
||||
ListeningWhitelist receiving = listener.getReceivingWhitelist();
|
||||
|
||||
// Do it in the opposite order
|
||||
if (serverTimeoutListeners.removeListener(listener, sending).size() > 0 ||
|
||||
clientTimeoutListeners.removeListener(listener, receiving).size() > 0) {
|
||||
timeoutListeners.remove(listener);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterAsyncHandler(PacketListener listener) {
|
||||
if (listener == null)
|
||||
throw new IllegalArgumentException("listener cannot be NULL.");
|
||||
|
||||
AsyncListenerHandler handler =
|
||||
findHandler(serverProcessingQueue, listener.getSendingWhitelist(), listener);
|
||||
|
||||
if (handler == null) {
|
||||
handler = findHandler(clientProcessingQueue, listener.getReceivingWhitelist(), listener);
|
||||
}
|
||||
unregisterAsyncHandler(handler);
|
||||
}
|
||||
|
||||
// Search for the first correct handler
|
||||
private AsyncListenerHandler findHandler(PacketProcessingQueue queue, ListeningWhitelist search, PacketListener target) {
|
||||
if (ListeningWhitelist.isEmpty(search))
|
||||
return null;
|
||||
|
||||
for (PacketType type : search.getTypes()) {
|
||||
for (PrioritizedListener<AsyncListenerHandler> element : queue.getListener(type)) {
|
||||
if (element.getListener().getAsyncListener() == target) {
|
||||
return element.getListener();
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterAsyncHandler(AsyncListenerHandler handler) {
|
||||
if (handler == null)
|
||||
throw new IllegalArgumentException("listenerToken cannot be NULL");
|
||||
|
||||
handler.cancel();
|
||||
}
|
||||
|
||||
// Called by AsyncListenerHandler
|
||||
void unregisterAsyncHandlerInternal(AsyncListenerHandler handler) {
|
||||
|
||||
PacketListener listener = handler.getAsyncListener();
|
||||
boolean synchronusOK = onMainThread();
|
||||
|
||||
// Unregister null packet listeners
|
||||
if (handler.getNullPacketListener() != null) {
|
||||
manager.removePacketListener(handler.getNullPacketListener());
|
||||
}
|
||||
|
||||
// Just remove it from the queue(s)
|
||||
if (hasValidWhitelist(listener.getSendingWhitelist())) {
|
||||
List<PacketType> removed = serverProcessingQueue.removeListener(handler, listener.getSendingWhitelist());
|
||||
|
||||
// We're already taking care of this, so don't do anything
|
||||
playerSendingHandler.sendServerPackets(removed, synchronusOK);
|
||||
}
|
||||
|
||||
if (hasValidWhitelist(listener.getReceivingWhitelist())) {
|
||||
List<PacketType> removed = clientProcessingQueue.removeListener(handler, listener.getReceivingWhitelist());
|
||||
playerSendingHandler.sendClientPackets(removed, synchronusOK);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if we're running on the main thread.
|
||||
* @return TRUE if we are, FALSE otherwise.
|
||||
*/
|
||||
private boolean onMainThread() {
|
||||
return Thread.currentThread().getId() == mainThread.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterAsyncHandlers(Plugin plugin) {
|
||||
unregisterAsyncHandlers(serverProcessingQueue, plugin);
|
||||
unregisterAsyncHandlers(clientProcessingQueue, plugin);
|
||||
}
|
||||
|
||||
private void unregisterAsyncHandlers(PacketProcessingQueue processingQueue, Plugin plugin) {
|
||||
|
||||
// Iterate through every packet listener
|
||||
for (PrioritizedListener<AsyncListenerHandler> listener : processingQueue.values()) {
|
||||
// Remove the listener
|
||||
if (Objects.equal(listener.getListener().getPlugin(), plugin)) {
|
||||
unregisterAsyncHandler(listener.getListener());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue a packet for asynchronous processing.
|
||||
*
|
||||
* @param syncPacket - synchronous packet event.
|
||||
* @param asyncMarker - the asynchronous marker to use.
|
||||
*/
|
||||
public synchronized void enqueueSyncPacket(PacketEvent syncPacket, AsyncMarker asyncMarker) {
|
||||
PacketEvent newEvent = PacketEvent.fromSynchronous(syncPacket, asyncMarker);
|
||||
|
||||
if (asyncMarker.isQueued() || asyncMarker.isTransmitted())
|
||||
throw new IllegalArgumentException("Cannot queue a packet that has already been queued.");
|
||||
|
||||
asyncMarker.setQueuedSendingIndex(asyncMarker.getNewSendingIndex());
|
||||
|
||||
// The player is only be null when they're logged out,
|
||||
// so this should be a pretty safe check
|
||||
Player player = newEvent.getPlayer();
|
||||
if (player != null) {
|
||||
// Start the process
|
||||
getSendingQueue(syncPacket).enqueue(newEvent);
|
||||
|
||||
// We know this is occuring on the main thread, so pass TRUE
|
||||
getProcessingQueue(syncPacket).enqueue(newEvent, true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Integer> getSendingFilters() {
|
||||
return PacketRegistry.toLegacy(serverProcessingQueue.keySet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<PacketType> getReceivingTypes() {
|
||||
return serverProcessingQueue.keySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Integer> getReceivingFilters() {
|
||||
return PacketRegistry.toLegacy(clientProcessingQueue.keySet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<PacketType> getSendingTypes() {
|
||||
return clientProcessingQueue.keySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the current task scheduler.
|
||||
* @return Current task scheduler.
|
||||
*/
|
||||
public BukkitScheduler getScheduler() {
|
||||
return scheduler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasAsynchronousListeners(PacketEvent packet) {
|
||||
Collection<?> list = getProcessingQueue(packet).getListener(packet.getPacketType());
|
||||
return list != null && list.size() > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a asynchronous marker with all the default values.
|
||||
* @return Asynchronous marker.
|
||||
*/
|
||||
public AsyncMarker createAsyncMarker() {
|
||||
return createAsyncMarker(AsyncMarker.DEFAULT_TIMEOUT_DELTA);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an async marker with the given sending priority delta and timeout delta.
|
||||
* @param timeoutDelta - how long (in ms) until the packet expire.
|
||||
* @return An async marker.
|
||||
*/
|
||||
public AsyncMarker createAsyncMarker(long timeoutDelta) {
|
||||
return createAsyncMarker(timeoutDelta, currentSendingIndex.incrementAndGet());
|
||||
}
|
||||
|
||||
// Helper method
|
||||
private AsyncMarker createAsyncMarker(long timeoutDelta, long sendingIndex) {
|
||||
return new AsyncMarker(manager, sendingIndex, System.currentTimeMillis(), timeoutDelta);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketStream getPacketStream() {
|
||||
return manager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ErrorReporter getErrorReporter() {
|
||||
return reporter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanupAll() {
|
||||
serverProcessingQueue.cleanupAll();
|
||||
playerSendingHandler.cleanupAll();
|
||||
timeoutListeners.clear();
|
||||
|
||||
serverTimeoutListeners = null;
|
||||
clientTimeoutListeners = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void signalPacketTransmission(PacketEvent packet) {
|
||||
signalPacketTransmission(packet, onMainThread());
|
||||
}
|
||||
|
||||
/**
|
||||
* Signal that a packet is ready to be transmitted.
|
||||
* @param packet - packet to signal.
|
||||
* @param onMainThread - whether or not this method was run by the main thread.
|
||||
*/
|
||||
private void signalPacketTransmission(PacketEvent packet, boolean onMainThread) {
|
||||
AsyncMarker marker = packet.getAsyncMarker();
|
||||
if (marker == null)
|
||||
throw new IllegalArgumentException(
|
||||
"A sync packet cannot be transmitted by the asynchronous manager.");
|
||||
if (!marker.isQueued())
|
||||
throw new IllegalArgumentException(
|
||||
"A packet must have been queued before it can be transmitted.");
|
||||
|
||||
// Only send if the packet is ready
|
||||
if (marker.decrementProcessingDelay() == 0) {
|
||||
PacketSendingQueue queue = getSendingQueue(packet, false);
|
||||
|
||||
// No need to create a new queue if the player has logged out
|
||||
if (queue != null)
|
||||
queue.signalPacketUpdate(packet, onMainThread);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the sending queue this packet belongs to.
|
||||
* @param packet - the packet.
|
||||
* @return The server or client sending queue the packet belongs to.
|
||||
*/
|
||||
public PacketSendingQueue getSendingQueue(PacketEvent packet) {
|
||||
return playerSendingHandler.getSendingQueue(packet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the sending queue this packet belongs to.
|
||||
* @param packet - the packet.
|
||||
* @param createNew - if TRUE, create a new queue if it hasn't already been created.
|
||||
* @return The server or client sending queue the packet belongs to.
|
||||
*/
|
||||
public PacketSendingQueue getSendingQueue(PacketEvent packet, boolean createNew) {
|
||||
return playerSendingHandler.getSendingQueue(packet, createNew);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the processing queue this packet belongs to.
|
||||
* @param packet - the packet.
|
||||
* @return The server or client sending processing the packet belongs to.
|
||||
*/
|
||||
public PacketProcessingQueue getProcessingQueue(PacketEvent packet) {
|
||||
return packet.isServerPacket() ? serverProcessingQueue : clientProcessingQueue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Signal that a packet has finished processing.
|
||||
* @param packet - packet to signal.
|
||||
*/
|
||||
public void signalFreeProcessingSlot(PacketEvent packet) {
|
||||
getProcessingQueue(packet).signalProcessingDone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send any due packets, or clean up packets that have expired.
|
||||
* @param tickCounter Tick counter
|
||||
* @param onMainThread Whether or not to execute on the main thread
|
||||
*/
|
||||
public void sendProcessedPackets(int tickCounter, boolean onMainThread) {
|
||||
// The server queue is unlikely to need checking that often
|
||||
if (tickCounter % 10 == 0) {
|
||||
playerSendingHandler.trySendServerPackets(onMainThread);
|
||||
}
|
||||
|
||||
playerSendingHandler.trySendClientPackets(onMainThread);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up after a given player has logged out.
|
||||
* @param player - the player that has just logged out.
|
||||
*/
|
||||
public void removePlayer(Player player) {
|
||||
playerSendingHandler.removePlayer(player);
|
||||
}
|
||||
}
|
||||
@@ -1,706 +0,0 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol.async;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import com.comphenix.protocol.ProtocolLibrary;
|
||||
import com.comphenix.protocol.error.Report;
|
||||
import com.comphenix.protocol.error.ReportType;
|
||||
import com.comphenix.protocol.events.ListeningWhitelist;
|
||||
import com.comphenix.protocol.events.PacketAdapter;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.events.PacketListener;
|
||||
import com.comphenix.protocol.timing.TimedListenerManager;
|
||||
import com.comphenix.protocol.timing.TimedListenerManager.ListenerType;
|
||||
import com.comphenix.protocol.timing.TimedTracker;
|
||||
import com.comphenix.protocol.utility.WrappedScheduler;
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.base.Joiner;
|
||||
|
||||
/**
|
||||
* Represents a handler for an asynchronous event.
|
||||
* <p>
|
||||
* Use {@link AsyncMarker#incrementProcessingDelay()} to delay a packet until a certain condition has been met.
|
||||
* @author Kristian
|
||||
*/
|
||||
public class AsyncListenerHandler {
|
||||
public static final ReportType REPORT_HANDLER_NOT_STARTED = new ReportType(
|
||||
"Plugin %s did not start the asynchronous handler %s by calling start() or syncStart().");
|
||||
|
||||
/**
|
||||
* Signal an end to packet processing.
|
||||
*/
|
||||
private static final PacketEvent INTERUPT_PACKET = new PacketEvent(new Object());
|
||||
|
||||
/**
|
||||
* Called when the threads have to wake up for something important.
|
||||
*/
|
||||
private static final PacketEvent WAKEUP_PACKET = new PacketEvent(new Object());
|
||||
|
||||
/**
|
||||
* The expected number of ticks per second.
|
||||
*/
|
||||
private static final int TICKS_PER_SECOND = 20;
|
||||
|
||||
// Unique worker ID
|
||||
private static final AtomicInteger nextID = new AtomicInteger();
|
||||
|
||||
// Default queue capacity
|
||||
private static final int DEFAULT_CAPACITY = 1024;
|
||||
|
||||
// Cancel the async handler
|
||||
private volatile boolean cancelled;
|
||||
|
||||
// Number of worker threads
|
||||
private final AtomicInteger started = new AtomicInteger();
|
||||
|
||||
// The packet listener
|
||||
private PacketListener listener;
|
||||
|
||||
// The filter manager
|
||||
private AsyncFilterManager filterManager;
|
||||
private NullPacketListener nullPacketListener;
|
||||
|
||||
// List of queued packets
|
||||
private ArrayBlockingQueue<PacketEvent> queuedPackets = new ArrayBlockingQueue<PacketEvent>(DEFAULT_CAPACITY);
|
||||
|
||||
// List of cancelled tasks
|
||||
private final Set<Integer> stoppedTasks = new HashSet<Integer>();
|
||||
private final Object stopLock = new Object();
|
||||
|
||||
// Processing task on the main thread
|
||||
private int syncTask = -1;
|
||||
|
||||
// Minecraft main thread
|
||||
private Thread mainThread;
|
||||
|
||||
// Warn plugins that the async listener handler must be started
|
||||
private int warningTask;
|
||||
|
||||
// Timing manager
|
||||
private TimedListenerManager timedManager = TimedListenerManager.getInstance();
|
||||
|
||||
/**
|
||||
* Construct a manager for an asynchronous packet handler.
|
||||
* @param mainThread - the main game thread.
|
||||
* @param filterManager - the parent filter manager.
|
||||
* @param listener - the current packet listener.
|
||||
*/
|
||||
AsyncListenerHandler(Thread mainThread, AsyncFilterManager filterManager, PacketListener listener) {
|
||||
if (filterManager == null)
|
||||
throw new IllegalArgumentException("filterManager cannot be NULL");
|
||||
if (listener == null)
|
||||
throw new IllegalArgumentException("listener cannot be NULL");
|
||||
|
||||
this.mainThread = mainThread;
|
||||
this.filterManager = filterManager;
|
||||
this.listener = listener;
|
||||
startWarningTask();
|
||||
}
|
||||
|
||||
private void startWarningTask() {
|
||||
warningTask = filterManager.getScheduler().scheduleSyncDelayedTask(getPlugin(), new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
ProtocolLibrary.getErrorReporter().reportWarning(AsyncListenerHandler.this, Report.
|
||||
newBuilder(REPORT_HANDLER_NOT_STARTED).
|
||||
messageParam(listener.getPlugin(), AsyncListenerHandler.this).
|
||||
build()
|
||||
);
|
||||
}
|
||||
}, 2 * TICKS_PER_SECOND);
|
||||
}
|
||||
|
||||
private void stopWarningTask() {
|
||||
int taskId = warningTask;
|
||||
|
||||
// Ensure we have a task to cancel
|
||||
if (warningTask >= 0) {
|
||||
filterManager.getScheduler().cancelTask(taskId);
|
||||
warningTask = -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether or not this asynchronous handler has been cancelled.
|
||||
* @return TRUE if it has been cancelled/stopped, FALSE otherwise.
|
||||
*/
|
||||
public boolean isCancelled() {
|
||||
return cancelled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the current asynchronous packet listener.
|
||||
* @return Current packet listener.
|
||||
*/
|
||||
public PacketListener getAsyncListener() {
|
||||
return listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the synchronized listener that has been automatically created.
|
||||
* @param nullPacketListener - automatically created listener.
|
||||
*/
|
||||
void setNullPacketListener(NullPacketListener nullPacketListener) {
|
||||
this.nullPacketListener = nullPacketListener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the synchronized listener that was automatically created.
|
||||
* @return Automatically created listener.
|
||||
*/
|
||||
PacketListener getNullPacketListener() {
|
||||
return nullPacketListener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the plugin associated with this async listener.
|
||||
* @return The plugin.
|
||||
*/
|
||||
public Plugin getPlugin() {
|
||||
return listener != null ? listener.getPlugin() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel the handler.
|
||||
*/
|
||||
public void cancel() {
|
||||
// Remove the listener as quickly as possible
|
||||
close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Queue a packet for processing.
|
||||
* @param packet - a packet for processing.
|
||||
* @throws IllegalStateException If the underlying packet queue is full.
|
||||
*/
|
||||
public void enqueuePacket(PacketEvent packet) {
|
||||
if (packet == null)
|
||||
throw new IllegalArgumentException("packet is NULL");
|
||||
|
||||
queuedPackets.add(packet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a worker that will initiate the listener loop. Note that using stop() to
|
||||
* close a specific worker is less efficient than stopping an arbitrary worker.
|
||||
* <p>
|
||||
* <b>Warning</b>: Never call the run() method in the main thread.
|
||||
* @return The listener loop
|
||||
*/
|
||||
public AsyncRunnable getListenerLoop() {
|
||||
return new AsyncRunnable() {
|
||||
|
||||
private final AtomicBoolean firstRun = new AtomicBoolean();
|
||||
private final AtomicBoolean finished = new AtomicBoolean();
|
||||
private final int id = nextID.incrementAndGet();
|
||||
|
||||
@Override
|
||||
public int getID() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
// Careful now
|
||||
if (firstRun.compareAndSet(false, true)) {
|
||||
listenerLoop(id);
|
||||
|
||||
synchronized (stopLock) {
|
||||
stoppedTasks.remove(id);
|
||||
stopLock.notifyAll();
|
||||
finished.set(true);
|
||||
}
|
||||
|
||||
} else {
|
||||
if (finished.get())
|
||||
throw new IllegalStateException(
|
||||
"This listener has already been run. Create a new instead.");
|
||||
else
|
||||
throw new IllegalStateException(
|
||||
"This listener loop has already been started. Create a new instead.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean stop() throws InterruptedException {
|
||||
synchronized (stopLock) {
|
||||
if (!isRunning())
|
||||
return false;
|
||||
|
||||
stoppedTasks.add(id);
|
||||
|
||||
// Wake up threads - we have a listener to stop
|
||||
for (int i = 0; i < getWorkers(); i++) {
|
||||
queuedPackets.offer(WAKEUP_PACKET);
|
||||
}
|
||||
|
||||
finished.set(true);
|
||||
waitForStops();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRunning() {
|
||||
return firstRun.get() && !finished.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFinished() {
|
||||
return finished.get();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a singler worker thread handling the asynchronous listener.
|
||||
*/
|
||||
public synchronized void start() {
|
||||
if (listener.getPlugin() == null)
|
||||
throw new IllegalArgumentException("Cannot start task without a valid plugin.");
|
||||
if (cancelled)
|
||||
throw new IllegalStateException("Cannot start a worker when the listener is closing.");
|
||||
|
||||
final AsyncRunnable listenerLoop = getListenerLoop();
|
||||
|
||||
stopWarningTask();
|
||||
scheduleAsync(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Thread thread = Thread.currentThread();
|
||||
|
||||
String previousName = thread.getName();
|
||||
String workerName = getFriendlyWorkerName(listenerLoop.getID());
|
||||
|
||||
// Add the friendly worker name
|
||||
thread.setName(workerName);
|
||||
listenerLoop.run();
|
||||
thread.setName(previousName);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a singler worker thread handling the asynchronous listener.
|
||||
* <p>
|
||||
* This method is intended to allow callers to customize the thread priority
|
||||
* before the worker loop is actually called. This is simpler than to
|
||||
* schedule the worker threads manually.
|
||||
* <pre><code>
|
||||
* listenerHandler.start(new Function<AsyncRunnable, Void>() {
|
||||
* @Override
|
||||
* public Void apply(@Nullable AsyncRunnable workerLoop) {
|
||||
* Thread thread = Thread.currentThread();
|
||||
* int prevPriority = thread.getPriority();
|
||||
*
|
||||
* thread.setPriority(Thread.MIN_PRIORITY);
|
||||
* workerLoop.run();
|
||||
* thread.setPriority(prevPriority);
|
||||
* return null;
|
||||
* }
|
||||
* });
|
||||
* }
|
||||
* </code></pre>
|
||||
* @param executor - a method that will execute the given listener loop.
|
||||
*/
|
||||
public synchronized void start(Function<AsyncRunnable, Void> executor) {
|
||||
if (listener.getPlugin() == null)
|
||||
throw new IllegalArgumentException("Cannot start task without a valid plugin.");
|
||||
if (cancelled)
|
||||
throw new IllegalStateException("Cannot start a worker when the listener is closing.");
|
||||
|
||||
final AsyncRunnable listenerLoop = getListenerLoop();
|
||||
final Function<AsyncRunnable, Void> delegateCopy = executor;
|
||||
|
||||
scheduleAsync(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
delegateCopy.apply(listenerLoop);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void scheduleAsync(Runnable runnable) {
|
||||
// Handle deprecation
|
||||
WrappedScheduler.runAsynchronouslyRepeat(listener.getPlugin(), filterManager.getScheduler(), runnable, 0L, -1L);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a friendly thread name using the following convention:
|
||||
* <p><code>
|
||||
* Protocol Worker {id} - {plugin} - [recv: {packets}, send: {packets}]
|
||||
* </code></p>
|
||||
* @param id - the worker ID.
|
||||
* @return A friendly thread name.
|
||||
*/
|
||||
public String getFriendlyWorkerName(int id) {
|
||||
return String.format("Protocol Worker #%s - %s - [recv: %s, send: %s]",
|
||||
id,
|
||||
PacketAdapter.getPluginName(listener),
|
||||
fromWhitelist(listener.getReceivingWhitelist()),
|
||||
fromWhitelist(listener.getSendingWhitelist())
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the given whitelist to a comma-separated list of packet IDs.
|
||||
* @param whitelist - the whitelist.
|
||||
* @return A comma separated list of packet IDs in the whitelist, or the emtpy string.
|
||||
*/
|
||||
private String fromWhitelist(ListeningWhitelist whitelist) {
|
||||
if (whitelist == null)
|
||||
return "";
|
||||
else
|
||||
return Joiner.on(", ").join(whitelist.getTypes());
|
||||
}
|
||||
|
||||
/**
|
||||
* Start processing packets on the main thread.
|
||||
* <p>
|
||||
* This is useful if you need to synchronize with the main thread in your packet listener, but
|
||||
* you're not performing any expensive processing.
|
||||
* <p>
|
||||
* <b>Note</b>: Use a asynchronous worker if the packet listener may use more than 0.5 ms
|
||||
* of processing time on a single packet. Do as much as possible on the worker thread, and schedule synchronous tasks
|
||||
* to use the Bukkit API instead.
|
||||
* @return TRUE if the synchronized processing was successfully started, FALSE if it's already running.
|
||||
* @throws IllegalStateException If we couldn't start the underlying task.
|
||||
*/
|
||||
public synchronized boolean syncStart() {
|
||||
return syncStart(500, TimeUnit.MICROSECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start processing packets on the main thread.
|
||||
* <p>
|
||||
* This is useful if you need to synchronize with the main thread in your packet listener, but
|
||||
* you're not performing any expensive processing.
|
||||
* <p>
|
||||
* The processing time parameter gives the upper bound for the amount of time spent processing pending packets.
|
||||
* It should be set to a fairly low number, such as 0.5 ms or 1% of a game tick - to reduce the impact
|
||||
* on the main thread. Never go beyond 50 milliseconds.
|
||||
* <p>
|
||||
* <b>Note</b>: Use a asynchronous worker if the packet listener may exceed the ideal processing time
|
||||
* on a single packet. Do as much as possible on the worker thread, and schedule synchronous tasks
|
||||
* to use the Bukkit API instead.
|
||||
*
|
||||
* @param time - the amount of processing time alloted per game tick (20 ticks per second).
|
||||
* @param unit - the unit of the processingTime argument.
|
||||
* @return TRUE if the synchronized processing was successfully started, FALSE if it's already running.
|
||||
* @throws IllegalStateException If we couldn't start the underlying task.
|
||||
*/
|
||||
public synchronized boolean syncStart(final long time, final TimeUnit unit) {
|
||||
if (time <= 0)
|
||||
throw new IllegalArgumentException("Time must be greater than zero.");
|
||||
if (unit == null)
|
||||
throw new IllegalArgumentException("TimeUnit cannot be NULL.");
|
||||
|
||||
final long tickDelay = 1;
|
||||
final int workerID = nextID.incrementAndGet();
|
||||
|
||||
if (syncTask < 0) {
|
||||
stopWarningTask();
|
||||
|
||||
syncTask = filterManager.getScheduler().scheduleSyncRepeatingTask(getPlugin(), new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
long stopTime = System.nanoTime() + unit.convert(time, TimeUnit.NANOSECONDS);
|
||||
|
||||
while (!cancelled) {
|
||||
PacketEvent packet = queuedPackets.poll();
|
||||
|
||||
if (packet == INTERUPT_PACKET || packet == WAKEUP_PACKET) {
|
||||
// Sorry, asynchronous threads!
|
||||
queuedPackets.add(packet);
|
||||
|
||||
// Try again next tick
|
||||
break;
|
||||
} else if (packet != null && packet.getAsyncMarker() != null) {
|
||||
processPacket(workerID, packet, "onSyncPacket()");
|
||||
} else {
|
||||
// No more packets left - wait a tick
|
||||
break;
|
||||
}
|
||||
|
||||
// Check time here, ensuring that we at least process one packet
|
||||
if (System.nanoTime() < stopTime)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}, tickDelay, tickDelay);
|
||||
|
||||
// This is very bad - force the caller to handle it
|
||||
if (syncTask < 0)
|
||||
throw new IllegalStateException("Cannot start synchronous task.");
|
||||
else
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop processing packets on the main thread.
|
||||
* @return TRUE if we stopped any processing tasks, FALSE if it has already been stopped.
|
||||
*/
|
||||
public synchronized boolean syncStop() {
|
||||
if (syncTask > 0) {
|
||||
filterManager.getScheduler().cancelTask(syncTask);
|
||||
|
||||
syncTask = -1;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start multiple worker threads for this listener.
|
||||
* @param count - number of worker threads to start.
|
||||
*/
|
||||
public synchronized void start(int count) {
|
||||
for (int i = 0; i < count; i++)
|
||||
start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop a worker thread.
|
||||
*/
|
||||
public synchronized void stop() {
|
||||
queuedPackets.add(INTERUPT_PACKET);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the given amount of worker threads.
|
||||
* @param count - number of threads to stop.
|
||||
*/
|
||||
public synchronized void stop(int count) {
|
||||
for (int i = 0; i < count; i++)
|
||||
stop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current number of workers.
|
||||
* <p>
|
||||
* This method can only be called with a count of zero when the listener is closing.
|
||||
* @param count - new number of workers.
|
||||
*/
|
||||
public synchronized void setWorkers(int count) {
|
||||
if (count < 0)
|
||||
throw new IllegalArgumentException("Number of workers cannot be less than zero.");
|
||||
if (count > DEFAULT_CAPACITY)
|
||||
throw new IllegalArgumentException("Cannot initiate more than " + DEFAULT_CAPACITY + " workers");
|
||||
if (cancelled && count > 0)
|
||||
throw new IllegalArgumentException("Cannot add workers when the listener is closing.");
|
||||
|
||||
long time = System.currentTimeMillis();
|
||||
|
||||
// Try to get to the correct count
|
||||
while (started.get() != count) {
|
||||
if (started.get() < count)
|
||||
start();
|
||||
else
|
||||
stop();
|
||||
|
||||
// May happen if another thread is doing something similar to "setWorkers"
|
||||
if ((System.currentTimeMillis() - time) > 50)
|
||||
throw new RuntimeException("Failed to set worker count.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the current number of registered workers.
|
||||
* <p>
|
||||
* Note that the returned value may be out of data.
|
||||
* @return Number of registered workers.
|
||||
*/
|
||||
public synchronized int getWorkers() {
|
||||
return started.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait until every tasks scheduled to stop has actually stopped.
|
||||
* @return TRUE if the current listener should stop, FALSE otherwise.
|
||||
* @throws InterruptedException - If the current thread was interrupted.
|
||||
*/
|
||||
private boolean waitForStops() throws InterruptedException {
|
||||
synchronized (stopLock) {
|
||||
while (stoppedTasks.size() > 0 && !cancelled) {
|
||||
stopLock.wait();
|
||||
}
|
||||
return cancelled;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The main processing loop of asynchronous threads.
|
||||
* <p>
|
||||
* Note: DO NOT call this method from the main thread
|
||||
* @param workerID - the current worker ID.
|
||||
*/
|
||||
private void listenerLoop(int workerID) {
|
||||
// Danger, danger!
|
||||
if (Thread.currentThread().getId() == mainThread.getId())
|
||||
throw new IllegalStateException("Do not call this method from the main thread.");
|
||||
if (cancelled)
|
||||
throw new IllegalStateException("Listener has been cancelled. Create a new listener instead.");
|
||||
|
||||
try {
|
||||
// Wait if certain threads are stopping
|
||||
if (waitForStops())
|
||||
return;
|
||||
|
||||
// Proceed
|
||||
started.incrementAndGet();
|
||||
|
||||
while (!cancelled) {
|
||||
PacketEvent packet = queuedPackets.take();
|
||||
|
||||
// Handle cancel requests
|
||||
if (packet == WAKEUP_PACKET) {
|
||||
// This is a bit slow, but it should be safe
|
||||
synchronized (stopLock) {
|
||||
// Are we the one who is supposed to stop?
|
||||
if (stoppedTasks.contains(workerID))
|
||||
return;
|
||||
if (waitForStops())
|
||||
return;
|
||||
}
|
||||
} else if (packet == INTERUPT_PACKET) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (packet != null && packet.getAsyncMarker() != null) {
|
||||
processPacket(workerID, packet, "onAsyncPacket()");
|
||||
}
|
||||
}
|
||||
|
||||
} catch (InterruptedException e) {
|
||||
// We're done
|
||||
} finally {
|
||||
// Clean up
|
||||
started.decrementAndGet();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a packet is scheduled for processing.
|
||||
* @param workerID - the current worker ID.
|
||||
* @param packet - the current packet.
|
||||
* @param methodName - name of the method.
|
||||
*/
|
||||
private void processPacket(int workerID, PacketEvent packet, String methodName) {
|
||||
AsyncMarker marker = packet.getAsyncMarker();
|
||||
|
||||
// Here's the core of the asynchronous processing
|
||||
try {
|
||||
synchronized (marker.getProcessingLock()) {
|
||||
marker.setListenerHandler(this);
|
||||
marker.setWorkerID(workerID);
|
||||
|
||||
// We're not THAT worried about performance here
|
||||
if (timedManager.isTiming()) {
|
||||
// Retrieve the tracker to use
|
||||
TimedTracker tracker = timedManager.getTracker(listener,
|
||||
packet.isServerPacket() ? ListenerType.ASYNC_SERVER_SIDE : ListenerType.ASYNC_CLIENT_SIDE);
|
||||
long token = tracker.beginTracking();
|
||||
|
||||
if (packet.isServerPacket())
|
||||
listener.onPacketSending(packet);
|
||||
else
|
||||
listener.onPacketReceiving(packet);
|
||||
|
||||
// And we're done
|
||||
tracker.endTracking(token, packet.getPacketType());
|
||||
|
||||
} else {
|
||||
if (packet.isServerPacket())
|
||||
listener.onPacketSending(packet);
|
||||
else
|
||||
listener.onPacketReceiving(packet);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (OutOfMemoryError e) {
|
||||
throw e;
|
||||
} catch (ThreadDeath e) {
|
||||
throw e;
|
||||
} catch (Throwable e) {
|
||||
// Minecraft doesn't want your Exception.
|
||||
filterManager.getErrorReporter().reportMinimal(listener.getPlugin(), methodName, e);
|
||||
}
|
||||
|
||||
// Now, get the next non-cancelled listener
|
||||
if (!marker.hasExpired()) {
|
||||
for (; marker.getListenerTraversal().hasNext(); ) {
|
||||
AsyncListenerHandler handler = marker.getListenerTraversal().next().getListener();
|
||||
|
||||
if (!handler.isCancelled()) {
|
||||
handler.enqueuePacket(packet);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// There are no more listeners - queue the packet for transmission
|
||||
filterManager.signalFreeProcessingSlot(packet);
|
||||
|
||||
// Note that listeners can opt to delay the packet transmission
|
||||
filterManager.signalPacketTransmission(packet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Close all worker threads and the handler itself.
|
||||
*/
|
||||
private synchronized void close() {
|
||||
// Remove the listener itself
|
||||
if (!cancelled) {
|
||||
filterManager.unregisterAsyncHandlerInternal(this);
|
||||
cancelled = true;
|
||||
|
||||
// Close processing tasks
|
||||
syncStop();
|
||||
|
||||
// Tell every uncancelled thread to end
|
||||
stopThreads();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Use the poision pill method to stop every worker thread.
|
||||
*/
|
||||
private void stopThreads() {
|
||||
// Poison Pill Shutdown
|
||||
queuedPackets.clear();
|
||||
stop(started.get());
|
||||
|
||||
// Individual shut down is irrelevant now
|
||||
synchronized (stopLock) {
|
||||
stopLock.notifyAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,485 +0,0 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol.async;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import com.comphenix.protocol.PacketStream;
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.ProtocolLibrary;
|
||||
import com.comphenix.protocol.events.NetworkMarker;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.injector.PrioritizedListener;
|
||||
import com.comphenix.protocol.reflect.FieldAccessException;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
import com.comphenix.protocol.utility.MinecraftVersion;
|
||||
import com.google.common.primitives.Longs;
|
||||
|
||||
/**
|
||||
* Contains information about the packet that is being processed by asynchronous listeners.
|
||||
* <p>
|
||||
* Asynchronous listeners can use this to set packet timeout or transmission order.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
|
||||
|
||||
/**
|
||||
* Generated by Eclipse.
|
||||
*/
|
||||
private static final long serialVersionUID = -2621498096616187384L;
|
||||
|
||||
/**
|
||||
* Default number of milliseconds until a packet will rejected.
|
||||
*/
|
||||
public static final int DEFAULT_TIMEOUT_DELTA = 1800 * 1000;
|
||||
|
||||
/**
|
||||
* Default number of packets to skip.
|
||||
*/
|
||||
public static final int DEFAULT_SENDING_DELTA = 0;
|
||||
|
||||
/**
|
||||
* The packet stream responsible for transmitting the packet when it's done processing.
|
||||
*/
|
||||
private transient PacketStream packetStream;
|
||||
|
||||
/**
|
||||
* Current list of async packet listeners.
|
||||
*/
|
||||
private transient Iterator<PrioritizedListener<AsyncListenerHandler>> listenerTraversal;
|
||||
|
||||
// Timeout handling
|
||||
private long initialTime;
|
||||
private long timeout;
|
||||
|
||||
// Packet order
|
||||
private long originalSendingIndex;
|
||||
private long newSendingIndex;
|
||||
|
||||
// Used to determine if a packet must be reordered in the sending queue
|
||||
private Long queuedSendingIndex;
|
||||
|
||||
// Whether or not the packet has been processed by the listeners
|
||||
private volatile boolean processed;
|
||||
|
||||
// Whether or not the packet has been sent
|
||||
private volatile boolean transmitted;
|
||||
|
||||
// Whether or not the asynchronous processing itself should be cancelled
|
||||
private volatile boolean asyncCancelled;
|
||||
|
||||
// Whether or not to delay processing
|
||||
private AtomicInteger processingDelay = new AtomicInteger();
|
||||
|
||||
// Used to synchronize processing on the shared PacketEvent
|
||||
private Object processingLock = new Object();
|
||||
|
||||
// Used to identify the asynchronous worker
|
||||
private transient AsyncListenerHandler listenerHandler;
|
||||
private transient int workerID;
|
||||
|
||||
// Determine if Minecraft processes this packet asynchronously
|
||||
private volatile static Method isMinecraftAsync;
|
||||
private volatile static boolean alwaysSync;
|
||||
|
||||
/**
|
||||
* Create a container for asyncronous packets.
|
||||
* @param initialTime - the current time in milliseconds since 01.01.1970 00:00.
|
||||
*/
|
||||
AsyncMarker(PacketStream packetStream, long sendingIndex, long initialTime, long timeoutDelta) {
|
||||
if (packetStream == null)
|
||||
throw new IllegalArgumentException("packetStream cannot be NULL");
|
||||
|
||||
this.packetStream = packetStream;
|
||||
|
||||
// Timeout
|
||||
this.initialTime = initialTime;
|
||||
this.timeout = initialTime + timeoutDelta;
|
||||
|
||||
// Sending index
|
||||
this.originalSendingIndex = sendingIndex;
|
||||
this.newSendingIndex = sendingIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the time the packet was initially queued for asynchronous processing.
|
||||
* @return The initial time in number of milliseconds since 01.01.1970 00:00.
|
||||
*/
|
||||
public long getInitialTime() {
|
||||
return initialTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the time the packet will be forcefully rejected.
|
||||
* @return The time to reject the packet, in milliseconds since 01.01.1970 00:00.
|
||||
*/
|
||||
public long getTimeout() {
|
||||
return timeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the time the packet will be forcefully rejected.
|
||||
* @param timeout - time to reject the packet, in milliseconds since 01.01.1970 00:00.
|
||||
*/
|
||||
public void setTimeout(long timeout) {
|
||||
this.timeout = timeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the order the packet was originally transmitted.
|
||||
* @return The original packet index.
|
||||
*/
|
||||
public long getOriginalSendingIndex() {
|
||||
return originalSendingIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the desired sending order after processing has completed.
|
||||
* <p>
|
||||
* Higher sending order means lower priority.
|
||||
* @return Desired sending order.
|
||||
*/
|
||||
public long getNewSendingIndex() {
|
||||
return newSendingIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the desired sending order after processing has completed.
|
||||
* <p>
|
||||
* Higher sending order means lower priority.
|
||||
* @param newSendingIndex - new packet send index.
|
||||
*/
|
||||
public void setNewSendingIndex(long newSendingIndex) {
|
||||
this.newSendingIndex = newSendingIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the packet stream responsible for transmitting this packet.
|
||||
* @return The packet stream.
|
||||
*/
|
||||
public PacketStream getPacketStream() {
|
||||
return packetStream;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the output packet stream responsible for transmitting this packet.
|
||||
* @param packetStream - new output packet stream.
|
||||
*/
|
||||
public void setPacketStream(PacketStream packetStream) {
|
||||
this.packetStream = packetStream;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve whether or not this packet has been processed by the async listeners.
|
||||
* @return TRUE if it has been processed, FALSE otherwise.
|
||||
*/
|
||||
public boolean isProcessed() {
|
||||
return processed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether or not this packet has been processed by the async listeners.
|
||||
* @param processed - TRUE if it has, FALSE otherwise.
|
||||
*/
|
||||
void setProcessed(boolean processed) {
|
||||
this.processed = processed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment the number of times the current packet must be signalled as done before its transmitted.
|
||||
* <p>
|
||||
* This is useful if an asynchronous listener is waiting for further information before the
|
||||
* packet can be sent to the user. A packet listener <b>MUST</b> eventually call
|
||||
* {@link AsyncFilterManager#signalPacketTransmission(PacketEvent)},
|
||||
* even if the packet is cancelled, after this method is called.
|
||||
* <p>
|
||||
* It is recommended that processing outside a packet listener is wrapped in a synchronized block
|
||||
* using the {@link #getProcessingLock()} method.
|
||||
*
|
||||
* @return The new processing delay.
|
||||
*/
|
||||
public int incrementProcessingDelay() {
|
||||
return processingDelay.incrementAndGet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrement the number of times this packet must be signalled as done before it's transmitted.
|
||||
* @return The new processing delay. If zero, the packet should be sent.
|
||||
*/
|
||||
int decrementProcessingDelay() {
|
||||
return processingDelay.decrementAndGet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the number of times a packet must be signalled to be done before it's sent.
|
||||
* @return Number of processing delays.
|
||||
*/
|
||||
public int getProcessingDelay() {
|
||||
return processingDelay.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not this packet is or has been queued for processing.
|
||||
* @return TRUE if it has, FALSE otherwise.
|
||||
*/
|
||||
public boolean isQueued() {
|
||||
return queuedSendingIndex != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the sending index when the packet was queued.
|
||||
* @return Queued sending index.
|
||||
*/
|
||||
public long getQueuedSendingIndex() {
|
||||
return queuedSendingIndex != null ? queuedSendingIndex : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the sending index when the packet was queued.
|
||||
* @param queuedSendingIndex - sending index.
|
||||
*/
|
||||
void setQueuedSendingIndex(Long queuedSendingIndex) {
|
||||
this.queuedSendingIndex = queuedSendingIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processing lock used to synchronize access to the parent PacketEvent and PacketContainer.
|
||||
* <p>
|
||||
* This lock is automatically acquired for every asynchronous packet listener. It should only be
|
||||
* used to synchronize access to a PacketEvent if it's processing has been delayed.
|
||||
* @return A processing lock.
|
||||
*/
|
||||
public Object getProcessingLock() {
|
||||
return processingLock;
|
||||
}
|
||||
|
||||
public void setProcessingLock(Object processingLock) {
|
||||
this.processingLock = processingLock;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve whether or not this packet has already been sent.
|
||||
* @return TRUE if it has been sent before, FALSE otherwise.
|
||||
*/
|
||||
public boolean isTransmitted() {
|
||||
return transmitted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if this packet has expired.
|
||||
* @return TRUE if it has, FALSE otherwise.
|
||||
*/
|
||||
public boolean hasExpired() {
|
||||
return hasExpired(System.currentTimeMillis());
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if this packet has expired given this time.
|
||||
* @param currentTime - the current time in milliseconds since 01.01.1970 00:00.
|
||||
* @return TRUE if it has, FALSE otherwise.
|
||||
*/
|
||||
public boolean hasExpired(long currentTime) {
|
||||
return timeout < currentTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the asynchronous handling should be cancelled.
|
||||
* @return TRUE if it should, FALSE otherwise.
|
||||
*/
|
||||
public boolean isAsyncCancelled() {
|
||||
return asyncCancelled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether or not the asynchronous handling should be cancelled.
|
||||
* <p>
|
||||
* This is only relevant during the synchronous processing. Asynchronous
|
||||
* listeners should use the normal cancel-field to cancel a PacketEvent.
|
||||
*
|
||||
* @param asyncCancelled - TRUE to cancel it, FALSE otherwise.
|
||||
*/
|
||||
public void setAsyncCancelled(boolean asyncCancelled) {
|
||||
this.asyncCancelled = asyncCancelled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the current asynchronous listener handler.
|
||||
* @return Asychronous listener handler, or NULL if this packet is not asynchronous.
|
||||
*/
|
||||
public AsyncListenerHandler getListenerHandler() {
|
||||
return listenerHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current asynchronous listener handler.
|
||||
* <p>
|
||||
* Used by the worker to update the value.
|
||||
* @param listenerHandler - new listener handler.
|
||||
*/
|
||||
void setListenerHandler(AsyncListenerHandler listenerHandler) {
|
||||
this.listenerHandler = listenerHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the current worker ID.
|
||||
* @return Current worker ID.
|
||||
*/
|
||||
public int getWorkerID() {
|
||||
return workerID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current worker ID.
|
||||
* <p>
|
||||
* Used by the worker.
|
||||
* @param workerID - new worker ID.
|
||||
*/
|
||||
void setWorkerID(int workerID) {
|
||||
this.workerID = workerID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve iterator for the next listener in line.
|
||||
* @return Next async packet listener iterator.
|
||||
*/
|
||||
Iterator<PrioritizedListener<AsyncListenerHandler>> getListenerTraversal() {
|
||||
return listenerTraversal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the iterator for the next listener.
|
||||
* @param listenerTraversal - the new async packet listener iterator.
|
||||
*/
|
||||
void setListenerTraversal(Iterator<PrioritizedListener<AsyncListenerHandler>> listenerTraversal) {
|
||||
this.listenerTraversal = listenerTraversal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transmit a given packet to the current packet stream.
|
||||
* @param event - the packet to send.
|
||||
* @throws IOException If the packet couldn't be sent.
|
||||
*/
|
||||
void sendPacket(PacketEvent event) throws IOException {
|
||||
try {
|
||||
if (event.isServerPacket()) {
|
||||
packetStream.sendServerPacket(event.getPlayer(), event.getPacket(), NetworkMarker.getNetworkMarker(event), false);
|
||||
} else {
|
||||
packetStream.recieveClientPacket(event.getPlayer(), event.getPacket(), NetworkMarker.getNetworkMarker(event), false);
|
||||
}
|
||||
transmitted = true;
|
||||
|
||||
} catch (InvocationTargetException e) {
|
||||
throw new IOException("Cannot send packet", e);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new IOException("Cannot send packet", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if Minecraft allows asynchronous processing of this packet.
|
||||
* @param event - packet event
|
||||
* @return TRUE if it does, FALSE otherwise.
|
||||
* @throws FieldAccessException If determining fails for some reasaon
|
||||
*/
|
||||
public boolean isMinecraftAsync(PacketEvent event) throws FieldAccessException {
|
||||
if (isMinecraftAsync == null && !alwaysSync) {
|
||||
try {
|
||||
isMinecraftAsync = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass()).getMethodByName("a_.*");
|
||||
} catch (RuntimeException e) {
|
||||
// This will occur in 1.2.5 (or possibly in later versions)
|
||||
List<Method> methods = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass()).
|
||||
getMethodListByParameters(boolean.class, new Class[] {});
|
||||
|
||||
// Try to look for boolean methods
|
||||
if (methods.size() == 2) {
|
||||
isMinecraftAsync = methods.get(1);
|
||||
} else if (methods.size() == 1) {
|
||||
// We're in 1.2.5
|
||||
alwaysSync = true;
|
||||
} else if (MinecraftVersion.getCurrentVersion().isAtLeast(MinecraftVersion.BOUNTIFUL_UPDATE)) {
|
||||
// The centralized async marker was removed in 1.8
|
||||
// Incoming chat packets can be async
|
||||
if (event.getPacketType() == PacketType.Play.Client.CHAT) {
|
||||
String content = event.getPacket().getStrings().readSafely(0);
|
||||
if (content != null) {
|
||||
// Incoming chat packets are async only if they aren't commands
|
||||
return ! content.startsWith("/");
|
||||
} else {
|
||||
ProtocolLibrary.log(Level.WARNING, "Failed to determine contents of incoming chat packet!");
|
||||
alwaysSync = true;
|
||||
}
|
||||
} else {
|
||||
// TODO: Find more cases of async packets
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
ProtocolLibrary.log(Level.INFO, "Could not determine asynchronous state of packets (this can probably be ignored)");
|
||||
alwaysSync = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (alwaysSync) {
|
||||
return false;
|
||||
} else {
|
||||
try {
|
||||
// Wrap exceptions
|
||||
return (Boolean) isMinecraftAsync.invoke(event.getPacket().getHandle());
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new FieldAccessException("Illegal argument", e);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new FieldAccessException("Unable to reflect method call 'a_', or: isAsyncPacket.", e);
|
||||
} catch (InvocationTargetException e) {
|
||||
throw new FieldAccessException("Minecraft error", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(AsyncMarker o) {
|
||||
if (o == null)
|
||||
return 1;
|
||||
else
|
||||
return Longs.compare(getNewSendingIndex(), o.getNewSendingIndex());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
// Standard equals
|
||||
if (other == this)
|
||||
return true;
|
||||
if (other instanceof AsyncMarker)
|
||||
return getNewSendingIndex() == ((AsyncMarker) other).getNewSendingIndex();
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Longs.hashCode(getNewSendingIndex());
|
||||
}
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol.async;
|
||||
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import com.comphenix.protocol.events.ListenerOptions;
|
||||
import com.comphenix.protocol.events.ListenerPriority;
|
||||
import com.comphenix.protocol.events.ListeningWhitelist;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.events.PacketListener;
|
||||
|
||||
/**
|
||||
* Represents a NO OPERATION listener.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
class NullPacketListener implements PacketListener {
|
||||
|
||||
private ListeningWhitelist sendingWhitelist;
|
||||
private ListeningWhitelist receivingWhitelist;
|
||||
private Plugin plugin;
|
||||
|
||||
/**
|
||||
* Create a no-op listener with the same whitelist and plugin as the given listener.
|
||||
* @param original - the packet listener to copy.
|
||||
*/
|
||||
public NullPacketListener(PacketListener original) {
|
||||
this.sendingWhitelist = cloneWhitelist(ListenerPriority.LOW, original.getSendingWhitelist());
|
||||
this.receivingWhitelist = cloneWhitelist(ListenerPriority.LOW, original.getReceivingWhitelist());
|
||||
this.plugin = original.getPlugin();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketSending(PacketEvent event) {
|
||||
// NULL
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketReceiving(PacketEvent event) {
|
||||
// NULL
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListeningWhitelist getSendingWhitelist() {
|
||||
return sendingWhitelist;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListeningWhitelist getReceivingWhitelist() {
|
||||
return receivingWhitelist;
|
||||
}
|
||||
|
||||
private ListeningWhitelist cloneWhitelist(ListenerPriority priority, ListeningWhitelist whitelist) {
|
||||
if (whitelist != null)
|
||||
// We don't use the Bukkit API, so don't engage the ProtocolLib synchronization code
|
||||
return ListeningWhitelist.newBuilder(whitelist).priority(priority).mergeOptions(ListenerOptions.ASYNC).build();
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Plugin getPlugin() {
|
||||
return plugin;
|
||||
}
|
||||
}
|
||||
@@ -1,196 +0,0 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol.async;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.PriorityQueue;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.Semaphore;
|
||||
|
||||
import com.comphenix.protocol.ProtocolLibrary;
|
||||
import com.comphenix.protocol.concurrency.AbstractConcurrentListenerMultimap;
|
||||
import com.comphenix.protocol.error.Report;
|
||||
import com.comphenix.protocol.error.ReportType;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.injector.PrioritizedListener;
|
||||
import com.google.common.collect.MinMaxPriorityQueue;
|
||||
|
||||
|
||||
/**
|
||||
* Handles the processing of every packet type.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
class PacketProcessingQueue extends AbstractConcurrentListenerMultimap<AsyncListenerHandler> {
|
||||
public static final ReportType REPORT_GUAVA_CORRUPT_MISSING =
|
||||
new ReportType("Guava is either missing or corrupt. Reverting to PriorityQueue.");
|
||||
|
||||
// Initial number of elements
|
||||
public static final int INITIAL_CAPACITY = 64;
|
||||
|
||||
/**
|
||||
* Default maximum number of packets to process concurrently.
|
||||
*/
|
||||
public static final int DEFAULT_MAXIMUM_CONCURRENCY = 32;
|
||||
|
||||
/**
|
||||
* Default maximum number of packets to queue for processing.
|
||||
*/
|
||||
public static final int DEFAULT_QUEUE_LIMIT = 1024 * 60;
|
||||
|
||||
/**
|
||||
* Number of packets we're processing concurrently.
|
||||
*/
|
||||
private final int maximumConcurrency;
|
||||
private Semaphore concurrentProcessing;
|
||||
|
||||
// Queued packets for being processed
|
||||
private Queue<PacketEventHolder> processingQueue;
|
||||
|
||||
// Packets for sending
|
||||
private PlayerSendingHandler sendingHandler;
|
||||
|
||||
public PacketProcessingQueue(PlayerSendingHandler sendingHandler) {
|
||||
this(sendingHandler, INITIAL_CAPACITY, DEFAULT_QUEUE_LIMIT, DEFAULT_MAXIMUM_CONCURRENCY);
|
||||
}
|
||||
|
||||
public PacketProcessingQueue(PlayerSendingHandler sendingHandler, int initialSize, int maximumSize, int maximumConcurrency) {
|
||||
super();
|
||||
|
||||
try {
|
||||
this.processingQueue = Synchronization.queue(MinMaxPriorityQueue.
|
||||
expectedSize(initialSize).
|
||||
maximumSize(maximumSize).
|
||||
<PacketEventHolder>create(), null);
|
||||
} catch (IncompatibleClassChangeError e) {
|
||||
// Print in the console
|
||||
ProtocolLibrary.getErrorReporter().reportWarning(
|
||||
this, Report.newBuilder(REPORT_GUAVA_CORRUPT_MISSING).error(e));
|
||||
|
||||
// It's a Beta class after all
|
||||
this.processingQueue = Synchronization.queue(
|
||||
new PriorityQueue<PacketEventHolder>(), null);
|
||||
}
|
||||
|
||||
this.maximumConcurrency = maximumConcurrency;
|
||||
this.concurrentProcessing = new Semaphore(maximumConcurrency);
|
||||
this.sendingHandler = sendingHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue a packet for processing by the asynchronous listeners.
|
||||
* @param packet - packet to process.
|
||||
* @param onMainThread - whether or not this is occuring on the main thread.
|
||||
* @return TRUE if we sucessfully queued the packet, FALSE if the queue ran out if space.
|
||||
*/
|
||||
public boolean enqueue(PacketEvent packet, boolean onMainThread) {
|
||||
try {
|
||||
processingQueue.add(new PacketEventHolder(packet));
|
||||
|
||||
// Begin processing packets
|
||||
signalBeginProcessing(onMainThread);
|
||||
return true;
|
||||
} catch (IllegalStateException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Number of packet events in the queue.
|
||||
* @return The number of packet events in the queue.
|
||||
*/
|
||||
public int size() {
|
||||
return processingQueue.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the current method and each thread to signal that a packet might be ready for processing.
|
||||
* @param onMainThread - whether or not this is occuring on the main thread.
|
||||
*/
|
||||
public void signalBeginProcessing(boolean onMainThread) {
|
||||
while (concurrentProcessing.tryAcquire()) {
|
||||
PacketEventHolder holder = processingQueue.poll();
|
||||
|
||||
// Any packet queued?
|
||||
if (holder != null) {
|
||||
PacketEvent packet = holder.getEvent();
|
||||
AsyncMarker marker = packet.getAsyncMarker();
|
||||
Collection<PrioritizedListener<AsyncListenerHandler>> list = getListener(packet.getPacketType());
|
||||
|
||||
marker.incrementProcessingDelay();
|
||||
|
||||
// Yes, removing the marker will cause the chain to stop
|
||||
if (list != null) {
|
||||
Iterator<PrioritizedListener<AsyncListenerHandler>> iterator = list.iterator();
|
||||
|
||||
if (iterator.hasNext()) {
|
||||
marker.setListenerTraversal(iterator);
|
||||
iterator.next().getListener().enqueuePacket(packet);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// The packet has no further listeners. Just send it.
|
||||
if (marker.decrementProcessingDelay() == 0) {
|
||||
PacketSendingQueue sendingQueue = sendingHandler.getSendingQueue(packet, false);
|
||||
|
||||
// In case the player has logged out
|
||||
if (sendingQueue != null)
|
||||
sendingQueue.signalPacketUpdate(packet, onMainThread);
|
||||
}
|
||||
signalProcessingDone();
|
||||
|
||||
} else {
|
||||
// No more queued packets.
|
||||
signalProcessingDone();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a packet has been processed.
|
||||
*/
|
||||
public void signalProcessingDone() {
|
||||
concurrentProcessing.release();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the maximum number of packets to process at any given time.
|
||||
* @return Number of simultaneous packet to process.
|
||||
*/
|
||||
public int getMaximumConcurrency() {
|
||||
return maximumConcurrency;
|
||||
}
|
||||
|
||||
public void cleanupAll() {
|
||||
// Cancel all the threads and every listener
|
||||
for (PrioritizedListener<AsyncListenerHandler> handler : values()) {
|
||||
if (handler != null) {
|
||||
handler.getListener().cancel();
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the rest, just in case
|
||||
clearListeners();
|
||||
|
||||
// Remove every packet in the queue
|
||||
processingQueue.clear();
|
||||
}
|
||||
}
|
||||
@@ -1,307 +0,0 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol.async;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.PriorityBlockingQueue;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.ProtocolLibrary;
|
||||
import com.comphenix.protocol.error.Report;
|
||||
import com.comphenix.protocol.error.ReportType;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.injector.PlayerLoggedOutException;
|
||||
import com.comphenix.protocol.reflect.FieldAccessException;
|
||||
|
||||
/**
|
||||
* Represents packets ready to be transmitted to a client.
|
||||
* @author Kristian
|
||||
*/
|
||||
abstract class PacketSendingQueue {
|
||||
public static final ReportType REPORT_DROPPED_PACKET = new ReportType("Warning: Dropped packet index %s of type %s.");
|
||||
|
||||
public static final int INITIAL_CAPACITY = 10;
|
||||
|
||||
private PriorityBlockingQueue<PacketEventHolder> sendingQueue;
|
||||
|
||||
// Asynchronous packet sending
|
||||
private Executor asynchronousSender;
|
||||
// Whether or not packet transmission must occur on a specific thread
|
||||
private final boolean notThreadSafe;
|
||||
// Whether or not we've run the cleanup procedure
|
||||
private boolean cleanedUp = false;
|
||||
|
||||
/**
|
||||
* Create a packet sending queue.
|
||||
* @param notThreadSafe - whether or not to synchronize with the main thread or a background thread.
|
||||
*/
|
||||
public PacketSendingQueue(boolean notThreadSafe, Executor asynchronousSender) {
|
||||
this.sendingQueue = new PriorityBlockingQueue<PacketEventHolder>(INITIAL_CAPACITY);
|
||||
this.notThreadSafe = notThreadSafe;
|
||||
this.asynchronousSender = asynchronousSender;
|
||||
}
|
||||
|
||||
/**
|
||||
* Number of packet events in the queue.
|
||||
* @return The number of packet events in the queue.
|
||||
*/
|
||||
public int size() {
|
||||
return sendingQueue.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue a packet for sending.
|
||||
* @param packet - packet to queue.
|
||||
*/
|
||||
public void enqueue(PacketEvent packet) {
|
||||
sendingQueue.add(new PacketEventHolder(packet));
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when one of the packets have finished processing.
|
||||
* @param packetUpdated - the packet that has now been updated.
|
||||
* @param onMainThread - whether or not this is occuring on the main thread.
|
||||
*/
|
||||
public synchronized void signalPacketUpdate(PacketEvent packetUpdated, boolean onMainThread) {
|
||||
|
||||
AsyncMarker marker = packetUpdated.getAsyncMarker();
|
||||
|
||||
// Should we reorder the event?
|
||||
if (marker.getQueuedSendingIndex() != marker.getNewSendingIndex() && !marker.hasExpired()) {
|
||||
PacketEvent copy = PacketEvent.fromSynchronous(packetUpdated, marker);
|
||||
|
||||
// "Cancel" the original event
|
||||
packetUpdated.setReadOnly(false);
|
||||
packetUpdated.setCancelled(true);
|
||||
|
||||
// Enqueue the copy with the new sending index
|
||||
enqueue(copy);
|
||||
}
|
||||
|
||||
// Mark this packet as finished
|
||||
marker.setProcessed(true);
|
||||
trySendPackets(onMainThread);
|
||||
}
|
||||
|
||||
/***
|
||||
* Invoked when a list of packet IDs are no longer associated with any listeners.
|
||||
* @param packetsRemoved - packets that no longer have any listeners.
|
||||
* @param onMainThread - whether or not this is occuring on the main thread.
|
||||
*/
|
||||
public synchronized void signalPacketUpdate(List<PacketType> packetsRemoved, boolean onMainThread) {
|
||||
Set<PacketType> lookup = new HashSet<PacketType>(packetsRemoved);
|
||||
|
||||
// Note that this is O(n), so it might be expensive
|
||||
for (PacketEventHolder holder : sendingQueue) {
|
||||
PacketEvent event = holder.getEvent();
|
||||
|
||||
if (lookup.contains(event.getPacketType())) {
|
||||
event.getAsyncMarker().setProcessed(true);
|
||||
}
|
||||
}
|
||||
|
||||
// This is likely to have changed the situation a bit
|
||||
trySendPackets(onMainThread);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to send any remaining packets.
|
||||
* @param onMainThread - whether or not this is occuring on the main thread.
|
||||
*/
|
||||
public void trySendPackets(boolean onMainThread) {
|
||||
// Whether or not to continue sending packets
|
||||
boolean sending = true;
|
||||
|
||||
// Transmit as many packets as we can
|
||||
while (sending) {
|
||||
PacketEventHolder holder = sendingQueue.poll();
|
||||
|
||||
if (holder != null) {
|
||||
sending = processPacketHolder(onMainThread, holder);
|
||||
|
||||
if (!sending) {
|
||||
// Add it back again
|
||||
sendingQueue.add(holder);
|
||||
}
|
||||
|
||||
} else {
|
||||
// No more packets to send
|
||||
sending = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when a packet might be ready for transmission.
|
||||
* @param onMainThread - TRUE if we're on the main thread, FALSE otherwise.
|
||||
* @param holder - packet container.
|
||||
* @return TRUE to continue sending packets, FALSE otherwise.
|
||||
*/
|
||||
private boolean processPacketHolder(boolean onMainThread, final PacketEventHolder holder) {
|
||||
PacketEvent current = holder.getEvent();
|
||||
AsyncMarker marker = current.getAsyncMarker();
|
||||
boolean hasExpired = marker.hasExpired();
|
||||
|
||||
// Guard in cause the queue is closed
|
||||
if (cleanedUp) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// End condition?
|
||||
if (marker.isProcessed() || hasExpired) {
|
||||
if (hasExpired) {
|
||||
// Notify timeout listeners
|
||||
onPacketTimeout(current);
|
||||
|
||||
// Recompute
|
||||
marker = current.getAsyncMarker();
|
||||
hasExpired = marker.hasExpired();
|
||||
|
||||
// Could happen due to the timeout listeners
|
||||
if (!marker.isProcessed() && !hasExpired) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Is it okay to send the packet?
|
||||
if (!current.isCancelled() && !hasExpired) {
|
||||
// Make sure we're on the main thread
|
||||
if (notThreadSafe) {
|
||||
try {
|
||||
boolean wantAsync = marker.isMinecraftAsync(current);
|
||||
boolean wantSync = !wantAsync;
|
||||
|
||||
// Wait for the next main thread heartbeat if we haven't fulfilled our promise
|
||||
if (!onMainThread && wantSync) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Let's give it what it wants
|
||||
if (onMainThread && wantAsync) {
|
||||
asynchronousSender.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// We know this isn't on the main thread
|
||||
processPacketHolder(false, holder);
|
||||
}
|
||||
});
|
||||
|
||||
// Scheduler will do the rest
|
||||
return true;
|
||||
}
|
||||
|
||||
} catch (FieldAccessException e) {
|
||||
e.printStackTrace();
|
||||
|
||||
// Just drop the packet
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Silently skip players that have logged out
|
||||
if (isOnline(current.getPlayer())) {
|
||||
sendPacket(current);
|
||||
}
|
||||
}
|
||||
|
||||
// Drop the packet
|
||||
return true;
|
||||
}
|
||||
|
||||
// Add it back and stop sending
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when a packet has timed out.
|
||||
* @param event - the timed out packet.
|
||||
*/
|
||||
protected abstract void onPacketTimeout(PacketEvent event);
|
||||
|
||||
private boolean isOnline(Player player) {
|
||||
return player != null && player.isOnline();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send every packet, regardless of the processing state.
|
||||
*/
|
||||
private void forceSend() {
|
||||
while (true) {
|
||||
PacketEventHolder holder = sendingQueue.poll();
|
||||
|
||||
if (holder != null) {
|
||||
sendPacket(holder.getEvent());
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not the packet transmission must synchronize with the main thread.
|
||||
* @return TRUE if it must, FALSE otherwise.
|
||||
*/
|
||||
public boolean isSynchronizeMain() {
|
||||
return notThreadSafe;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transmit a packet, if it hasn't already.
|
||||
* @param event - the packet to transmit.
|
||||
*/
|
||||
private void sendPacket(PacketEvent event) {
|
||||
|
||||
AsyncMarker marker = event.getAsyncMarker();
|
||||
|
||||
try {
|
||||
// Don't send a packet twice
|
||||
if (marker != null && !marker.isTransmitted()) {
|
||||
marker.sendPacket(event);
|
||||
}
|
||||
|
||||
} catch (PlayerLoggedOutException e) {
|
||||
ProtocolLibrary.getErrorReporter().reportDebug(this, Report.newBuilder(REPORT_DROPPED_PACKET).
|
||||
messageParam(marker.getOriginalSendingIndex(), event.getPacketType()).
|
||||
callerParam(event)
|
||||
);
|
||||
|
||||
} catch (IOException e) {
|
||||
// Just print the error
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatically transmits every delayed packet.
|
||||
*/
|
||||
public void cleanupAll() {
|
||||
if (!cleanedUp) {
|
||||
// Note that the cleanup itself will always occur on the main thread
|
||||
forceSend();
|
||||
|
||||
// And we're done
|
||||
cleanedUp = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.async;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.concurrency.ConcurrentPlayerMap;
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.injector.SortedPacketListenerList;
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
|
||||
/**
|
||||
* Contains every sending queue for every player.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
class PlayerSendingHandler {
|
||||
private ErrorReporter reporter;
|
||||
private ConcurrentMap<Player, QueueContainer> playerSendingQueues;
|
||||
|
||||
// Timeout listeners
|
||||
private SortedPacketListenerList serverTimeoutListeners;
|
||||
private SortedPacketListenerList clientTimeoutListeners;
|
||||
|
||||
// Asynchronous packet sending
|
||||
private Executor asynchronousSender;
|
||||
|
||||
// Whether or not we're currently cleaning up
|
||||
private volatile boolean cleaningUp;
|
||||
|
||||
/**
|
||||
* Sending queues for a given player.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
private class QueueContainer {
|
||||
private PacketSendingQueue serverQueue;
|
||||
private PacketSendingQueue clientQueue;
|
||||
|
||||
public QueueContainer() {
|
||||
// Server packets can be sent concurrently
|
||||
serverQueue = new PacketSendingQueue(false, asynchronousSender) {
|
||||
@Override
|
||||
protected void onPacketTimeout(PacketEvent event) {
|
||||
if (!cleaningUp) {
|
||||
serverTimeoutListeners.invokePacketSending(reporter, event);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Client packets must be synchronized
|
||||
clientQueue = new PacketSendingQueue(true, asynchronousSender) {
|
||||
@Override
|
||||
protected void onPacketTimeout(PacketEvent event) {
|
||||
if (!cleaningUp) {
|
||||
clientTimeoutListeners.invokePacketSending(reporter, event);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public PacketSendingQueue getServerQueue() {
|
||||
return serverQueue;
|
||||
}
|
||||
|
||||
public PacketSendingQueue getClientQueue() {
|
||||
return clientQueue;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a packet sending handler.
|
||||
* @param reporter - error reporter.
|
||||
* @param serverTimeoutListeners - set of server timeout listeners.
|
||||
* @param clientTimeoutListeners - set of client timeout listeners.
|
||||
*/
|
||||
public PlayerSendingHandler(ErrorReporter reporter,
|
||||
SortedPacketListenerList serverTimeoutListeners, SortedPacketListenerList clientTimeoutListeners) {
|
||||
|
||||
this.reporter = reporter;
|
||||
this.serverTimeoutListeners = serverTimeoutListeners;
|
||||
this.clientTimeoutListeners = clientTimeoutListeners;
|
||||
|
||||
// Initialize storage of queues
|
||||
this.playerSendingQueues = ConcurrentPlayerMap.usingAddress();
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the asynchronous packet sender.
|
||||
*/
|
||||
public synchronized void initializeScheduler() {
|
||||
if (asynchronousSender == null) {
|
||||
ThreadFactory factory = new ThreadFactoryBuilder().
|
||||
setDaemon(true).
|
||||
setNameFormat("ProtocolLib-AsyncSender %s").
|
||||
build();
|
||||
asynchronousSender = Executors.newSingleThreadExecutor(factory);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the sending queue this packet belongs to.
|
||||
* @param packet - the packet.
|
||||
* @return The server or client sending queue the packet belongs to.
|
||||
*/
|
||||
public PacketSendingQueue getSendingQueue(PacketEvent packet) {
|
||||
return getSendingQueue(packet, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the sending queue this packet belongs to.
|
||||
* @param packet - the packet.
|
||||
* @param createNew - if TRUE, create a new queue if it hasn't already been created.
|
||||
* @return The server or client sending queue the packet belongs to.
|
||||
*/
|
||||
public PacketSendingQueue getSendingQueue(PacketEvent packet, boolean createNew) {
|
||||
QueueContainer queues = playerSendingQueues.get(packet.getPlayer());
|
||||
|
||||
// Safe concurrent initialization
|
||||
if (queues == null && createNew) {
|
||||
final QueueContainer newContainer = new QueueContainer();
|
||||
|
||||
// Attempt to map the queue
|
||||
queues = playerSendingQueues.putIfAbsent(packet.getPlayer(), newContainer);
|
||||
|
||||
if (queues == null) {
|
||||
queues = newContainer;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for NULL again
|
||||
if (queues != null)
|
||||
return packet.isServerPacket() ? queues.getServerQueue() : queues.getClientQueue();
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send all pending packets.
|
||||
*/
|
||||
public void sendAllPackets() {
|
||||
if (!cleaningUp) {
|
||||
for (QueueContainer queues : playerSendingQueues.values()) {
|
||||
queues.getClientQueue().cleanupAll();
|
||||
queues.getServerQueue().cleanupAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Immediately send every server packet with the given list of IDs.
|
||||
* @param types - types of every packet to send immediately.
|
||||
* @param synchronusOK - whether or not we're running on the main thread.
|
||||
*/
|
||||
public void sendServerPackets(List<PacketType> types, boolean synchronusOK) {
|
||||
if (!cleaningUp) {
|
||||
for (QueueContainer queue : playerSendingQueues.values()) {
|
||||
queue.getServerQueue().signalPacketUpdate(types, synchronusOK);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Immediately send every client packet with the given list of IDs.
|
||||
* @param ids - ID of every packet to send immediately.
|
||||
* @param synchronusOK - whether or not we're running on the main thread.
|
||||
*/
|
||||
public void sendClientPackets(List<PacketType> types, boolean synchronusOK) {
|
||||
if (!cleaningUp) {
|
||||
for (QueueContainer queue : playerSendingQueues.values()) {
|
||||
queue.getClientQueue().signalPacketUpdate(types, synchronusOK);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send any outstanding server packets.
|
||||
* @param onMainThread - whether or not this is occuring on the main thread.
|
||||
*/
|
||||
public void trySendServerPackets(boolean onMainThread) {
|
||||
for (QueueContainer queue : playerSendingQueues.values()) {
|
||||
queue.getServerQueue().trySendPackets(onMainThread);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send any outstanding server packets.
|
||||
* @param onMainThread - whether or not this is occuring on the main thread.
|
||||
*/
|
||||
public void trySendClientPackets(boolean onMainThread) {
|
||||
for (QueueContainer queue : playerSendingQueues.values()) {
|
||||
queue.getClientQueue().trySendPackets(onMainThread);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve every server packet queue for every player.
|
||||
* @return Every sever packet queue.
|
||||
*/
|
||||
public List<PacketSendingQueue> getServerQueues() {
|
||||
List<PacketSendingQueue> result = new ArrayList<PacketSendingQueue>();
|
||||
|
||||
for (QueueContainer queue : playerSendingQueues.values())
|
||||
result.add(queue.getServerQueue());
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve every client packet queue for every player.
|
||||
* @return Every client packet queue.
|
||||
*/
|
||||
public List<PacketSendingQueue> getClientQueues() {
|
||||
List<PacketSendingQueue> result = new ArrayList<PacketSendingQueue>();
|
||||
|
||||
for (QueueContainer queue : playerSendingQueues.values())
|
||||
result.add(queue.getClientQueue());
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send all pending packets and clean up queues.
|
||||
*/
|
||||
public void cleanupAll() {
|
||||
if (!cleaningUp) {
|
||||
cleaningUp = true;
|
||||
|
||||
sendAllPackets();
|
||||
playerSendingQueues.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when a player has just logged out.
|
||||
* @param player - the player that just logged out.
|
||||
*/
|
||||
public void removePlayer(Player player) {
|
||||
// Every packet will be dropped - there's nothing we can do
|
||||
playerSendingQueues.remove(player);
|
||||
}
|
||||
}
|
||||
@@ -1,228 +0,0 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol.async;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.Queue;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
|
||||
/**
|
||||
* Synchronization views copied from Google Guava.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
class Synchronization {
|
||||
|
||||
/**
|
||||
* Create a synchronized wrapper for the given queue.
|
||||
* <p>
|
||||
* This wrapper cannot synchronize the iterator(). Callers are expected
|
||||
* to synchronize iterators manually.
|
||||
* @param queue - the queue to synchronize.
|
||||
* @param mutex - synchronization mutex, or NULL to use the queue.
|
||||
* @return A synchronization wrapper.
|
||||
*/
|
||||
public static <E> Queue<E> queue(Queue<E> queue, @Nullable Object mutex) {
|
||||
return (queue instanceof SynchronizedQueue) ?
|
||||
queue :
|
||||
new SynchronizedQueue<E>(queue, mutex);
|
||||
}
|
||||
|
||||
private static class SynchronizedObject implements Serializable {
|
||||
private static final long serialVersionUID = -4408866092364554628L;
|
||||
|
||||
final Object delegate;
|
||||
final Object mutex;
|
||||
|
||||
SynchronizedObject(Object delegate, @Nullable Object mutex) {
|
||||
this.delegate = Preconditions.checkNotNull(delegate);
|
||||
this.mutex = (mutex == null) ? this : mutex;
|
||||
}
|
||||
|
||||
Object delegate() {
|
||||
return delegate;
|
||||
}
|
||||
|
||||
// No equals and hashCode; see ForwardingObject for details.
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
synchronized (mutex) {
|
||||
return delegate.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class SynchronizedCollection<E> extends SynchronizedObject implements Collection<E> {
|
||||
private static final long serialVersionUID = 5440572373531285692L;
|
||||
|
||||
private SynchronizedCollection(Collection<E> delegate,
|
||||
@Nullable Object mutex) {
|
||||
super(delegate, mutex);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
Collection<E> delegate() {
|
||||
return (Collection<E>) super.delegate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean add(E e) {
|
||||
synchronized (mutex) {
|
||||
return delegate().add(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addAll(Collection<? extends E> c) {
|
||||
synchronized (mutex) {
|
||||
return delegate().addAll(c);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
synchronized (mutex) {
|
||||
delegate().clear();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(Object o) {
|
||||
synchronized (mutex) {
|
||||
return delegate().contains(o);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsAll(Collection<?> c) {
|
||||
synchronized (mutex) {
|
||||
return delegate().containsAll(c);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
synchronized (mutex) {
|
||||
return delegate().isEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<E> iterator() {
|
||||
return delegate().iterator(); // manually synchronized
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(Object o) {
|
||||
synchronized (mutex) {
|
||||
return delegate().remove(o);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeAll(Collection<?> c) {
|
||||
synchronized (mutex) {
|
||||
return delegate().removeAll(c);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean retainAll(Collection<?> c) {
|
||||
synchronized (mutex) {
|
||||
return delegate().retainAll(c);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
synchronized (mutex) {
|
||||
return delegate().size();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] toArray() {
|
||||
synchronized (mutex) {
|
||||
return delegate().toArray();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T[] toArray(T[] a) {
|
||||
synchronized (mutex) {
|
||||
return delegate().toArray(a);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class SynchronizedQueue<E> extends SynchronizedCollection<E> implements Queue<E> {
|
||||
private static final long serialVersionUID = 1961791630386791902L;
|
||||
|
||||
SynchronizedQueue(Queue<E> delegate, @Nullable Object mutex) {
|
||||
super(delegate, mutex);
|
||||
}
|
||||
|
||||
@Override
|
||||
Queue<E> delegate() {
|
||||
return (Queue<E>) super.delegate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public E element() {
|
||||
synchronized (mutex) {
|
||||
return delegate().element();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean offer(E e) {
|
||||
synchronized (mutex) {
|
||||
return delegate().offer(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public E peek() {
|
||||
synchronized (mutex) {
|
||||
return delegate().peek();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public E poll() {
|
||||
synchronized (mutex) {
|
||||
return delegate().poll();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public E remove() {
|
||||
synchronized (mutex) {
|
||||
return delegate().remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,246 +0,0 @@
|
||||
package com.comphenix.protocol.collections;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.PriorityQueue;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.base.Objects;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.base.Ticker;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.primitives.Longs;
|
||||
|
||||
/**
|
||||
* Represents a hash map where each association may expire after a given time has elapsed.
|
||||
* <p>
|
||||
* Note that replaced key-value associations are only collected once the original expiration time has elapsed.
|
||||
*
|
||||
* @author Kristian Stangeland
|
||||
*
|
||||
* @param <K> - type of the keys.
|
||||
* @param <V> - type of the values.
|
||||
*/
|
||||
public class ExpireHashMap<K, V> {
|
||||
private class ExpireEntry implements Comparable<ExpireEntry> {
|
||||
public final long expireTime;
|
||||
public final K expireKey;
|
||||
public final V expireValue;
|
||||
|
||||
public ExpireEntry(long expireTime, K expireKey, V expireValue) {
|
||||
this.expireTime = expireTime;
|
||||
this.expireKey = expireKey;
|
||||
this.expireValue = expireValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(ExpireEntry o) {
|
||||
return Longs.compare(expireTime, o.expireTime);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ExpireEntry [expireTime=" + expireTime + ", expireKey=" + expireKey
|
||||
+ ", expireValue=" + expireValue + "]";
|
||||
}
|
||||
}
|
||||
|
||||
private Map<K, ExpireEntry> keyLookup = new HashMap<K, ExpireEntry>();
|
||||
private PriorityQueue<ExpireEntry> expireQueue = new PriorityQueue<ExpireEntry>();
|
||||
|
||||
// View of keyLookup with direct values
|
||||
private Map<K, V> valueView = Maps.transformValues(keyLookup, new Function<ExpireEntry, V>() {
|
||||
@Override
|
||||
public V apply(ExpireEntry entry) {
|
||||
return entry.expireValue;
|
||||
}
|
||||
});
|
||||
|
||||
// Supplied by the constructor
|
||||
private Ticker ticker;
|
||||
|
||||
/**
|
||||
* Construct a new hash map where each entry may expire at a given time.
|
||||
*/
|
||||
public ExpireHashMap() {
|
||||
this(Ticker.systemTicker());
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new hash map where each entry may expire at a given time.
|
||||
* @param ticker - supplier of the current time.
|
||||
*/
|
||||
public ExpireHashMap(Ticker ticker) {
|
||||
this.ticker = ticker;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the value associated with the given key, if it has not expired.
|
||||
* @param key - the key.
|
||||
* @return The value, or NULL if not found or it has expired.
|
||||
*/
|
||||
public V get(K key) {
|
||||
evictExpired();
|
||||
|
||||
ExpireEntry entry = keyLookup.get(key);
|
||||
return entry != null ? entry.expireValue : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Associate the given key with the given value, until the expire delay have elapsed.
|
||||
* @param key - the key.
|
||||
* @param value - the value.
|
||||
* @param expireDelay - the amount of time until this association expires. Must be greater than zero.
|
||||
* @param expireUnit - the unit of the expiration.
|
||||
* @return Any previously unexpired association with this key, or NULL.
|
||||
*/
|
||||
public V put(K key, V value, long expireDelay, TimeUnit expireUnit) {
|
||||
Preconditions.checkNotNull(expireUnit, "expireUnit cannot be NULL");
|
||||
Preconditions.checkState(expireDelay > 0, "expireDelay cannot be equal or less than zero.");
|
||||
evictExpired();
|
||||
|
||||
ExpireEntry entry = new ExpireEntry(
|
||||
ticker.read() + TimeUnit.NANOSECONDS.convert(expireDelay, expireUnit),
|
||||
key, value
|
||||
);
|
||||
ExpireEntry previous = keyLookup.put(key, entry);
|
||||
|
||||
// We enqueue its removal
|
||||
expireQueue.add(entry);
|
||||
return previous != null ? previous.expireValue : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the given key is referring to an unexpired association in the map.
|
||||
* @param key - the key.
|
||||
* @return TRUE if it is, FALSE otherwise.
|
||||
*/
|
||||
public boolean containsKey(K key) {
|
||||
evictExpired();
|
||||
return keyLookup.containsKey(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the given value is referring to an unexpired association in the map.
|
||||
* @param value - the value.
|
||||
* @return TRUE if it is, FALSE otherwise.
|
||||
*/
|
||||
public boolean containsValue(V value) {
|
||||
evictExpired();
|
||||
|
||||
// Linear scan is the best we've got
|
||||
for (ExpireEntry entry : keyLookup.values()) {
|
||||
if (Objects.equal(value, entry.expireValue)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a key and its associated value from the map.
|
||||
* @param key - the key to remove.
|
||||
* @return Value of the removed association, NULL otherwise.
|
||||
*/
|
||||
public V removeKey(K key) {
|
||||
evictExpired();
|
||||
|
||||
ExpireEntry entry = keyLookup.remove(key);
|
||||
return entry != null ? entry.expireValue : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the number of entries in the map.
|
||||
* @return The number of entries.
|
||||
*/
|
||||
public int size() {
|
||||
evictExpired();
|
||||
return keyLookup.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a view of the keys in the current map.
|
||||
* @return View of the keys.
|
||||
*/
|
||||
public Set<K> keySet() {
|
||||
evictExpired();
|
||||
return keyLookup.keySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a view of all the values in the current map.
|
||||
* @return All the values.
|
||||
*/
|
||||
public Collection<V> values() {
|
||||
evictExpired();
|
||||
return valueView.values();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a view of all the entries in the set.
|
||||
* @return All the entries.
|
||||
*/
|
||||
public Set<Entry<K, V>> entrySet() {
|
||||
evictExpired();
|
||||
return valueView.entrySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a view of this expire map as an ordinary map that does not support insertion.
|
||||
* @return The map.
|
||||
*/
|
||||
public Map<K, V> asMap() {
|
||||
evictExpired();
|
||||
return valueView;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all references to key-value pairs that have been removed or replaced before they were naturally evicted.
|
||||
* <p>
|
||||
* This operation requires a linear scan of the current entries in the map.
|
||||
*/
|
||||
public void collect() {
|
||||
// First evict what we can
|
||||
evictExpired();
|
||||
|
||||
// Recreate the eviction queue - this is faster than removing entries in the old queue
|
||||
expireQueue.clear();
|
||||
expireQueue.addAll(keyLookup.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all the entries in the current map.
|
||||
*/
|
||||
public void clear() {
|
||||
keyLookup.clear();
|
||||
expireQueue.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Evict any expired entries in the map.
|
||||
* <p>
|
||||
* This is called automatically by any of the read or write operations.
|
||||
*/
|
||||
protected void evictExpired() {
|
||||
long currentTime = ticker.read();
|
||||
|
||||
// Remove expired entries
|
||||
while (expireQueue.size() > 0 && expireQueue.peek().expireTime <= currentTime) {
|
||||
ExpireEntry entry = expireQueue.poll();
|
||||
|
||||
if (entry == keyLookup.get(entry.expireKey)) {
|
||||
keyLookup.remove(entry.expireKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return valueView.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,140 +0,0 @@
|
||||
package com.comphenix.protocol.collections;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
/**
|
||||
* Represents a very quick integer-based lookup map, with a fixed key space size.
|
||||
* <p>
|
||||
* Integers must be non-negative.
|
||||
* @author Kristian
|
||||
*/
|
||||
public class IntegerMap<T> {
|
||||
private T[] array;
|
||||
private int size;
|
||||
|
||||
/**
|
||||
* Construct a new integer map.
|
||||
* @param <T> Parameter type
|
||||
* @return A new integer map.
|
||||
*/
|
||||
public static <T> IntegerMap<T> newMap() {
|
||||
return new IntegerMap<T>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new integer map with a default capacity.
|
||||
*/
|
||||
public IntegerMap() {
|
||||
this(8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new integer map with a given capacity.
|
||||
* @param initialCapacity - the capacity.
|
||||
*/
|
||||
public IntegerMap(int initialCapacity) {
|
||||
@SuppressWarnings("unchecked")
|
||||
T[] backingArray = (T[]) new Object[initialCapacity];
|
||||
this.array = backingArray;
|
||||
this.size = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Associate an integer key with the given value.
|
||||
* @param key - the integer key. Cannot be negative.
|
||||
* @param value - the value. Cannot be NULL.
|
||||
* @return The previous association, or NULL if not found.
|
||||
*/
|
||||
public T put(int key, T value) {
|
||||
ensureCapacity(key);
|
||||
|
||||
T old = array[key];
|
||||
array[key] = Preconditions.checkNotNull(value, "value cannot be NULL");
|
||||
|
||||
if (old == null)
|
||||
size++;
|
||||
return old;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an association from the map.
|
||||
* @param key - the key of the association to remove.
|
||||
* @return The old associated value, or NULL.
|
||||
*/
|
||||
public T remove(int key) {
|
||||
T old = array[key];
|
||||
array[key] = null;
|
||||
|
||||
if (old != null)
|
||||
size--;
|
||||
return old;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resize the backing array to fit the given key.
|
||||
* @param key - the key.
|
||||
*/
|
||||
protected void ensureCapacity(int key) {
|
||||
int newLength = array.length;
|
||||
|
||||
// Don't resize if the key fits
|
||||
if (key < 0)
|
||||
throw new IllegalArgumentException("Negative key values are not permitted.");
|
||||
if (key < newLength)
|
||||
return;
|
||||
|
||||
while (newLength <= key) {
|
||||
int next = newLength * 2;
|
||||
// Handle overflow
|
||||
newLength = next > newLength ? next : Integer.MAX_VALUE;
|
||||
}
|
||||
this.array = Arrays.copyOf(array, newLength);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the number of mappings in this map.
|
||||
* @return The number of mapping.
|
||||
*/
|
||||
public int size() {
|
||||
return size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the value associated with a given key.
|
||||
* @param key - the key.
|
||||
* @return The value, or NULL if not found.
|
||||
*/
|
||||
public T get(int key) {
|
||||
if (key >= 0 && key < array.length)
|
||||
return array[key];
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the given key exists in the map.
|
||||
* @param key - the key to check.
|
||||
* @return TRUE if it does, FALSE otherwise.
|
||||
*/
|
||||
public boolean containsKey(int key) {
|
||||
return get(key) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the current map to an Integer map.
|
||||
* @return The Integer map.
|
||||
*/
|
||||
public Map<Integer, Object> toMap() {
|
||||
Map<Integer, Object> map = Maps.newHashMap();
|
||||
|
||||
for (int i = 0; i < array.length; i++) {
|
||||
if (array[i] != null) {
|
||||
map.put(i, array[i]);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
}
|
||||
@@ -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,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.guava;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.util.Set;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.google.common.collect.Range;
|
||||
|
||||
/**
|
||||
* @author dmulloy2
|
||||
*/
|
||||
|
||||
public interface GuavaCompat {
|
||||
|
||||
<C extends Comparable<C>> Range<C> closedRange(C lower, C upper);
|
||||
|
||||
<C extends Comparable<C>> Range<C> singletonRange(C singleton);
|
||||
|
||||
Set<Integer> toSet(Range<Integer> range);
|
||||
|
||||
DataInputStream addHeader(DataInputStream input, PacketType type);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
-103
@@ -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);
|
||||
}
|
||||
-105
@@ -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());
|
||||
}
|
||||
}
|
||||
-234
@@ -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
|
||||
}
|
||||
-83
@@ -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();
|
||||
}
|
||||
}
|
||||
-392
@@ -1,392 +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.AbstractByteBuf;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufAllocator;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.channels.Channels;
|
||||
import java.nio.channels.GatheringByteChannel;
|
||||
import java.nio.channels.ScatteringByteChannel;
|
||||
import java.nio.channels.WritableByteChannel;
|
||||
|
||||
import com.comphenix.protocol.reflect.accessors.Accessors;
|
||||
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
import com.google.common.io.ByteStreams;
|
||||
|
||||
/**
|
||||
* Construct a ByteBuf around an input stream and an output stream.
|
||||
* <p>
|
||||
* Note that as streams usually don't support seeking, this implementation will ignore
|
||||
* all indexing in the byte buffer.
|
||||
* @author Kristian
|
||||
*/
|
||||
public class NettyByteBufAdapter extends AbstractByteBuf {
|
||||
private DataInputStream input;
|
||||
private DataOutputStream output;
|
||||
|
||||
// For modifying the reader or writer index
|
||||
private static FieldAccessor READER_INDEX;
|
||||
private static FieldAccessor WRITER_INDEX;
|
||||
|
||||
private static final int CAPACITY = Short.MAX_VALUE;
|
||||
|
||||
private NettyByteBufAdapter(DataInputStream input, DataOutputStream output) {
|
||||
// Just pick a figure
|
||||
super(CAPACITY);
|
||||
this.input = input;
|
||||
this.output = output;
|
||||
|
||||
// Prepare accessors
|
||||
try {
|
||||
if (READER_INDEX == null) {
|
||||
READER_INDEX = Accessors.getFieldAccessor(AbstractByteBuf.class.getDeclaredField("readerIndex"));
|
||||
}
|
||||
if (WRITER_INDEX == null) {
|
||||
WRITER_INDEX = Accessors.getFieldAccessor(AbstractByteBuf.class.getDeclaredField("writerIndex"));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Cannot initialize ByteBufAdapter.", e);
|
||||
}
|
||||
|
||||
// "Infinite" reading/writing
|
||||
if (input == null)
|
||||
READER_INDEX.set(this, Integer.MAX_VALUE);
|
||||
if (output == null)
|
||||
WRITER_INDEX.set(this, Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new Minecraft packet serializer using the current byte buf adapter.
|
||||
* @param input - the input stream.
|
||||
* @return A packet serializer with a wrapped byte buf adapter.
|
||||
*/
|
||||
public static ByteBuf packetReader(DataInputStream input) {
|
||||
return (ByteBuf) MinecraftReflection.getPacketDataSerializer(new NettyByteBufAdapter(input, null));
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new Minecraft packet deserializer using the current byte buf adapter.
|
||||
* @param output - the output stream.
|
||||
* @return A packet serializer with a wrapped byte buf adapter.
|
||||
*/
|
||||
public static ByteBuf packetWriter(DataOutputStream output) {
|
||||
return (ByteBuf) MinecraftReflection.getPacketDataSerializer(new NettyByteBufAdapter(null, output));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int refCnt() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean release() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean release(int paramInt) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte _getByte(int paramInt) {
|
||||
try {
|
||||
return input.readByte();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Cannot read input.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected short _getShort(int paramInt) {
|
||||
try {
|
||||
return input.readShort();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Cannot read input.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int _getUnsignedMedium(int paramInt) {
|
||||
try {
|
||||
return input.readUnsignedShort();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Cannot read input.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int _getInt(int paramInt) {
|
||||
try {
|
||||
return input.readInt();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Cannot read input.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected long _getLong(int paramInt) {
|
||||
try {
|
||||
return input.readLong();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Cannot read input.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void _setByte(int index, int value) {
|
||||
try {
|
||||
output.writeByte(value);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Cannot write output.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void _setShort(int index, int value) {
|
||||
try {
|
||||
output.writeShort(value);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Cannot write output.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void _setMedium(int index, int value) {
|
||||
try {
|
||||
output.writeShort(value);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Cannot write output.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void _setInt(int index, int value) {
|
||||
try {
|
||||
output.writeInt(value);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Cannot write output.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void _setLong(int index, long value) {
|
||||
try {
|
||||
output.writeLong(value);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Cannot write output.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int capacity() {
|
||||
return CAPACITY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf capacity(int paramInt) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBufAllocator alloc() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteOrder order() {
|
||||
return ByteOrder.LITTLE_ENDIAN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf unwrap() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDirect() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf getBytes(int index, ByteBuf dst, int dstIndex, int length) {
|
||||
try {
|
||||
for (int i = 0; i < length; i++) {
|
||||
dst.setByte(dstIndex + i, input.read());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Cannot read input.", e);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf getBytes(int index, byte[] dst, int dstIndex, int length) {
|
||||
try {
|
||||
input.read(dst, dstIndex, length);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Cannot read input.", e);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf getBytes(int index, ByteBuffer dst) {
|
||||
try {
|
||||
dst.put(ByteStreams.toByteArray(input));
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Cannot read input.", e);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf getBytes(int index, OutputStream dst, int length) throws IOException {
|
||||
ByteStreams.copy(ByteStreams.limit(input, length), dst);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBytes(int index, GatheringByteChannel out, int length) throws IOException {
|
||||
byte[] data = ByteStreams.toByteArray(ByteStreams.limit(input, length));
|
||||
|
||||
out.write(ByteBuffer.wrap(data));
|
||||
return data.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setBytes(int index, ByteBuf src, int srcIndex, int length) {
|
||||
byte[] buffer = new byte[length];
|
||||
src.getBytes(srcIndex, buffer);
|
||||
|
||||
try {
|
||||
output.write(buffer);
|
||||
return this;
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Cannot write output.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setBytes(int index, byte[] src, int srcIndex, int length) {
|
||||
try {
|
||||
output.write(src, srcIndex, length);
|
||||
return this;
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Cannot write output.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setBytes(int index, ByteBuffer src) {
|
||||
try {
|
||||
WritableByteChannel channel = Channels.newChannel(output);
|
||||
|
||||
channel.write(src);
|
||||
return this;
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Cannot write output.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int setBytes(int index, InputStream in, int length) throws IOException {
|
||||
InputStream limit = ByteStreams.limit(in, length);
|
||||
ByteStreams.copy(limit, output);
|
||||
return length - limit.available();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int setBytes(int index, ScatteringByteChannel in, int length) throws IOException {
|
||||
ByteBuffer buffer = ByteBuffer.allocate(length);
|
||||
WritableByteChannel channel = Channels.newChannel(output);
|
||||
|
||||
int count = in.read(buffer);
|
||||
channel.write(buffer);
|
||||
return count;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf copy(int index, int length) {
|
||||
throw new UnsupportedOperationException("Cannot seek in input stream.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int nioBufferCount() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer nioBuffer(int paramInt1, int paramInt2) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer internalNioBuffer(int paramInt1, int paramInt2) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer[] nioBuffers(int paramInt1, int paramInt2) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasArray() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] array() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int arrayOffset() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasMemoryAddress() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long memoryAddress() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf retain(int paramInt) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf retain() {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
-38
@@ -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);
|
||||
}
|
||||
}
|
||||
-932
@@ -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);
|
||||
}
|
||||
}
|
||||
-334
@@ -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);
|
||||
}
|
||||
}
|
||||
-259
@@ -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();
|
||||
}
|
||||
}
|
||||
-239
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
-371
@@ -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);
|
||||
}
|
||||
}
|
||||
-441
@@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
-104
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-265
@@ -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();
|
||||
}
|
||||
}
|
||||
-144
@@ -1,144 +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.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.events.ListeningWhitelist;
|
||||
import com.comphenix.protocol.injector.PrioritizedListener;
|
||||
import com.google.common.collect.Iterables;
|
||||
|
||||
/**
|
||||
* A thread-safe implementation of a listener multimap.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public abstract class AbstractConcurrentListenerMultimap<TListener> {
|
||||
// The core of our map
|
||||
private ConcurrentMap<PacketType, SortedCopyOnWriteArray<PrioritizedListener<TListener>>> mapListeners;
|
||||
|
||||
public AbstractConcurrentListenerMultimap() {
|
||||
mapListeners = new ConcurrentHashMap<PacketType, SortedCopyOnWriteArray<PrioritizedListener<TListener>>>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a listener to its requested list of packet receivers.
|
||||
* @param listener - listener with a list of packets to receive notifications for.
|
||||
* @param whitelist - the packet whitelist to use.
|
||||
*/
|
||||
public void addListener(TListener listener, ListeningWhitelist whitelist) {
|
||||
PrioritizedListener<TListener> prioritized = new PrioritizedListener<TListener>(listener, whitelist.getPriority());
|
||||
|
||||
for (PacketType type : whitelist.getTypes()) {
|
||||
addListener(type, prioritized);
|
||||
}
|
||||
}
|
||||
|
||||
// Add the listener to a specific packet notifcation list
|
||||
private void addListener(PacketType type, PrioritizedListener<TListener> listener) {
|
||||
SortedCopyOnWriteArray<PrioritizedListener<TListener>> list = mapListeners.get(type);
|
||||
|
||||
// We don't want to create this for every lookup
|
||||
if (list == null) {
|
||||
// It would be nice if we could use a PriorityBlockingQueue, but it doesn't preseve iterator order,
|
||||
// which is a essential feature for our purposes.
|
||||
final SortedCopyOnWriteArray<PrioritizedListener<TListener>> value = new SortedCopyOnWriteArray<PrioritizedListener<TListener>>();
|
||||
|
||||
// We may end up creating multiple multisets, but we'll agree on which to use
|
||||
list = mapListeners.putIfAbsent(type, value);
|
||||
|
||||
if (list == null) {
|
||||
list = value;
|
||||
}
|
||||
}
|
||||
|
||||
// Thread safe
|
||||
list.add(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the given listener from the packet event list.
|
||||
* @param listener - listener to remove.
|
||||
* @param whitelist - the packet whitelist that was used.
|
||||
* @return Every packet ID that was removed due to no listeners.
|
||||
*/
|
||||
public List<PacketType> removeListener(TListener listener, ListeningWhitelist whitelist) {
|
||||
List<PacketType> removedPackets = new ArrayList<PacketType>();
|
||||
|
||||
// Again, not terribly efficient. But adding or removing listeners should be a rare event.
|
||||
for (PacketType type : whitelist.getTypes()) {
|
||||
SortedCopyOnWriteArray<PrioritizedListener<TListener>> list = mapListeners.get(type);
|
||||
|
||||
// Remove any listeners
|
||||
if (list != null) {
|
||||
// Don't remove from newly created lists
|
||||
if (list.size() > 0) {
|
||||
// Remove this listener. Note that priority is generally ignored.
|
||||
list.remove(new PrioritizedListener<TListener>(listener, whitelist.getPriority()));
|
||||
|
||||
if (list.size() == 0) {
|
||||
mapListeners.remove(type);
|
||||
removedPackets.add(type);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Move on to the next
|
||||
}
|
||||
return removedPackets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the registered listeners, in order from the lowest to the highest priority.
|
||||
* <p>
|
||||
* The returned list is thread-safe and doesn't require synchronization.
|
||||
* @param type - packet type.
|
||||
* @return Registered listeners.
|
||||
*/
|
||||
public Collection<PrioritizedListener<TListener>> getListener(PacketType type) {
|
||||
return mapListeners.get(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve every listener.
|
||||
* @return Every listener.
|
||||
*/
|
||||
public Iterable<PrioritizedListener<TListener>> values() {
|
||||
return Iterables.concat(mapListeners.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve every registered packet type:
|
||||
* @return Registered packet type.
|
||||
*/
|
||||
public Set<PacketType> keySet() {
|
||||
return mapListeners.keySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all packet listeners.
|
||||
*/
|
||||
protected void clearListeners() {
|
||||
mapListeners.clear();
|
||||
}
|
||||
}
|
||||
-495
@@ -1,495 +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.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.NavigableMap;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import com.comphenix.protocol.compat.guava.Guava;
|
||||
import com.google.common.base.Objects;
|
||||
import com.google.common.collect.Range;
|
||||
|
||||
/**
|
||||
* Represents a generic store of intervals and associated values. No two intervals
|
||||
* can overlap in this representation.
|
||||
* <p>
|
||||
* Note that this implementation is not thread safe.
|
||||
*
|
||||
* @author Kristian
|
||||
*
|
||||
* @param <TKey> - type of the key. Must implement Comparable.
|
||||
* @param <TValue> - type of the value to associate.
|
||||
*/
|
||||
public abstract class AbstractIntervalTree<TKey extends Comparable<TKey>, TValue> {
|
||||
|
||||
protected enum State {
|
||||
OPEN,
|
||||
CLOSE,
|
||||
BOTH
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a range and a value in this interval tree.
|
||||
*/
|
||||
public class Entry implements Map.Entry<Range<TKey>, TValue> {
|
||||
private EndPoint left;
|
||||
private EndPoint right;
|
||||
|
||||
Entry(EndPoint left, EndPoint right) {
|
||||
if (left == null)
|
||||
throw new IllegalAccessError("left cannot be NUll");
|
||||
if (right == null)
|
||||
throw new IllegalAccessError("right cannot be NUll");
|
||||
if (left.key.compareTo(right.key) > 0)
|
||||
throw new IllegalArgumentException(
|
||||
"Left key (" + left.key + ") cannot be greater than the right key (" + right.key + ")");
|
||||
|
||||
this.left = left;
|
||||
this.right = right;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Range<TKey> getKey() {
|
||||
return Guava.closedRange(left.key, right.key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TValue getValue() {
|
||||
return left.value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TValue setValue(TValue value) {
|
||||
TValue old = left.value;
|
||||
|
||||
// Set both end points
|
||||
left.value = value;
|
||||
right.value = value;
|
||||
return old;
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
// Quick equality check
|
||||
if (obj == this) {
|
||||
return true;
|
||||
} else if (obj instanceof AbstractIntervalTree.Entry) {
|
||||
return Objects.equal(left.key, ((AbstractIntervalTree.Entry) obj).left.key) &&
|
||||
Objects.equal(right.key, ((AbstractIntervalTree.Entry) obj).right.key) &&
|
||||
Objects.equal(left.value, ((AbstractIntervalTree.Entry) obj).left.value);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hashCode(left.key, right.key, left.value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("Value %s at [%s, %s]", left.value, left.key, right.key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a single end point (open, close or both) of a range.
|
||||
*/
|
||||
protected class EndPoint {
|
||||
|
||||
// Whether or not the end-point is opening a range, closing a range or both.
|
||||
public State state;
|
||||
|
||||
// The value this range contains
|
||||
public TValue value;
|
||||
|
||||
// The key of this end point
|
||||
public TKey key;
|
||||
|
||||
public EndPoint(State state, TKey key, TValue value) {
|
||||
this.state = state;
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
// To quickly look up ranges we'll index them by endpoints
|
||||
protected NavigableMap<TKey, EndPoint> bounds = new TreeMap<TKey, EndPoint>();
|
||||
|
||||
/**
|
||||
* Removes every interval that intersects with the given range.
|
||||
* @param lowerBound - lowest value to remove.
|
||||
* @param upperBound - highest value to remove.
|
||||
* @return Intervals that were removed
|
||||
*/
|
||||
public Set<Entry> remove(TKey lowerBound, TKey upperBound) {
|
||||
return remove(lowerBound, upperBound, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes every interval that intersects with the given range.
|
||||
* @param lowerBound - lowest value to remove.
|
||||
* @param upperBound - highest value to remove.
|
||||
* @param preserveDifference - whether or not to preserve the intervals that are partially outside.
|
||||
* @return Intervals that were removed
|
||||
*/
|
||||
public Set<Entry> remove(TKey lowerBound, TKey upperBound, boolean preserveDifference) {
|
||||
checkBounds(lowerBound, upperBound);
|
||||
NavigableMap<TKey, EndPoint> range = bounds.subMap(lowerBound, true, upperBound, true);
|
||||
|
||||
EndPoint first = getNextEndPoint(lowerBound, true);
|
||||
EndPoint last = getPreviousEndPoint(upperBound, true);
|
||||
|
||||
// Used while resizing intervals
|
||||
EndPoint previous = null;
|
||||
EndPoint next = null;
|
||||
|
||||
Set<Entry> resized = new HashSet<Entry>();
|
||||
Set<Entry> removed = new HashSet<Entry>();
|
||||
|
||||
// Remove the previous element too. A close end-point must be preceded by an OPEN end-point.
|
||||
if (first != null && first.state == State.CLOSE) {
|
||||
previous = getPreviousEndPoint(first.key, false);
|
||||
|
||||
// Add the interval back
|
||||
if (previous != null) {
|
||||
removed.add(getEntry(previous, first));
|
||||
}
|
||||
}
|
||||
|
||||
// Get the closing element too.
|
||||
if (last != null && last.state == State.OPEN) {
|
||||
next = getNextEndPoint(last.key, false);
|
||||
|
||||
if (next != null) {
|
||||
removed.add(getEntry(last, next));
|
||||
}
|
||||
}
|
||||
|
||||
// Now remove both ranges
|
||||
removeEntrySafely(previous, first);
|
||||
removeEntrySafely(last, next);
|
||||
|
||||
// Add new resized intervals
|
||||
if (preserveDifference) {
|
||||
if (previous != null) {
|
||||
resized.add(putUnsafe(previous.key, decrementKey(lowerBound), previous.value));
|
||||
}
|
||||
if (next != null) {
|
||||
resized.add(putUnsafe(incrementKey(upperBound), next.key, next.value));
|
||||
}
|
||||
}
|
||||
|
||||
// Get the removed entries too
|
||||
getEntries(removed, range);
|
||||
invokeEntryRemoved(removed);
|
||||
|
||||
if (preserveDifference) {
|
||||
invokeEntryAdded(resized);
|
||||
}
|
||||
|
||||
// Remove the range as well
|
||||
range.clear();
|
||||
return removed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the entry from a given set of end points.
|
||||
* @param left - leftmost end point.
|
||||
* @param right - rightmost end point.
|
||||
* @return The associated entry.
|
||||
*/
|
||||
protected Entry getEntry(EndPoint left, EndPoint right) {
|
||||
if (left == null)
|
||||
throw new IllegalArgumentException("left endpoint cannot be NULL.");
|
||||
if (right == null)
|
||||
throw new IllegalArgumentException("right endpoint cannot be NULL.");
|
||||
|
||||
// Make sure the order is correct
|
||||
if (right.key.compareTo(left.key) < 0) {
|
||||
return getEntry(right, left);
|
||||
} else {
|
||||
return new Entry(left, right);
|
||||
}
|
||||
}
|
||||
|
||||
private void removeEntrySafely(EndPoint left, EndPoint right) {
|
||||
if (left != null && right != null) {
|
||||
bounds.remove(left.key);
|
||||
bounds.remove(right.key);
|
||||
}
|
||||
}
|
||||
|
||||
// Adds a given end point
|
||||
protected EndPoint addEndPoint(TKey key, TValue value, State state) {
|
||||
EndPoint endPoint = bounds.get(key);
|
||||
|
||||
if (endPoint != null) {
|
||||
endPoint.state = State.BOTH;
|
||||
} else {
|
||||
endPoint = new EndPoint(state, key, value);
|
||||
bounds.put(key, endPoint);
|
||||
}
|
||||
return endPoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Associates a given interval of keys with a certain value. Any previous
|
||||
* association will be overwritten in the given interval.
|
||||
* <p>
|
||||
* Overlapping intervals are not permitted. A key can only be associated with a single value.
|
||||
*
|
||||
* @param lowerBound - the minimum key (inclusive).
|
||||
* @param upperBound - the maximum key (inclusive).
|
||||
* @param value - the value, or NULL to reset this range.
|
||||
*/
|
||||
public void put(TKey lowerBound, TKey upperBound, TValue value) {
|
||||
// While we don't permit overlapping intervals, we'll still allow overwriting existing intervals.
|
||||
remove(lowerBound, upperBound, true);
|
||||
invokeEntryAdded(putUnsafe(lowerBound, upperBound, value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Associates a given interval without performing any interval checks.
|
||||
* @param lowerBound - the minimum key (inclusive).
|
||||
* @param upperBound - the maximum key (inclusive).
|
||||
* @param value - the value, or NULL to reset the range.
|
||||
*/
|
||||
private Entry putUnsafe(TKey lowerBound, TKey upperBound, TValue value) {
|
||||
// OK. Add the end points now
|
||||
if (value != null) {
|
||||
EndPoint left = addEndPoint(lowerBound, value, State.OPEN);
|
||||
EndPoint right = addEndPoint(upperBound, value, State.CLOSE);
|
||||
|
||||
return new Entry(left, right);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to verify the validity of the given interval.
|
||||
* @param lowerBound - lower bound (inclusive).
|
||||
* @param upperBound - upper bound (inclusive).
|
||||
*/
|
||||
private void checkBounds(TKey lowerBound, TKey upperBound) {
|
||||
if (lowerBound == null)
|
||||
throw new IllegalAccessError("lowerbound cannot be NULL.");
|
||||
if (upperBound == null)
|
||||
throw new IllegalAccessError("upperBound cannot be NULL.");
|
||||
if (upperBound.compareTo(lowerBound) < 0)
|
||||
throw new IllegalArgumentException("upperBound cannot be less than lowerBound.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the given key is within an interval.
|
||||
* @param key - key to check.
|
||||
* @return TRUE if the given key is within an interval in this tree, FALSE otherwise.
|
||||
*/
|
||||
public boolean containsKey(TKey key) {
|
||||
return getEndPoint(key) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enumerates over every range in this interval tree.
|
||||
* @return Number of ranges.
|
||||
*/
|
||||
public Set<Entry> entrySet() {
|
||||
// Don't mind the Java noise
|
||||
Set<Entry> result = new HashSet<Entry>();
|
||||
getEntries(result, bounds);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove every interval.
|
||||
*/
|
||||
public void clear() {
|
||||
if (!bounds.isEmpty()) {
|
||||
remove(bounds.firstKey(), bounds.lastKey());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a map of end points into a set of entries.
|
||||
* @param destination - set of entries.
|
||||
* @param map - a map of end points.
|
||||
*/
|
||||
private void getEntries(Set<Entry> destination, NavigableMap<TKey, EndPoint> map) {
|
||||
Map.Entry<TKey, EndPoint> last = null;
|
||||
|
||||
for (Map.Entry<TKey, EndPoint> entry : map.entrySet()) {
|
||||
switch (entry.getValue().state) {
|
||||
case BOTH:
|
||||
EndPoint point = entry.getValue();
|
||||
destination.add(new Entry(point, point));
|
||||
break;
|
||||
case CLOSE:
|
||||
if (last != null) {
|
||||
destination.add(new Entry(last.getValue(), entry.getValue()));
|
||||
}
|
||||
break;
|
||||
case OPEN:
|
||||
// We don't know the full range yet
|
||||
last = entry;
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Illegal open/close state detected.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts every range from the given tree into the current tree.
|
||||
* @param other - the other tree to read from.
|
||||
*/
|
||||
public void putAll(AbstractIntervalTree<TKey, TValue> other) {
|
||||
// Naively copy every range.
|
||||
for (Entry entry : other.entrySet()) {
|
||||
put(entry.left.key, entry.right.key, entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the value of the range that matches the given key, or NULL if nothing was found.
|
||||
* @param key - the level to read for.
|
||||
* @return The correct amount of experience, or NULL if nothing was recorded.
|
||||
*/
|
||||
public TValue get(TKey key) {
|
||||
EndPoint point = getEndPoint(key);
|
||||
|
||||
if (point != null)
|
||||
return point.value;
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the left-most end-point associated with this key.
|
||||
* @param key - key to search for.
|
||||
* @return The end point found, or NULL.
|
||||
*/
|
||||
protected EndPoint getEndPoint(TKey key) {
|
||||
EndPoint ends = bounds.get(key);
|
||||
|
||||
if (ends != null) {
|
||||
// Always return the end point to the left
|
||||
if (ends.state == State.CLOSE) {
|
||||
Map.Entry<TKey, EndPoint> left = bounds.floorEntry(decrementKey(key));
|
||||
return left != null ? left.getValue() : null;
|
||||
|
||||
} else {
|
||||
return ends;
|
||||
}
|
||||
|
||||
} else {
|
||||
// We need to determine if the point intersects with a range
|
||||
Map.Entry<TKey, EndPoint> left = bounds.floorEntry(key);
|
||||
|
||||
// We only need to check to the left
|
||||
if (left != null && left.getValue().state == State.OPEN) {
|
||||
return left.getValue();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the previous end point of a given key.
|
||||
* @param point - the point to search with.
|
||||
* @param inclusive - whether or not to include the current point in the search.
|
||||
* @return The previous end point of a given given key, or NULL if not found.
|
||||
*/
|
||||
protected EndPoint getPreviousEndPoint(TKey point, boolean inclusive) {
|
||||
if (point != null) {
|
||||
Map.Entry<TKey, EndPoint> previous = bounds.floorEntry(inclusive ? point : decrementKey(point));
|
||||
|
||||
if (previous != null)
|
||||
return previous.getValue();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the next end point of a given key.
|
||||
* @param point - the point to search with.
|
||||
* @param inclusive - whether or not to include the current point in the search.
|
||||
* @return The next end point of a given given key, or NULL if not found.
|
||||
*/
|
||||
protected EndPoint getNextEndPoint(TKey point, boolean inclusive) {
|
||||
if (point != null) {
|
||||
Map.Entry<TKey, EndPoint> next = bounds.ceilingEntry(inclusive ? point : incrementKey(point));
|
||||
|
||||
if (next != null)
|
||||
return next.getValue();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void invokeEntryAdded(Entry added) {
|
||||
if (added != null) {
|
||||
onEntryAdded(added);
|
||||
}
|
||||
}
|
||||
|
||||
private void invokeEntryAdded(Set<Entry> added) {
|
||||
for (Entry entry : added) {
|
||||
onEntryAdded(entry);
|
||||
}
|
||||
}
|
||||
|
||||
private void invokeEntryRemoved(Set<Entry> removed) {
|
||||
for (Entry entry : removed) {
|
||||
onEntryRemoved(entry);
|
||||
}
|
||||
}
|
||||
|
||||
// Listeners for added or removed entries
|
||||
/**
|
||||
* Invoked when an entry is added.
|
||||
* @param added - the entry that was added.
|
||||
*/
|
||||
protected void onEntryAdded(Entry added) { }
|
||||
|
||||
/**
|
||||
* Invoked when an entry is removed.
|
||||
* @param removed - the removed entry.
|
||||
*/
|
||||
protected void onEntryRemoved(Entry removed) { }
|
||||
|
||||
// Helpers for decrementing or incrementing key values
|
||||
/**
|
||||
* Decrement the given key by one unit.
|
||||
* @param key - the key that should be decremented.
|
||||
* @return The new decremented key.
|
||||
*/
|
||||
protected abstract TKey decrementKey(TKey key);
|
||||
|
||||
/**
|
||||
* Increment the given key by one unit.
|
||||
* @param key - the key that should be incremented.
|
||||
* @return The new incremented key.
|
||||
*/
|
||||
protected abstract TKey incrementKey(TKey key);
|
||||
}
|
||||
@@ -1,266 +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.Collection;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import com.comphenix.protocol.utility.SafeCacheBuilder;
|
||||
import com.google.common.cache.CacheLoader;
|
||||
import com.google.common.cache.RemovalCause;
|
||||
import com.google.common.cache.RemovalListener;
|
||||
import com.google.common.cache.RemovalNotification;
|
||||
|
||||
/**
|
||||
* A map that supports blocking on read operations. Null keys are not supported.
|
||||
* <p>
|
||||
* Values are stored as weak references, and will be automatically removed once they've all been dereferenced.
|
||||
* <p>
|
||||
* @author Kristian
|
||||
*
|
||||
* @param <TKey> - type of the key.
|
||||
* @param <TValue> - type of the value.
|
||||
*/
|
||||
public class BlockingHashMap<TKey, TValue> {
|
||||
// Map of values
|
||||
private final ConcurrentMap<TKey, TValue> backingMap;
|
||||
|
||||
// Map of locked objects
|
||||
private final ConcurrentMap<TKey, Object> locks;
|
||||
|
||||
/**
|
||||
* Retrieve a cache loader that will always throw an exception.
|
||||
* @param <TKey> Type of the key
|
||||
* @param <TValue> Type of the value
|
||||
* @return An invalid cache loader.
|
||||
*/
|
||||
public static <TKey, TValue> CacheLoader<TKey, TValue> newInvalidCacheLoader() {
|
||||
return new CacheLoader<TKey, TValue>() {
|
||||
@Override
|
||||
public TValue load(TKey key) throws Exception {
|
||||
throw new IllegalStateException("Illegal use. Access the map directly instead.");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a new map.
|
||||
*/
|
||||
public BlockingHashMap() {
|
||||
backingMap = SafeCacheBuilder.<TKey, TValue>newBuilder().
|
||||
weakValues().
|
||||
removalListener(
|
||||
new RemovalListener<TKey, TValue>() {
|
||||
@Override
|
||||
public void onRemoval(RemovalNotification<TKey, TValue> entry) {
|
||||
// Clean up locks too
|
||||
if (entry.getCause() != RemovalCause.REPLACED) {
|
||||
locks.remove(entry.getKey());
|
||||
}
|
||||
}
|
||||
}).
|
||||
build(BlockingHashMap.<TKey, TValue> newInvalidCacheLoader());
|
||||
|
||||
// Normal concurrent hash map
|
||||
locks = new ConcurrentHashMap<TKey, Object>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a new map.
|
||||
* @param <TKey> Type of the key
|
||||
* @param <TValue> Type of the value
|
||||
* @return The created map.
|
||||
*/
|
||||
public static <TKey, TValue> BlockingHashMap<TKey, TValue> create() {
|
||||
return new BlockingHashMap<TKey, TValue>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits until a value has been associated with the given key, and then retrieves that value.
|
||||
* @param key - the key whose associated value is to be returned
|
||||
* @return The value to which the specified key is mapped.
|
||||
* @throws InterruptedException If the current thread got interrupted while waiting.
|
||||
*/
|
||||
public TValue get(TKey key) throws InterruptedException {
|
||||
if (key == null)
|
||||
throw new IllegalArgumentException("key cannot be NULL.");
|
||||
|
||||
TValue value = backingMap.get(key);
|
||||
|
||||
// Only lock if no value is available
|
||||
if (value == null) {
|
||||
final Object lock = getLock(key);
|
||||
|
||||
synchronized (lock) {
|
||||
while (value == null) {
|
||||
lock.wait();
|
||||
value = backingMap.get(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits until a value has been associated with the given key, and then retrieves that value.
|
||||
* @param key - the key whose associated value is to be returned
|
||||
* @param timeout - the amount of time to wait until an association has been made.
|
||||
* @param unit - unit of timeout.
|
||||
* @return The value to which the specified key is mapped, or NULL if the timeout elapsed.
|
||||
* @throws InterruptedException If the current thread got interrupted while waiting.
|
||||
*/
|
||||
public TValue get(TKey key, long timeout, TimeUnit unit) throws InterruptedException {
|
||||
return get(key, timeout, unit, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits until a value has been associated with the given key, and then retrieves that value.
|
||||
* <p>
|
||||
* If timeout is zero, this method will return immediately if it can't find an socket injector.
|
||||
*
|
||||
* @param key - the key whose associated value is to be returned
|
||||
* @param timeout - the amount of time to wait until an association has been made.
|
||||
* @param unit - unit of timeout.
|
||||
* @param ignoreInterrupted - TRUE if we should ignore the thread being interrupted, FALSE otherwise.
|
||||
* @return The value to which the specified key is mapped, or NULL if the timeout elapsed.
|
||||
* @throws InterruptedException If the current thread got interrupted while waiting.
|
||||
*/
|
||||
public TValue get(TKey key, long timeout, TimeUnit unit, boolean ignoreInterrupted) throws InterruptedException {
|
||||
if (key == null)
|
||||
throw new IllegalArgumentException("key cannot be NULL.");
|
||||
if (unit == null)
|
||||
throw new IllegalArgumentException("Unit cannot be NULL.");
|
||||
if (timeout < 0)
|
||||
throw new IllegalArgumentException("Timeout cannot be less than zero.");
|
||||
|
||||
TValue value = backingMap.get(key);
|
||||
|
||||
// Only lock if no value is available
|
||||
if (value == null && timeout > 0) {
|
||||
final Object lock = getLock(key);
|
||||
final long stopTimeNS = System.nanoTime() + unit.toNanos(timeout);
|
||||
|
||||
// Don't exceed the timeout
|
||||
synchronized (lock) {
|
||||
while (value == null) {
|
||||
try {
|
||||
long remainingTime = stopTimeNS - System.nanoTime();
|
||||
|
||||
if (remainingTime > 0) {
|
||||
TimeUnit.NANOSECONDS.timedWait(lock, remainingTime);
|
||||
value = backingMap.get(key);
|
||||
} else {
|
||||
// Timeout elapsed
|
||||
break;
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
// This is fairly dangerous - but we might HAVE to block the thread
|
||||
if (!ignoreInterrupted)
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Associate a given key with the given value.
|
||||
* <p>
|
||||
* Wakes up any blocking getters on this specific key.
|
||||
*
|
||||
* @param key - the key to associate.
|
||||
* @param value - the value.
|
||||
* @return The previously associated value.
|
||||
*/
|
||||
public TValue put(TKey key, TValue value) {
|
||||
if (value == null)
|
||||
throw new IllegalArgumentException("This map doesn't support NULL values.");
|
||||
|
||||
final TValue previous = backingMap.put(key, value);
|
||||
final Object lock = getLock(key);
|
||||
|
||||
// Inform our readers about this change
|
||||
synchronized (lock) {
|
||||
lock.notifyAll();
|
||||
return previous;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If and only if a key is not present in the map will it be associated with the given value.
|
||||
* @param key - the key to associate.
|
||||
* @param value - the value to associate.
|
||||
* @return The previous value this key has been associated with.
|
||||
*/
|
||||
public TValue putIfAbsent(TKey key, TValue value) {
|
||||
if (value == null)
|
||||
throw new IllegalArgumentException("This map doesn't support NULL values.");
|
||||
|
||||
final TValue previous = backingMap.putIfAbsent(key, value);
|
||||
|
||||
// No need to unlock readers if we haven't changed anything
|
||||
if (previous == null) {
|
||||
final Object lock = getLock(key);
|
||||
|
||||
synchronized (lock) {
|
||||
lock.notifyAll();
|
||||
}
|
||||
}
|
||||
return previous;
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return backingMap.size();
|
||||
}
|
||||
|
||||
public Collection<TValue> values() {
|
||||
return backingMap.values();
|
||||
}
|
||||
|
||||
public Set<TKey> keys() {
|
||||
return backingMap.keySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Atomically retrieve the lock associated with a given key.
|
||||
* @param key - the current key.
|
||||
* @return An asssociated lock.
|
||||
*/
|
||||
private Object getLock(TKey key) {
|
||||
Object lock = locks.get(key);
|
||||
|
||||
if (lock == null) {
|
||||
Object created = new Object();
|
||||
|
||||
// Do this atomically
|
||||
lock = locks.putIfAbsent(key, created);
|
||||
|
||||
// If we succeeded, use the latch we created - otherwise, use the already inserted latch
|
||||
if (lock == null) {
|
||||
lock = created;
|
||||
}
|
||||
}
|
||||
|
||||
return lock;
|
||||
}
|
||||
}
|
||||
-332
@@ -1,332 +0,0 @@
|
||||
package com.comphenix.protocol.concurrency;
|
||||
|
||||
import java.util.AbstractMap;
|
||||
import java.util.AbstractSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import com.comphenix.protocol.utility.SafeCacheBuilder;
|
||||
import com.comphenix.protocol.utility.Util;
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.cache.CacheLoader;
|
||||
import com.google.common.cache.RemovalListener;
|
||||
import com.google.common.cache.RemovalNotification;
|
||||
import com.google.common.collect.AbstractIterator;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.util.concurrent.UncheckedExecutionException;
|
||||
|
||||
/**
|
||||
* Represents a concurrent player map.
|
||||
* <p>
|
||||
* This map may use player addresses as keys.
|
||||
* @author Kristian
|
||||
*/
|
||||
public class ConcurrentPlayerMap<TValue> extends AbstractMap<Player, TValue> implements ConcurrentMap<Player, TValue> {
|
||||
/**
|
||||
* Represents the different standard player keys,
|
||||
* @author Kristian
|
||||
*/
|
||||
public enum PlayerKey implements Function<Player, Object> {
|
||||
/**
|
||||
* Use a player's {@link Player#getAddress()} as key in the map.
|
||||
*/
|
||||
ADDRESS {
|
||||
@Override
|
||||
public Object apply(Player player) {
|
||||
return player.getAddress();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Use a player's {@link Player#getName()} as key in the map.
|
||||
*/
|
||||
NAME {
|
||||
@Override
|
||||
public Object apply(Player player) {
|
||||
return player.getName();
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* An internal map of player keys to values.
|
||||
*/
|
||||
protected ConcurrentMap<Object, TValue> valueLookup = createValueMap();
|
||||
|
||||
/**
|
||||
* A cache of the associated keys for each player.
|
||||
*/
|
||||
|
||||
protected ConcurrentMap<Object, Player> keyLookup = createKeyCache();
|
||||
/**
|
||||
* The method used to retrieve a unique key for a player.
|
||||
*/
|
||||
protected final Function<Player, Object> keyMethod;
|
||||
|
||||
/**
|
||||
* Construct a new concurrent player map that uses each player's address as key.
|
||||
* @param <T> Parameter type
|
||||
* @return Concurrent player map.
|
||||
*/
|
||||
public static <T> ConcurrentPlayerMap<T> usingAddress() {
|
||||
return new ConcurrentPlayerMap<T>(PlayerKey.ADDRESS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new concurrent player map that uses each player's name as key.
|
||||
* @param <T> Parameter type
|
||||
* @return Concurrent player map.
|
||||
*/
|
||||
public static <T> ConcurrentPlayerMap<T> usingName() {
|
||||
return new ConcurrentPlayerMap<T>(PlayerKey.NAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new concurrent player map using the given standard key method.
|
||||
* @param standardMethod - the standard key method.
|
||||
*/
|
||||
public ConcurrentPlayerMap(PlayerKey standardMethod) {
|
||||
this.keyMethod = standardMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new concurrent player map using the given custom key method.
|
||||
* @param method - custom key method.
|
||||
*/
|
||||
public ConcurrentPlayerMap(Function<Player, Object> method) {
|
||||
this.keyMethod = method;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct the map that will store the associated values.
|
||||
* <p>
|
||||
* The default implementation uses a {@link ConcurrentHashMap}.
|
||||
* @return The value map.
|
||||
*/
|
||||
protected ConcurrentMap<Object, TValue> createValueMap() {
|
||||
return Maps.newConcurrentMap();
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a cache of keys and the associated player.
|
||||
* @return The key map.
|
||||
*/
|
||||
protected ConcurrentMap<Object, Player> createKeyCache() {
|
||||
return SafeCacheBuilder.newBuilder().
|
||||
weakValues().
|
||||
removalListener(
|
||||
new RemovalListener<Object, Player>() {
|
||||
@Override
|
||||
public void onRemoval(RemovalNotification<Object, Player> removed) {
|
||||
// We ignore explicit removal
|
||||
if (removed.wasEvicted()) {
|
||||
onCacheEvicted(removed.getKey());
|
||||
}
|
||||
}
|
||||
}).
|
||||
build(
|
||||
new CacheLoader<Object, Player>() {
|
||||
@Override
|
||||
public Player load(Object key) throws Exception {
|
||||
Player player = findOnlinePlayer(key);
|
||||
|
||||
if (player != null)
|
||||
return player;
|
||||
|
||||
// Per the contract, this method should not return NULL
|
||||
throw new IllegalArgumentException(
|
||||
"Unable to find a player associated with: " + key);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when an entry in the cache has been evicted, typically by the garbage collector.
|
||||
* @param key - the key.
|
||||
* @param player - the value that was evicted or collected.
|
||||
*/
|
||||
private void onCacheEvicted(Object key) {
|
||||
Player newPlayer = findOnlinePlayer(key);
|
||||
|
||||
if (newPlayer != null) {
|
||||
// Update the reference
|
||||
keyLookup.put(key, newPlayer);
|
||||
} else {
|
||||
valueLookup.remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find an online player from the given key.
|
||||
* @param key - a non-null key.
|
||||
* @return The player with the given key, or NULL if not found.
|
||||
*/
|
||||
protected Player findOnlinePlayer(Object key) {
|
||||
for (Player player : Util.getOnlinePlayers()) {
|
||||
if (key.equals(keyMethod.apply(player))) {
|
||||
return player;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup a player by key in the cache, optionally searching every online player.
|
||||
* @param key - the key of the player we are locating.
|
||||
* @return The player, or NULL if not found.
|
||||
*/
|
||||
protected Player lookupPlayer(Object key) {
|
||||
try {
|
||||
return keyLookup.get(key);
|
||||
} catch (UncheckedExecutionException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the key of a particular player, ensuring it is cached.
|
||||
* @param player - the player whose key we want to retrieve.
|
||||
* @return The key.
|
||||
*/
|
||||
protected Object cachePlayerKey(Player player) {
|
||||
Object key = keyMethod.apply(player);
|
||||
|
||||
keyLookup.put(key, player);
|
||||
return key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TValue put(Player key, TValue value) {
|
||||
return valueLookup.put(cachePlayerKey(key), value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TValue putIfAbsent(Player key, TValue value) {
|
||||
return valueLookup.putIfAbsent(cachePlayerKey(key), value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TValue replace(Player key, TValue value) {
|
||||
return valueLookup.replace(cachePlayerKey(key), value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean replace(Player key, TValue oldValue, TValue newValue) {
|
||||
return valueLookup.replace(cachePlayerKey(key), oldValue, newValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TValue remove(Object key) {
|
||||
if (key instanceof Player) {
|
||||
Object playerKey = keyMethod.apply((Player) key);
|
||||
|
||||
if (playerKey != null) {
|
||||
TValue value = valueLookup.remove(playerKey);
|
||||
|
||||
keyLookup.remove(playerKey);
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(Object key, Object value) {
|
||||
if (key instanceof Player) {
|
||||
Object playerKey = keyMethod.apply((Player) key);
|
||||
|
||||
if (playerKey != null && valueLookup.remove(playerKey, value)) {
|
||||
keyLookup.remove(playerKey);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TValue get(Object key) {
|
||||
if (key instanceof Player) {
|
||||
Object playerKey = keyMethod.apply((Player) key);
|
||||
return playerKey != null ? valueLookup.get(playerKey) : null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsKey(Object key) {
|
||||
if (key instanceof Player) {
|
||||
Object playerKey = keyMethod.apply((Player) key);
|
||||
return playerKey != null && valueLookup.containsKey(playerKey);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Entry<Player, TValue>> entrySet() {
|
||||
return new AbstractSet<Entry<Player,TValue>>() {
|
||||
@Override
|
||||
public Iterator<Entry<Player, TValue>> iterator() {
|
||||
return entryIterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return valueLookup.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
valueLookup.clear();
|
||||
keyLookup.clear();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve an iterator of entries that supports removal of elements.
|
||||
* @return Entry iterator.
|
||||
*/
|
||||
private Iterator<Entry<Player, TValue>> entryIterator() {
|
||||
// Skip entries with stale data
|
||||
final Iterator<Entry<Object, TValue>> source = valueLookup.entrySet().iterator();
|
||||
final AbstractIterator<Entry<Player,TValue>> filtered =
|
||||
new AbstractIterator<Entry<Player,TValue>>() {
|
||||
@Override
|
||||
protected Entry<Player, TValue> computeNext() {
|
||||
while (source.hasNext()) {
|
||||
Entry<Object, TValue> entry = source.next();
|
||||
Player player = lookupPlayer(entry.getKey());
|
||||
|
||||
if (player == null) {
|
||||
// Remove entries that cannot be found
|
||||
source.remove();
|
||||
keyLookup.remove(entry.getKey());
|
||||
} else {
|
||||
return new SimpleEntry<Player, TValue>(player, entry.getValue());
|
||||
}
|
||||
}
|
||||
return endOfData();
|
||||
}
|
||||
};
|
||||
|
||||
// We can't return AbstractIterator directly, as it doesn't permitt the remove() method
|
||||
return new Iterator<Entry<Player, TValue>>() {
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return filtered.hasNext();
|
||||
}
|
||||
@Override
|
||||
public Entry<Player, TValue> next() {
|
||||
return filtered.next();
|
||||
}
|
||||
@Override
|
||||
public void remove() {
|
||||
source.remove();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,116 +0,0 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol.concurrency;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Represents a very quick integer set that uses a lookup table to store membership.
|
||||
* <p>
|
||||
* This class is intentionally missing a size method.
|
||||
* @author Kristian
|
||||
*/
|
||||
public class IntegerSet {
|
||||
private final boolean[] array;
|
||||
|
||||
/**
|
||||
* Initialize a lookup table with the given maximum number of elements.
|
||||
* <p>
|
||||
* This creates a set for elements in the range [0, count).
|
||||
* <p>
|
||||
* Formally, the current set must be a subset of [0, 1, 2, ..., count - 1].
|
||||
* @param maximumCount - maximum element value and count.
|
||||
*/
|
||||
public IntegerSet(int maximumCount) {
|
||||
this.array = new boolean[maximumCount];
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a lookup table with a given maximum and value list.
|
||||
* <p>
|
||||
* The provided elements must be in the range [0, count).
|
||||
* @param maximumCount - the maximum element value and count.
|
||||
* @param values - the elements to add to the set.
|
||||
*/
|
||||
public IntegerSet(int maximumCount, Collection<Integer> values) {
|
||||
this.array = new boolean[maximumCount];
|
||||
addAll(values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether or not the given element exists in the set.
|
||||
* @param element - the element to check. Must be in the range [0, count).
|
||||
* @return TRUE if the given element exists, FALSE otherwise.
|
||||
*/
|
||||
public boolean contains(int element) {
|
||||
return array[element];
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the given element to the set, or do nothing if it already exists.
|
||||
* @param element - element to add.
|
||||
* @throws ArrayIndexOutOfBoundsException If the given element is not in the range [0, count).
|
||||
*/
|
||||
public void add(int element) {
|
||||
array[element] = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the given collection of elements to the set.
|
||||
* @param packets - elements to add.
|
||||
*/
|
||||
public void addAll(Collection<Integer> packets) {
|
||||
for (Integer id : packets) {
|
||||
add(id);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the given element from the set, or do nothing if it's already removed.
|
||||
* @param element - element to remove.
|
||||
*/
|
||||
public void remove(int element) {
|
||||
// We don't actually care if the caller tries to remove an element outside the valid set
|
||||
if (element >= 0 && element < array.length)
|
||||
array[element] = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove every element from the set.
|
||||
*/
|
||||
public void clear() {
|
||||
Arrays.fill(array, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the current IntegerSet to an equivalent HashSet.
|
||||
* @return The resulting HashSet.
|
||||
*/
|
||||
public Set<Integer> toSet() {
|
||||
Set<Integer> elements = new HashSet<Integer>();
|
||||
|
||||
for (int i = 0; i < array.length; i++) {
|
||||
if (array[i])
|
||||
elements.add(i);
|
||||
}
|
||||
return elements;
|
||||
}
|
||||
}
|
||||
@@ -1,134 +0,0 @@
|
||||
package com.comphenix.protocol.concurrency;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.injector.packet.PacketRegistry;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
/**
|
||||
* Represents a concurrent set of packet types.
|
||||
* @author Kristian
|
||||
*/
|
||||
public class PacketTypeSet {
|
||||
private Set<PacketType> types = Collections.newSetFromMap(Maps.<PacketType, Boolean>newConcurrentMap());
|
||||
private Set<Class<?>> classes = Collections.newSetFromMap(Maps.<Class<?>, Boolean>newConcurrentMap());
|
||||
|
||||
public PacketTypeSet() {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
public PacketTypeSet(Collection<? extends PacketType> values) {
|
||||
for (PacketType type : values) {
|
||||
addType(type);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a particular type to the set.
|
||||
* @param type - the type to add.
|
||||
*/
|
||||
public synchronized void addType(PacketType type) {
|
||||
Class<?> packetClass = getPacketClass(type);
|
||||
types.add(Preconditions.checkNotNull(type, "type cannot be NULL."));
|
||||
|
||||
if (packetClass != null) {
|
||||
classes.add(getPacketClass(type));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the given types to the set of packet types.
|
||||
* @param types - the types to add.
|
||||
*/
|
||||
public synchronized void addAll(Iterable<? extends PacketType> types) {
|
||||
for (PacketType type : types) {
|
||||
addType(type);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a particular type to the set.
|
||||
* @param type - the type to remove.
|
||||
*/
|
||||
public synchronized void removeType(PacketType type) {
|
||||
Class<?> packetClass = getPacketClass(type);
|
||||
types.remove(Preconditions.checkNotNull(type, "type cannot be NULL."));
|
||||
|
||||
if (packetClass != null) {
|
||||
classes.remove(getPacketClass(type));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the given types from the set.
|
||||
* @param types Types to remove
|
||||
*/
|
||||
public synchronized void removeAll(Iterable<? extends PacketType> types) {
|
||||
for (PacketType type : types) {
|
||||
removeType(type);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the packet class associated with a particular type.
|
||||
* @param type - the packet type.
|
||||
* @return The associated packet type.
|
||||
*/
|
||||
protected Class<?> getPacketClass(PacketType type) {
|
||||
return PacketRegistry.getPacketClassFromType(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the given packet type exists in the set.
|
||||
* @param type - the type to find.
|
||||
* @return TRUE if it does, FALSE otherwise.
|
||||
*/
|
||||
public boolean contains(PacketType type) {
|
||||
return types.contains(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a packet type with the given packet class exists in the set.
|
||||
* @param packetClass - the class to find.
|
||||
* @return TRUE if it does, FALSE otherwise.
|
||||
*/
|
||||
public boolean contains(Class<?> packetClass) {
|
||||
return classes.contains(packetClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the type of a packet is in the current set.
|
||||
* @param packet - the packet.
|
||||
* @return TRUE if it is, FALSE otherwise.
|
||||
*/
|
||||
public boolean containsPacket(Object packet) {
|
||||
if (packet == null)
|
||||
return false;
|
||||
return classes.contains(packet.getClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a view of this packet type set.
|
||||
* @return The packet type values.
|
||||
*/
|
||||
public Set<PacketType> values() {
|
||||
return types;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the number of entries in the set.
|
||||
* @return The number of entries.
|
||||
*/
|
||||
public int size() {
|
||||
return types.size();
|
||||
}
|
||||
|
||||
public synchronized void clear() {
|
||||
types.clear();
|
||||
classes.clear();
|
||||
}
|
||||
}
|
||||
-244
@@ -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,107 +0,0 @@
|
||||
package com.comphenix.protocol.error;
|
||||
|
||||
import java.io.PrintStream;
|
||||
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import com.comphenix.protocol.error.Report.ReportBuilder;
|
||||
import com.comphenix.protocol.reflect.PrettyPrinter;
|
||||
|
||||
/**
|
||||
* Represents a basic error reporter that prints error reports to the standard error stream.
|
||||
* <p>
|
||||
* Note that this implementation doesn't distinguish between {@link #reportWarning(Object, Report)}
|
||||
* and {@link #reportDetailed(Object, Report)} - they both have the exact same behavior.
|
||||
* @author Kristian
|
||||
*/
|
||||
public class BasicErrorReporter implements ErrorReporter {
|
||||
private final PrintStream output;
|
||||
|
||||
/**
|
||||
* Construct a new basic error reporter that prints directly the standard error stream.
|
||||
*/
|
||||
public BasicErrorReporter() {
|
||||
this(System.err);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a error reporter that prints to the given output stream.
|
||||
* @param output - the output stream.
|
||||
*/
|
||||
public BasicErrorReporter(PrintStream output) {
|
||||
this.output = output;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportMinimal(Plugin sender, String methodName, Throwable error) {
|
||||
output.println("Unhandled exception occured in " + methodName + " for " + sender.getName());
|
||||
error.printStackTrace(output);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportMinimal(Plugin sender, String methodName, Throwable error, Object... parameters) {
|
||||
reportMinimal(sender, methodName, error);
|
||||
|
||||
// Also print parameters
|
||||
printParameters(parameters);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportDebug(Object sender, Report report) {
|
||||
// We just have to swallow it
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportDebug(Object sender, ReportBuilder builder) {
|
||||
// As above
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportWarning(Object sender, Report report) {
|
||||
// Basic warning
|
||||
output.println("[" + sender.getClass().getSimpleName() + "] " + report.getReportMessage());
|
||||
|
||||
if (report.getException() != null) {
|
||||
report.getException().printStackTrace(output);
|
||||
}
|
||||
printParameters(report.getCallerParameters());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportWarning(Object sender, ReportBuilder reportBuilder) {
|
||||
reportWarning(sender, reportBuilder.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportDetailed(Object sender, Report report) {
|
||||
// No difference from warning
|
||||
reportWarning(sender, report);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportDetailed(Object sender, ReportBuilder reportBuilder) {
|
||||
reportWarning(sender, reportBuilder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Print the given parameters to the standard error stream.
|
||||
* @param parameters - the output parameters.
|
||||
*/
|
||||
private void printParameters(Object[] parameters) {
|
||||
if (parameters != null && parameters.length > 0) {
|
||||
output.println("Parameters: ");
|
||||
|
||||
try {
|
||||
for (Object parameter : parameters) {
|
||||
if (parameter == null)
|
||||
output.println("[NULL]");
|
||||
else
|
||||
output.println(PrettyPrinter.printObject(parameter));
|
||||
}
|
||||
} catch (IllegalAccessException e) {
|
||||
// Damn it
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
package com.comphenix.protocol.error;
|
||||
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import com.comphenix.protocol.error.Report.ReportBuilder;
|
||||
|
||||
/**
|
||||
* Construct an error reporter that delegates to another error reporter.
|
||||
* @author Kristian
|
||||
*/
|
||||
public class DelegatedErrorReporter implements ErrorReporter {
|
||||
private final ErrorReporter delegated;
|
||||
|
||||
/**
|
||||
* Construct a new error reporter that forwards all reports to a given reporter.
|
||||
* @param delegated - the delegated reporter.
|
||||
*/
|
||||
public DelegatedErrorReporter(ErrorReporter delegated) {
|
||||
this.delegated = delegated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the underlying error reporter.
|
||||
* @return Underlying error reporter.
|
||||
*/
|
||||
public ErrorReporter getDelegated() {
|
||||
return delegated;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportMinimal(Plugin sender, String methodName, Throwable error) {
|
||||
delegated.reportMinimal(sender, methodName, error);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportMinimal(Plugin sender, String methodName, Throwable error, Object... parameters) {
|
||||
delegated.reportMinimal(sender, methodName, error, parameters);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportDebug(Object sender, Report report) {
|
||||
Report transformed = filterReport(sender, report, false);
|
||||
|
||||
if (transformed != null) {
|
||||
delegated.reportDebug(sender, transformed);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportWarning(Object sender, Report report) {
|
||||
Report transformed = filterReport(sender, report, false);
|
||||
|
||||
if (transformed != null) {
|
||||
delegated.reportWarning(sender, transformed);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportDetailed(Object sender, Report report) {
|
||||
Report transformed = filterReport(sender, report, true);
|
||||
|
||||
if (transformed != null) {
|
||||
delegated.reportDetailed(sender, transformed);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked before an error report is passed on to the underlying error reporter.
|
||||
* <p>
|
||||
* To cancel a report, return NULL.
|
||||
* @param sender - the sender instance or class.
|
||||
* @param report - the error report.
|
||||
* @param detailed - whether or not the report will be displayed in detail.
|
||||
* @return The report to pass on, or NULL to cancel it.
|
||||
*/
|
||||
protected Report filterReport(Object sender, Report report, boolean detailed) {
|
||||
return report;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportWarning(Object sender, ReportBuilder reportBuilder) {
|
||||
reportWarning(sender, reportBuilder.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportDetailed(Object sender, ReportBuilder reportBuilder) {
|
||||
reportDetailed(sender, reportBuilder.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportDebug(Object sender, ReportBuilder builder) {
|
||||
reportDebug(sender, builder.build());
|
||||
}
|
||||
}
|
||||
@@ -1,616 +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.error;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.apache.commons.lang.builder.ToStringBuilder;
|
||||
import org.apache.commons.lang.builder.ToStringStyle;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import com.comphenix.protocol.ProtocolLibrary;
|
||||
import com.comphenix.protocol.collections.ExpireHashMap;
|
||||
import com.comphenix.protocol.error.Report.ReportBuilder;
|
||||
import com.comphenix.protocol.events.PacketAdapter;
|
||||
import com.comphenix.protocol.reflect.PrettyPrinter;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.primitives.Primitives;
|
||||
|
||||
/**
|
||||
* Internal class used to handle exceptions.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public class DetailedErrorReporter implements ErrorReporter {
|
||||
/**
|
||||
* Report format for printing the current exception count.
|
||||
*/
|
||||
public static final ReportType REPORT_EXCEPTION_COUNT = new ReportType("Internal exception count: %s!");
|
||||
|
||||
public static final String SECOND_LEVEL_PREFIX = " ";
|
||||
public static final String DEFAULT_PREFIX = " ";
|
||||
public static final String DEFAULT_SUPPORT_URL = "https://github.com/dmulloy2/ProtocolLib/issues";
|
||||
|
||||
// Users that are informed about errors in the chat
|
||||
public static final String ERROR_PERMISSION = "protocol.info";
|
||||
|
||||
// We don't want to spam the server
|
||||
public static final int DEFAULT_MAX_ERROR_COUNT = 20;
|
||||
|
||||
// Prevent spam per plugin too
|
||||
private ConcurrentMap<String, AtomicInteger> warningCount = new ConcurrentHashMap<String, AtomicInteger>();
|
||||
|
||||
protected String prefix;
|
||||
protected String supportURL;
|
||||
|
||||
protected AtomicInteger internalErrorCount = new AtomicInteger();
|
||||
|
||||
protected int maxErrorCount;
|
||||
protected Logger logger;
|
||||
|
||||
protected WeakReference<Plugin> pluginReference;
|
||||
protected String pluginName;
|
||||
|
||||
// Whether or not Apache Commons is not present
|
||||
protected static boolean apacheCommonsMissing;
|
||||
|
||||
// Whether or not detailed errror reporting is enabled
|
||||
protected boolean detailedReporting;
|
||||
|
||||
// Map of global objects
|
||||
protected Map<String, Object> globalParameters = new HashMap<String, Object>();
|
||||
|
||||
// Reports to ignore
|
||||
private ExpireHashMap<Report, Boolean> rateLimited = new ExpireHashMap<Report, Boolean>();
|
||||
private Object rateLock = new Object();
|
||||
|
||||
/**
|
||||
* Create a default error reporting system.
|
||||
* @param plugin - the plugin owner.
|
||||
*/
|
||||
public DetailedErrorReporter(Plugin plugin) {
|
||||
this(plugin, DEFAULT_PREFIX, DEFAULT_SUPPORT_URL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a central error reporting system.
|
||||
* @param plugin - the plugin owner.
|
||||
* @param prefix - default line prefix.
|
||||
* @param supportURL - URL to report the error.
|
||||
*/
|
||||
public DetailedErrorReporter(Plugin plugin, String prefix, String supportURL) {
|
||||
this(plugin, prefix, supportURL, DEFAULT_MAX_ERROR_COUNT, getBukkitLogger());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a central error reporting system.
|
||||
* @param plugin - the plugin owner.
|
||||
* @param prefix - default line prefix.
|
||||
* @param supportURL - URL to report the error.
|
||||
* @param maxErrorCount - number of errors to print before giving up.
|
||||
* @param logger - current logger.
|
||||
*/
|
||||
public DetailedErrorReporter(Plugin plugin, String prefix, String supportURL, int maxErrorCount, Logger logger) {
|
||||
if (plugin == null)
|
||||
throw new IllegalArgumentException("Plugin cannot be NULL.");
|
||||
|
||||
this.pluginReference = new WeakReference<Plugin>(plugin);
|
||||
this.pluginName = getNameSafely(plugin);
|
||||
this.prefix = prefix;
|
||||
this.supportURL = supportURL;
|
||||
this.maxErrorCount = maxErrorCount;
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
private String getNameSafely(Plugin plugin) {
|
||||
try {
|
||||
return plugin.getName();
|
||||
} catch (LinkageError e) {
|
||||
return "ProtocolLib";
|
||||
}
|
||||
}
|
||||
|
||||
// Attempt to get the logger.
|
||||
private static Logger getBukkitLogger() {
|
||||
try {
|
||||
return Bukkit.getLogger();
|
||||
} catch (LinkageError e) {
|
||||
return Logger.getLogger("Minecraft");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if we're using detailed error reporting.
|
||||
* @return TRUE if we are, FALSE otherwise.
|
||||
*/
|
||||
public boolean isDetailedReporting() {
|
||||
return detailedReporting;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether or not to use detailed error reporting.
|
||||
* @param detailedReporting - TRUE to enable it, FALSE otherwise.
|
||||
*/
|
||||
public void setDetailedReporting(boolean detailedReporting) {
|
||||
this.detailedReporting = detailedReporting;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportMinimal(Plugin sender, String methodName, Throwable error, Object... parameters) {
|
||||
if (reportMinimalNoSpam(sender, methodName, error)) {
|
||||
// Print parameters, if they are given
|
||||
if (parameters != null && parameters.length > 0) {
|
||||
logger.log(Level.SEVERE, printParameters(parameters));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportMinimal(Plugin sender, String methodName, Throwable error) {
|
||||
reportMinimalNoSpam(sender, methodName, error);
|
||||
}
|
||||
|
||||
/**
|
||||
* Report a problem with a given method and plugin, ensuring that we don't exceed the maximum number of error reports.
|
||||
* @param sender - the component that observed this exception.
|
||||
* @param methodName - the method name.
|
||||
* @param error - the error itself.
|
||||
* @return TRUE if the error was printed, FALSE if it was suppressed.
|
||||
*/
|
||||
public boolean reportMinimalNoSpam(Plugin sender, String methodName, Throwable error) {
|
||||
String pluginName = PacketAdapter.getPluginName(sender);
|
||||
AtomicInteger counter = warningCount.get(pluginName);
|
||||
|
||||
// Thread safe pattern
|
||||
if (counter == null) {
|
||||
AtomicInteger created = new AtomicInteger();
|
||||
counter = warningCount.putIfAbsent(pluginName, created);
|
||||
|
||||
if (counter == null) {
|
||||
counter = created;
|
||||
}
|
||||
}
|
||||
|
||||
final int errorCount = counter.incrementAndGet();
|
||||
|
||||
// See if we should print the full error
|
||||
if (errorCount < getMaxErrorCount()) {
|
||||
logger.log(Level.SEVERE, "[" + pluginName + "] Unhandled exception occured in " +
|
||||
methodName + " for " + pluginName, error);
|
||||
return true;
|
||||
|
||||
} else {
|
||||
// Nope - only print the error count occationally
|
||||
if (isPowerOfTwo(errorCount)) {
|
||||
logger.log(Level.SEVERE, "[" + pluginName + "] Unhandled exception number " + errorCount + " occured in " +
|
||||
methodName + " for " + pluginName, error);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a given number is a power of two.
|
||||
* <p>
|
||||
* That is, if there exists an N such that 2^N = number.
|
||||
* @param number - the number to check.
|
||||
* @return TRUE if the given number is a power of two, FALSE otherwise.
|
||||
*/
|
||||
private boolean isPowerOfTwo(int number) {
|
||||
return (number & (number - 1)) == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportDebug(Object sender, ReportBuilder builder) {
|
||||
reportDebug(sender, Preconditions.checkNotNull(builder, "builder cannot be NULL").build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportDebug(Object sender, Report report) {
|
||||
if (logger.isLoggable(Level.FINE) && canReport(report)) {
|
||||
reportLevel(Level.FINE, sender, report);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportWarning(Object sender, ReportBuilder reportBuilder) {
|
||||
if (reportBuilder == null)
|
||||
throw new IllegalArgumentException("reportBuilder cannot be NULL.");
|
||||
|
||||
reportWarning(sender, reportBuilder.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportWarning(Object sender, Report report) {
|
||||
if (logger.isLoggable(Level.WARNING) && canReport(report)) {
|
||||
reportLevel(Level.WARNING, sender, report);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if we should print the given report.
|
||||
* <p>
|
||||
* The default implementation will check for rate limits.
|
||||
* @param report - the report to check.
|
||||
* @return TRUE if we should print it, FALSE otherwise.
|
||||
*/
|
||||
protected boolean canReport(Report report) {
|
||||
long rateLimit = report.getRateLimit();
|
||||
|
||||
// Check for rate limit
|
||||
if (rateLimit > 0) {
|
||||
synchronized (rateLock) {
|
||||
if (rateLimited.containsKey(report)) {
|
||||
return false;
|
||||
}
|
||||
rateLimited.put(report, true, rateLimit, TimeUnit.NANOSECONDS);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void reportLevel(Level level, Object sender, Report report) {
|
||||
String message = "[" + pluginName + "] [" + getSenderName(sender) + "] " + report.getReportMessage();
|
||||
|
||||
// Print the main warning
|
||||
if (report.getException() != null) {
|
||||
logger.log(level, message, report.getException());
|
||||
} else {
|
||||
logger.log(level, message);
|
||||
|
||||
// Remember the call stack
|
||||
if (detailedReporting) {
|
||||
printCallStack(level, logger);
|
||||
}
|
||||
}
|
||||
|
||||
// Parameters?
|
||||
if (report.hasCallerParameters()) {
|
||||
// Write it
|
||||
logger.log(level, printParameters(report.getCallerParameters()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the name of a sender class.
|
||||
* @param sender - sender object.
|
||||
* @return The name of the sender's class.
|
||||
*/
|
||||
private String getSenderName(Object sender) {
|
||||
if (sender != null)
|
||||
return ReportType.getSenderClass(sender).getSimpleName();
|
||||
else
|
||||
return "NULL";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportDetailed(Object sender, ReportBuilder reportBuilder) {
|
||||
reportDetailed(sender, reportBuilder.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportDetailed(Object sender, Report report) {
|
||||
final Plugin plugin = pluginReference.get();
|
||||
final int errorCount = internalErrorCount.incrementAndGet();
|
||||
|
||||
// Do not overtly spam the server!
|
||||
if (errorCount > getMaxErrorCount()) {
|
||||
// Only allow the error count at rare occations
|
||||
if (isPowerOfTwo(errorCount)) {
|
||||
// Permit it - but print the number of exceptions first
|
||||
reportWarning(this, Report.newBuilder(REPORT_EXCEPTION_COUNT).messageParam(errorCount).build());
|
||||
} else {
|
||||
// NEVER SPAM THE CONSOLE
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Secondary rate limit
|
||||
if (!canReport(report)) {
|
||||
return;
|
||||
}
|
||||
|
||||
StringWriter text = new StringWriter();
|
||||
PrintWriter writer = new PrintWriter(text);
|
||||
|
||||
// Helpful message
|
||||
writer.println("[" + pluginName + "] INTERNAL ERROR: " + report.getReportMessage());
|
||||
writer.println("If this problem hasn't already been reported, please open a ticket");
|
||||
writer.println("at " + supportURL + " with the following data:");
|
||||
|
||||
// Now, let us print important exception information
|
||||
writer.println("Stack Trace:");
|
||||
|
||||
if (report.getException() != null) {
|
||||
report.getException().printStackTrace(writer);
|
||||
|
||||
} else if (detailedReporting) {
|
||||
printCallStack(writer);
|
||||
}
|
||||
|
||||
// Data dump!
|
||||
writer.println("Dump:");
|
||||
|
||||
// Relevant parameters
|
||||
if (report.hasCallerParameters()) {
|
||||
printParameters(writer, report.getCallerParameters());
|
||||
}
|
||||
|
||||
// Global parameters
|
||||
for (String param : globalParameters()) {
|
||||
writer.println(SECOND_LEVEL_PREFIX + param + ":");
|
||||
writer.println(addPrefix(getStringDescription(getGlobalParameter(param)),
|
||||
SECOND_LEVEL_PREFIX + SECOND_LEVEL_PREFIX));
|
||||
}
|
||||
|
||||
// Now, for the sender itself
|
||||
writer.println("Sender:");
|
||||
writer.println(addPrefix(getStringDescription(sender), SECOND_LEVEL_PREFIX));
|
||||
|
||||
// And plugin
|
||||
if (plugin != null) {
|
||||
writer.println("Version:");
|
||||
writer.println(addPrefix(plugin.toString(), SECOND_LEVEL_PREFIX));
|
||||
}
|
||||
|
||||
// And java version
|
||||
writer.println("Java Version:");
|
||||
writer.println(addPrefix(System.getProperty("java.version"), SECOND_LEVEL_PREFIX));
|
||||
|
||||
// Add the server version too
|
||||
if (Bukkit.getServer() != null) {
|
||||
writer.println("Server:");
|
||||
writer.println(addPrefix(Bukkit.getServer().getVersion(), SECOND_LEVEL_PREFIX));
|
||||
|
||||
// Inform of this occurrence
|
||||
if (ERROR_PERMISSION != null) {
|
||||
Bukkit.getServer().broadcast(
|
||||
String.format("Error %s (%s) occured in %s.", report.getReportMessage(), report.getException(), sender),
|
||||
ERROR_PERMISSION
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure it is reported
|
||||
logger.severe(addPrefix(text.toString(), prefix));
|
||||
}
|
||||
|
||||
/**
|
||||
* Print the call stack to the given logger.
|
||||
* @param logger - the logger.
|
||||
*/
|
||||
private void printCallStack(Level level, Logger logger) {
|
||||
StringWriter text = new StringWriter();
|
||||
printCallStack(new PrintWriter(text));
|
||||
|
||||
// Print the exception
|
||||
logger.log(level, text.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Print the current call stack.
|
||||
* @param writer - the writer.
|
||||
*/
|
||||
private void printCallStack(PrintWriter writer) {
|
||||
Exception current = new Exception("Not an error! This is the call stack.");
|
||||
current.printStackTrace(writer);
|
||||
}
|
||||
|
||||
private String printParameters(Object... parameters) {
|
||||
StringWriter writer = new StringWriter();
|
||||
|
||||
// Print and retrieve the string buffer
|
||||
printParameters(new PrintWriter(writer), parameters);
|
||||
return writer.toString();
|
||||
}
|
||||
|
||||
private void printParameters(PrintWriter writer, Object[] parameters) {
|
||||
writer.println("Parameters: ");
|
||||
|
||||
// We *really* want to get as much information as possible
|
||||
for (Object param : parameters) {
|
||||
writer.println(addPrefix(getStringDescription(param), SECOND_LEVEL_PREFIX));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given prefix to every line in the text.
|
||||
* @param text - text to modify.
|
||||
* @param prefix - prefix added to every line in the text.
|
||||
* @return The modified text.
|
||||
*/
|
||||
protected String addPrefix(String text, String prefix) {
|
||||
return text.replaceAll("(?m)^", prefix);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a string representation of the given object.
|
||||
* @param value - object to convert.
|
||||
* @return String representation.
|
||||
*/
|
||||
public static String getStringDescription(Object value) {
|
||||
// We can't only rely on toString.
|
||||
if (value == null) {
|
||||
return "[NULL]";
|
||||
} if (isSimpleType(value) || value instanceof Class<?>) {
|
||||
return value.toString();
|
||||
} else {
|
||||
try {
|
||||
if (!apacheCommonsMissing)
|
||||
return (ToStringBuilder.reflectionToString(value, ToStringStyle.MULTI_LINE_STYLE, false, null));
|
||||
} catch (LinkageError ex) {
|
||||
// Apache is probably missing
|
||||
apacheCommonsMissing = true;
|
||||
} catch (Exception e) {
|
||||
// Don't use the error logger to log errors in error logging (that could lead to infinite loops)
|
||||
ProtocolLibrary.log(Level.WARNING, "Cannot convert to a String with Apache: " + e.getMessage());
|
||||
}
|
||||
|
||||
// Use our custom object printer instead
|
||||
try {
|
||||
return PrettyPrinter.printObject(value, value.getClass(), Object.class);
|
||||
} catch (IllegalAccessException e) {
|
||||
return "[Error: " + e.getMessage() + "]";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the given object is a wrapper for a primitive/simple type or not.
|
||||
* @param test - the object to test.
|
||||
* @return TRUE if this object is simple enough to simply be printed, FALSE othewise.
|
||||
*/
|
||||
protected static boolean isSimpleType(Object test) {
|
||||
return test instanceof String || Primitives.isWrapperType(test.getClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the current number of errors printed through {@link #reportDetailed(Object, Report)}.
|
||||
* @return Number of errors printed.
|
||||
*/
|
||||
public int getErrorCount() {
|
||||
return internalErrorCount.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the number of errors printed.
|
||||
* @param errorCount - new number of errors printed.
|
||||
*/
|
||||
public void setErrorCount(int errorCount) {
|
||||
internalErrorCount.set(errorCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the maximum number of errors we can print before we begin suppressing errors.
|
||||
* @return Maximum number of errors.
|
||||
*/
|
||||
public int getMaxErrorCount() {
|
||||
return maxErrorCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the maximum number of errors we can print before we begin suppressing errors.
|
||||
* @param maxErrorCount - new max count.
|
||||
*/
|
||||
public void setMaxErrorCount(int maxErrorCount) {
|
||||
this.maxErrorCount = maxErrorCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given global parameter. It will be included in every error report.
|
||||
* <p>
|
||||
* Both key and value must be non-null.
|
||||
* @param key - name of parameter.
|
||||
* @param value - the global parameter itself.
|
||||
*/
|
||||
public void addGlobalParameter(String key, Object value) {
|
||||
if (key == null)
|
||||
throw new IllegalArgumentException("key cannot be NULL.");
|
||||
if (value == null)
|
||||
throw new IllegalArgumentException("value cannot be NULL.");
|
||||
|
||||
globalParameters.put(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a global parameter by its key.
|
||||
* @param key - key of the parameter to retrieve.
|
||||
* @return The value of the global parameter, or NULL if not found.
|
||||
*/
|
||||
public Object getGlobalParameter(String key) {
|
||||
if (key == null)
|
||||
throw new IllegalArgumentException("key cannot be NULL.");
|
||||
|
||||
return globalParameters.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset all global parameters.
|
||||
*/
|
||||
public void clearGlobalParameters() {
|
||||
globalParameters.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a set of every registered global parameter.
|
||||
* @return Set of all registered global parameters.
|
||||
*/
|
||||
public Set<String> globalParameters() {
|
||||
return globalParameters.keySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the support URL that will be added to all detailed reports.
|
||||
* @return Support URL.
|
||||
*/
|
||||
public String getSupportURL() {
|
||||
return supportURL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the support URL that will be added to all detailed reports.
|
||||
* @param supportURL - the new support URL.
|
||||
*/
|
||||
public void setSupportURL(String supportURL) {
|
||||
this.supportURL = supportURL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the prefix to apply to every line in the error reports.
|
||||
* @return Error report prefix.
|
||||
*/
|
||||
public String getPrefix() {
|
||||
return prefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the prefix to apply to every line in the error reports.
|
||||
* @param prefix - new prefix.
|
||||
*/
|
||||
public void setPrefix(String prefix) {
|
||||
this.prefix = prefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the current logger that is used to print all reports.
|
||||
* @return The current logger.
|
||||
*/
|
||||
public Logger getLogger() {
|
||||
return logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current logger that is used to print all reports.
|
||||
* @param logger - new logger.
|
||||
*/
|
||||
public void setLogger(Logger logger) {
|
||||
this.logger = logger;
|
||||
}
|
||||
}
|
||||
@@ -1,90 +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.error;
|
||||
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import com.comphenix.protocol.error.Report.ReportBuilder;
|
||||
|
||||
/**
|
||||
* Represents an object that can forward an error {@link Report} to the display and permanent storage.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public interface ErrorReporter {
|
||||
/**
|
||||
* Prints a small minimal error report regarding an exception from another plugin.
|
||||
* @param sender - the other plugin.
|
||||
* @param methodName - name of the caller method.
|
||||
* @param error - the exception itself.
|
||||
*/
|
||||
public abstract void reportMinimal(Plugin sender, String methodName, Throwable error);
|
||||
|
||||
/**
|
||||
* Prints a small minimal error report regarding an exception from another plugin.
|
||||
* @param sender - the other plugin.
|
||||
* @param methodName - name of the caller method.
|
||||
* @param error - the exception itself.
|
||||
* @param parameters - any relevant parameters to print.
|
||||
*/
|
||||
public abstract void reportMinimal(Plugin sender, String methodName, Throwable error, Object... parameters);
|
||||
|
||||
/**
|
||||
* Prints a debug message from the current sender.
|
||||
* <p>
|
||||
* Most users will not see this message.
|
||||
* @param sender - the sender.
|
||||
* @param report - the report.
|
||||
*/
|
||||
public abstract void reportDebug(Object sender, Report report);
|
||||
|
||||
/**
|
||||
* Prints a debug message from the current sender.
|
||||
* @param sender - the sender.
|
||||
* @param builder - the report builder.
|
||||
*/
|
||||
public abstract void reportDebug(Object sender, ReportBuilder builder);
|
||||
|
||||
/**
|
||||
* Prints a warning message from the current plugin.
|
||||
* @param sender - the object containing the caller method.
|
||||
* @param report - an error report to include.
|
||||
*/
|
||||
public abstract void reportWarning(Object sender, Report report);
|
||||
|
||||
/**
|
||||
* Prints a warning message from the current plugin.
|
||||
* @param sender - the object containing the caller method.
|
||||
* @param reportBuilder - an error report builder that will be used to get the report.
|
||||
*/
|
||||
public abstract void reportWarning(Object sender, ReportBuilder reportBuilder);
|
||||
|
||||
/**
|
||||
* Prints a detailed error report about an unhandled exception.
|
||||
* @param sender - the object containing the caller method.
|
||||
* @param report - an error report to include.
|
||||
*/
|
||||
public abstract void reportDetailed(Object sender, Report report);
|
||||
|
||||
/**
|
||||
* Prints a detailed error report about an unhandled exception.
|
||||
* @param sender - the object containing the caller method.
|
||||
* @param reportBuilder - an error report builder that will be used to get the report.
|
||||
*/
|
||||
public abstract void reportDetailed(Object sender, ReportBuilder reportBuilder);
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
package com.comphenix.protocol.error;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URLDecoder;
|
||||
import java.security.CodeSource;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
|
||||
public final class PluginContext {
|
||||
// Determine plugin folder
|
||||
private static File pluginFolder;
|
||||
|
||||
private PluginContext() {
|
||||
// Not constructable
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the name of the plugin that called the last method(s) in the exception.
|
||||
* @param ex - the exception.
|
||||
* @return The name of the plugin, or NULL.
|
||||
*/
|
||||
public static String getPluginCaller(Exception ex) {
|
||||
StackTraceElement[] elements = ex.getStackTrace();
|
||||
String current = getPluginName(elements[0]);
|
||||
|
||||
for (int i = 1; i < elements.length; i++) {
|
||||
String caller = getPluginName(elements[i]);
|
||||
if (caller != null && !caller.equals(current)) {
|
||||
return caller;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup the plugin that this method invocation belongs to, and return its file name.
|
||||
* @param element - the method invocation.
|
||||
* @return Plugin name, or NULL if not found.
|
||||
*/
|
||||
public static String getPluginName(StackTraceElement element) {
|
||||
try {
|
||||
if (Bukkit.getServer() == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
CodeSource codeSource = Class.forName(element.getClassName()).getProtectionDomain().getCodeSource();
|
||||
if (codeSource != null) {
|
||||
String encoding = codeSource.getLocation().getPath();
|
||||
File path = new File(URLDecoder.decode(encoding, "UTF-8"));
|
||||
File plugins = getPluginFolder();
|
||||
|
||||
if (plugins != null && folderContains(plugins, path)) {
|
||||
return path.getName().replaceAll(".jar", "");
|
||||
}
|
||||
}
|
||||
} catch (Throwable ex) {
|
||||
// throw new RuntimeException("Cannot lookup plugin name.", ex);
|
||||
}
|
||||
|
||||
return null; // Cannot find it
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a folder contains the given file.
|
||||
* @param folder - the folder.
|
||||
* @param file - the file.
|
||||
* @return TRUE if it does, FALSE otherwise.
|
||||
*/
|
||||
private static boolean folderContains(File folder, File file) {
|
||||
Preconditions.checkNotNull(folder, "folder cannot be NULL");
|
||||
Preconditions.checkNotNull(file, "file cannot be NULL");
|
||||
|
||||
// Get absolute versions
|
||||
folder = folder.getAbsoluteFile();
|
||||
file = file.getAbsoluteFile();
|
||||
|
||||
while (file != null) {
|
||||
if (folder.equals(file))
|
||||
return true;
|
||||
file = file.getParentFile();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the folder that contains every plugin on the server.
|
||||
* @return Folder with every plugin, or NULL if Bukkit has not been initialized yet.
|
||||
*/
|
||||
private static File getPluginFolder() {
|
||||
File folder = pluginFolder;
|
||||
|
||||
if (folder == null && Bukkit.getServer() != null) {
|
||||
Plugin[] plugins = Bukkit.getPluginManager().getPlugins();
|
||||
|
||||
if (plugins.length > 0) {
|
||||
folder = plugins[0].getDataFolder().getParentFile();
|
||||
pluginFolder = folder;
|
||||
}
|
||||
}
|
||||
|
||||
return folder;
|
||||
}
|
||||
}
|
||||
@@ -1,240 +0,0 @@
|
||||
package com.comphenix.protocol.error;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Represents a error or warning report.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public class Report {
|
||||
private final ReportType type;
|
||||
private final Throwable exception;
|
||||
private final Object[] messageParameters;
|
||||
private final Object[] callerParameters;
|
||||
|
||||
// Used to rate limit reports that are similar
|
||||
private final long rateLimit;
|
||||
|
||||
/**
|
||||
* Must be constructed through the factory method in Report.
|
||||
*/
|
||||
public static class ReportBuilder {
|
||||
private ReportType type;
|
||||
private Throwable exception;
|
||||
private Object[] messageParameters;
|
||||
private Object[] callerParameters;
|
||||
private long rateLimit;
|
||||
|
||||
private ReportBuilder() {
|
||||
// Don't allow
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current report type. Cannot be NULL.
|
||||
* @param type - report type.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public ReportBuilder type(ReportType type) {
|
||||
if (type == null)
|
||||
throw new IllegalArgumentException("Report type cannot be set to NULL.");
|
||||
this.type = type;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current exception that occurred.
|
||||
* @param exception - exception that occurred.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public ReportBuilder error(@Nullable Throwable exception) {
|
||||
this.exception = exception;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the message parameters that are used to construct a message text.
|
||||
* @param messageParameters - parameters for the report type.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public ReportBuilder messageParam(@Nullable Object... messageParameters) {
|
||||
this.messageParameters = messageParameters;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the parameters in the caller method. This is optional.
|
||||
* @param callerParameters - parameters of the caller method.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public ReportBuilder callerParam(@Nullable Object... callerParameters) {
|
||||
this.callerParameters = callerParameters;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the minimum number of nanoseconds to wait until a report of equal type and parameters
|
||||
* is allowed to be printed again.
|
||||
* @param rateLimit - number of nanoseconds, or 0 to disable. Cannot be negative.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public ReportBuilder rateLimit(long rateLimit) {
|
||||
if (rateLimit < 0)
|
||||
throw new IllegalArgumentException("Rate limit cannot be less than zero.");
|
||||
this.rateLimit = rateLimit;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the minimum time to wait until a report of equal type and parameters is allowed to be printed again.
|
||||
* @param rateLimit - the time, or 0 to disable. Cannot be negative.
|
||||
* @param rateUnit - the unit of the rate limit.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public ReportBuilder rateLimit(long rateLimit, TimeUnit rateUnit) {
|
||||
return rateLimit(TimeUnit.NANOSECONDS.convert(rateLimit, rateUnit));
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new report with the provided input.
|
||||
* @return A new report.
|
||||
*/
|
||||
public Report build() {
|
||||
return new Report(type, exception, messageParameters, callerParameters, rateLimit);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new report builder.
|
||||
* @param type - the initial report type.
|
||||
* @return Report builder.
|
||||
*/
|
||||
public static ReportBuilder newBuilder(ReportType type) {
|
||||
return new ReportBuilder().type(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new report with the given type and parameters.
|
||||
* @param exception - exception that occured in the caller method.
|
||||
* @param type - the report type that will be used to construct the message.
|
||||
* @param messageParameters - parameters used to construct the report message.
|
||||
* @param callerParameters - parameters from the caller method.
|
||||
*/
|
||||
protected Report(ReportType type, @Nullable Throwable exception,
|
||||
@Nullable Object[] messageParameters, @Nullable Object[] callerParameters) {
|
||||
this(type, exception, messageParameters, callerParameters, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new report with the given type and parameters.
|
||||
* @param exception - exception that occurred in the caller method.
|
||||
* @param type - the report type that will be used to construct the message.
|
||||
* @param messageParameters - parameters used to construct the report message.
|
||||
* @param callerParameters - parameters from the caller method.
|
||||
* @param rateLimit - minimum number of nanoseconds to wait until a report of equal type and parameters is allowed to be printed again.
|
||||
*/
|
||||
protected Report(ReportType type, @Nullable Throwable exception,
|
||||
@Nullable Object[] messageParameters, @Nullable Object[] callerParameters, long rateLimit) {
|
||||
if (type == null)
|
||||
throw new IllegalArgumentException("type cannot be NULL.");
|
||||
this.type = type;
|
||||
this.exception = exception;
|
||||
this.messageParameters = messageParameters;
|
||||
this.callerParameters = callerParameters;
|
||||
this.rateLimit = rateLimit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format the current report type with the provided message parameters.
|
||||
* @return The formated report message.
|
||||
*/
|
||||
public String getReportMessage() {
|
||||
return type.getMessage(messageParameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the message parameters that will be used to construc the report message.
|
||||
* <p>
|
||||
* This should not be confused with the method parameters of the caller method.
|
||||
* @return Message parameters.
|
||||
*/
|
||||
public Object[] getMessageParameters() {
|
||||
return messageParameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the parameters of the caller method. Optional - may be NULL.
|
||||
* @return Parameters or the caller method.
|
||||
*/
|
||||
public Object[] getCallerParameters() {
|
||||
return callerParameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the report type.
|
||||
* @return Report type.
|
||||
*/
|
||||
public ReportType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the associated exception, or NULL if not found.
|
||||
* @return Associated exception, or NULL.
|
||||
*/
|
||||
public Throwable getException() {
|
||||
return exception;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if we have any message parameters.
|
||||
* @return TRUE if there are any message parameters, FALSE otherwise.
|
||||
*/
|
||||
public boolean hasMessageParameters() {
|
||||
return messageParameters != null && messageParameters.length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if we have any caller parameters.
|
||||
* @return TRUE if there are any caller parameters, FALSE otherwise.
|
||||
*/
|
||||
public boolean hasCallerParameters() {
|
||||
return callerParameters != null && callerParameters.length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve desired minimum number of nanoseconds until a report of the same type and parameters should be reprinted.
|
||||
* <p>
|
||||
* Note that this may be ignored or modified by the error reporter. Zero indicates no rate limit.
|
||||
* @return The number of nanoseconds. Never negative.
|
||||
*/
|
||||
public long getRateLimit() {
|
||||
return rateLimit;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + Arrays.hashCode(callerParameters);
|
||||
result = prime * result + Arrays.hashCode(messageParameters);
|
||||
result = prime * result + ((type == null) ? 0 : type.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj instanceof Report) {
|
||||
Report other = (Report) obj;
|
||||
return type == other.type &&
|
||||
Arrays.equals(callerParameters, other.callerParameters) &&
|
||||
Arrays.equals(messageParameters, other.messageParameters);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,148 +0,0 @@
|
||||
package com.comphenix.protocol.error;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.comphenix.protocol.reflect.FieldAccessException;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.comphenix.protocol.reflect.fuzzy.FuzzyFieldContract;
|
||||
|
||||
/**
|
||||
* Represents a strongly-typed report. Subclasses should be immutable.
|
||||
* <p>
|
||||
* By convention, a report must be declared as a static field publicly accessible from the sender class.
|
||||
* @author Kristian
|
||||
*/
|
||||
public class ReportType {
|
||||
private final String errorFormat;
|
||||
|
||||
// Used to store the report name
|
||||
protected String reportName;
|
||||
|
||||
/**
|
||||
* Construct a new report type.
|
||||
* @param errorFormat - string used to format the underlying report.
|
||||
*/
|
||||
public ReportType(String errorFormat) {
|
||||
this.errorFormat = errorFormat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the given report to a string, using the provided parameters.
|
||||
* @param parameters - parameters to insert, or NULL to insert nothing.
|
||||
* @return The full report in string format.
|
||||
*/
|
||||
public String getMessage(Object[] parameters) {
|
||||
if (parameters == null || parameters.length == 0)
|
||||
return toString();
|
||||
else
|
||||
return String.format(errorFormat, parameters);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return errorFormat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the class of the given sender.
|
||||
* <p>
|
||||
* If the sender is already a Class, we return it.
|
||||
* @param sender - the sender to look up.
|
||||
* @return The class of the sender.
|
||||
*/
|
||||
public static Class<?> getSenderClass(Object sender) {
|
||||
if (sender == null)
|
||||
throw new IllegalArgumentException("sender cannot be NUll.");
|
||||
else if (sender instanceof Class<?>)
|
||||
return (Class<?>) sender;
|
||||
else
|
||||
return sender.getClass();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the full canonical name of a given report type.
|
||||
* <p>
|
||||
* Note that the sender may be a class (for static callers), in which
|
||||
* case it will be used directly instead of its getClass() method.
|
||||
* <p>
|
||||
* It is thus not advisable for class classes to report reports.
|
||||
* @param sender - the sender, or its class.
|
||||
* @param type - the report type.
|
||||
* @return The full canonical name.
|
||||
*/
|
||||
public static String getReportName(Object sender, ReportType type) {
|
||||
if (sender == null)
|
||||
throw new IllegalArgumentException("sender cannot be NUll.");
|
||||
return getReportName(getSenderClass(sender), type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the full canonical name of a given report type.
|
||||
* <p>
|
||||
* This is in the format <i>canonical_name_of_class#report_type</i>
|
||||
* @param clazz - the sender class.
|
||||
* @param type - the report instance.
|
||||
* @return The full canonical name.
|
||||
*/
|
||||
private static String getReportName(Class<?> sender, ReportType type) {
|
||||
if (sender == null)
|
||||
throw new IllegalArgumentException("sender cannot be NUll.");
|
||||
|
||||
// Whether or not we need to retrieve the report name again
|
||||
if (type.reportName == null) {
|
||||
for (Field field : getReportFields(sender)) {
|
||||
try {
|
||||
field.setAccessible(true);
|
||||
|
||||
if (field.get(null) == type) {
|
||||
// We got the right field!
|
||||
return type.reportName = field.getDeclaringClass().getCanonicalName() + "#" + field.getName();
|
||||
}
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new FieldAccessException("Unable to read field " + field, e);
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Cannot find report name for " + type);
|
||||
}
|
||||
return type.reportName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all publicly associated reports.
|
||||
* @param sender - sender class.
|
||||
* @return All associated reports.
|
||||
*/
|
||||
public static ReportType[] getReports(Class<?> sender) {
|
||||
if (sender == null)
|
||||
throw new IllegalArgumentException("sender cannot be NULL.");
|
||||
List<ReportType> result = new ArrayList<ReportType>();
|
||||
|
||||
// Go through all the fields
|
||||
for (Field field : getReportFields(sender)) {
|
||||
try {
|
||||
field.setAccessible(true);
|
||||
result.add((ReportType) field.get(null));
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new FieldAccessException("Unable to read field " + field, e);
|
||||
}
|
||||
}
|
||||
return result.toArray(new ReportType[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all publicly associated report fields.
|
||||
* @param clazz - sender class.
|
||||
* @return All associated report fields.
|
||||
*/
|
||||
private static List<Field> getReportFields(Class<?> clazz) {
|
||||
return FuzzyReflection.fromClass(clazz).getFieldList(
|
||||
FuzzyFieldContract.newBuilder().
|
||||
requireModifier(Modifier.STATIC).
|
||||
typeDerivedOf(ReportType.class).
|
||||
build()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
package com.comphenix.protocol.error;
|
||||
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import com.comphenix.protocol.error.Report.ReportBuilder;
|
||||
import com.google.common.base.Joiner;
|
||||
|
||||
/**
|
||||
* Represents an error reporter that rethrows every exception instead.
|
||||
* @author Kristian
|
||||
*/
|
||||
public class RethrowErrorReporter implements ErrorReporter {
|
||||
@Override
|
||||
public void reportMinimal(Plugin sender, String methodName, Throwable error) {
|
||||
throw new RuntimeException("Minimal error by " + sender + " in " + methodName, error);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportMinimal(Plugin sender, String methodName, Throwable error, Object... parameters) {
|
||||
throw new RuntimeException(
|
||||
"Minimal error by " + sender + " in " + methodName + " with " + Joiner.on(",").join(parameters), error);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportDebug(Object sender, Report report) {
|
||||
// Do nothing - this is just a debug
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportDebug(Object sender, ReportBuilder builder) {
|
||||
// As above
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportWarning(Object sender, ReportBuilder reportBuilder) {
|
||||
reportWarning(sender, reportBuilder.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportWarning(Object sender, Report report) {
|
||||
throw new RuntimeException("Warning by " + sender + ": " + report);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportDetailed(Object sender, ReportBuilder reportBuilder) {
|
||||
reportDetailed(sender, reportBuilder.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportDetailed(Object sender, Report report) {
|
||||
throw new RuntimeException("Detailed error " + sender + ": " + report, report.getException());
|
||||
}
|
||||
}
|
||||
@@ -1,90 +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 com.comphenix.protocol.PacketType.Sender;
|
||||
|
||||
/**
|
||||
* Used to set a packet filter.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public enum ConnectionSide {
|
||||
/**
|
||||
* Listen for server side packets that will invoke onPacketSending().
|
||||
*/
|
||||
SERVER_SIDE,
|
||||
|
||||
/**
|
||||
* Listen for client side packets that will invoke onPacketReceiving().
|
||||
*/
|
||||
CLIENT_SIDE,
|
||||
|
||||
/**
|
||||
* Listen for both client and server side packets.
|
||||
*/
|
||||
BOTH;
|
||||
|
||||
public boolean isForClient() {
|
||||
return this == CLIENT_SIDE || this == BOTH;
|
||||
}
|
||||
|
||||
public boolean isForServer() {
|
||||
return this == SERVER_SIDE || this == BOTH;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the sender of this connection side.
|
||||
* <p>
|
||||
* This is NULL for {@link #BOTH}.
|
||||
* @return The sender.
|
||||
*/
|
||||
public Sender getSender() {
|
||||
if (this == SERVER_SIDE)
|
||||
return Sender.SERVER;
|
||||
else if (this == CLIENT_SIDE)
|
||||
return Sender.CLIENT;
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* If both connection sides are present, return {@link #BOTH} - otherwise, return the one valud connection side.
|
||||
* <p>
|
||||
* NULL is not a valid connection side.
|
||||
* @param a - the first connection side.
|
||||
* @param b - the second connection side.
|
||||
* @return BOTH or the one valid side, or NULL.
|
||||
*/
|
||||
public static ConnectionSide add(ConnectionSide a, ConnectionSide b) {
|
||||
if (a == null)
|
||||
return b;
|
||||
if (b == null)
|
||||
return a;
|
||||
|
||||
// Now merge them together
|
||||
boolean client = a.isForClient() || b.isForClient();
|
||||
boolean server = a.isForServer() || b.isForServer();
|
||||
|
||||
if (client && server)
|
||||
return BOTH;
|
||||
else if (client)
|
||||
return CLIENT_SIDE;
|
||||
else
|
||||
return SERVER_SIDE;
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
package com.comphenix.protocol.events;
|
||||
|
||||
import com.comphenix.protocol.injector.GamePhase;
|
||||
|
||||
/**
|
||||
* Represents additional options a listener may require.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public enum ListenerOptions {
|
||||
/**
|
||||
* Retrieve the serialized client packet as it appears on the network stream.
|
||||
*/
|
||||
INTERCEPT_INPUT_BUFFER,
|
||||
|
||||
/**
|
||||
* Disable the automatic game phase detection that will normally force {@link GamePhase#LOGIN} when
|
||||
* a packet ID is known to be transmitted during login.
|
||||
*/
|
||||
DISABLE_GAMEPHASE_DETECTION,
|
||||
|
||||
/**
|
||||
* Do not verify that the owning plugin has a vaid plugin.yml.
|
||||
*/
|
||||
SKIP_PLUGIN_VERIFIER,
|
||||
|
||||
/**
|
||||
* Notify ProtocolLib that {@link PacketListener#onPacketSending(PacketEvent)} is thread safe.
|
||||
*/
|
||||
ASYNC;
|
||||
}
|
||||
@@ -1,484 +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.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Set;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.injector.GamePhase;
|
||||
import com.comphenix.protocol.injector.packet.PacketRegistry;
|
||||
import com.google.common.base.Objects;
|
||||
import com.google.common.collect.Sets;
|
||||
|
||||
/**
|
||||
* Determines which packets will be observed by a listener, and with what priority.
|
||||
|
||||
* @author Kristian
|
||||
*/
|
||||
public class ListeningWhitelist {
|
||||
/**
|
||||
* A whitelist with no packets - indicates that the listener shouldn't observe any packets.
|
||||
*/
|
||||
public static final ListeningWhitelist EMPTY_WHITELIST = new ListeningWhitelist(ListenerPriority.LOW);
|
||||
|
||||
private final ListenerPriority priority;
|
||||
private final GamePhase gamePhase;
|
||||
private final Set<ListenerOptions> options;
|
||||
private final Set<PacketType> types;
|
||||
|
||||
// Cache whitelist
|
||||
private transient Set<Integer> intWhitelist;
|
||||
|
||||
private ListeningWhitelist(Builder builder) {
|
||||
this.priority = builder.priority;
|
||||
this.types = builder.types;
|
||||
this.gamePhase = builder.gamePhase;
|
||||
this.options = builder.options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a packet whitelist for a given priority with a set of packet IDs.
|
||||
* <p>
|
||||
* Deprecated: Use {@link #newBuilder()} instead.
|
||||
* @param priority - the listener priority.
|
||||
* @param whitelist - set of IDs to observe/enable.
|
||||
*/
|
||||
@Deprecated
|
||||
public ListeningWhitelist(ListenerPriority priority, Set<Integer> whitelist) {
|
||||
this(priority, whitelist, GamePhase.PLAYING);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a packet whitelist for a given priority with a set of packet IDs.
|
||||
* <p>
|
||||
* Deprecated: Use {@link #newBuilder()} instead.
|
||||
* @param priority - the listener priority.
|
||||
* @param whitelist - set of IDs to observe/enable.
|
||||
* @param gamePhase - which game phase to receieve notifications on.
|
||||
*/
|
||||
@Deprecated
|
||||
public ListeningWhitelist(ListenerPriority priority, Set<Integer> whitelist, GamePhase gamePhase) {
|
||||
this.priority = priority;
|
||||
this.types = PacketRegistry.toPacketTypes(safeSet(whitelist));
|
||||
this.gamePhase = gamePhase;
|
||||
this.options = EnumSet.noneOf(ListenerOptions.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a packet whitelist of a given priority for a list of packets.
|
||||
* <p>
|
||||
* Deprecated: Use {@link #newBuilder()} instead.
|
||||
* @param priority - the listener priority.
|
||||
* @param whitelist - list of packet IDs to observe/enable.
|
||||
*/
|
||||
@Deprecated
|
||||
public ListeningWhitelist(ListenerPriority priority, Integer... whitelist) {
|
||||
this.priority = priority;
|
||||
this.types = PacketRegistry.toPacketTypes(Sets.newHashSet(whitelist));
|
||||
this.gamePhase = GamePhase.PLAYING;
|
||||
this.options = EnumSet.noneOf(ListenerOptions.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a packet whitelist for a given priority with a set of packet IDs.
|
||||
* <p>
|
||||
* Deprecated: Use {@link #newBuilder()} instead.
|
||||
* @param priority - the listener priority.
|
||||
* @param whitelist - list of packet IDs to observe/enable.
|
||||
* @param gamePhase - which game phase to receieve notifications on.
|
||||
*/
|
||||
@Deprecated
|
||||
public ListeningWhitelist(ListenerPriority priority, Integer[] whitelist, GamePhase gamePhase) {
|
||||
this.priority = priority;
|
||||
this.types = PacketRegistry.toPacketTypes(Sets.newHashSet(whitelist));
|
||||
this.gamePhase = gamePhase;
|
||||
this.options = EnumSet.noneOf(ListenerOptions.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a packet whitelist for a given priority with a set of packet IDs and options.
|
||||
* <p>
|
||||
* Deprecated: Use {@link #newBuilder()} instead.
|
||||
* @param priority - the listener priority.
|
||||
* @param whitelist - list of packet IDs to observe/enable.
|
||||
* @param gamePhase - which game phase to receieve notifications on.
|
||||
* @param options - every special option associated with this whitelist.
|
||||
*/
|
||||
@Deprecated
|
||||
public ListeningWhitelist(ListenerPriority priority, Integer[] whitelist, GamePhase gamePhase, ListenerOptions... options) {
|
||||
this.priority = priority;
|
||||
this.types = PacketRegistry.toPacketTypes(Sets.newHashSet(whitelist));
|
||||
this.gamePhase = gamePhase;
|
||||
this.options = safeEnumSet(Arrays.asList(options), ListenerOptions.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not this whitelist has any enabled packets.
|
||||
* @return TRUE if there are any packets, FALSE otherwise.
|
||||
*/
|
||||
public boolean isEnabled() {
|
||||
return types != null && types.size() > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the priority in the execution order of the packet listener. Highest priority will be executed last.
|
||||
* @return Execution order in terms of priority.
|
||||
*/
|
||||
public ListenerPriority getPriority() {
|
||||
return priority;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the list of packets that will be observed by the listeners.
|
||||
* <p>
|
||||
* Deprecated: Use {@link #getTypes()} instead.
|
||||
* @return Packet whitelist.
|
||||
*/
|
||||
@Deprecated
|
||||
public Set<Integer> getWhitelist() {
|
||||
if (intWhitelist == null)
|
||||
intWhitelist = PacketRegistry.toLegacy(types);
|
||||
return intWhitelist;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a set of the packets that will be observed by the listeners.
|
||||
* @return Packet whitelist.
|
||||
*/
|
||||
public Set<PacketType> getTypes() {
|
||||
return types;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve which game phase this listener is active under.
|
||||
* @return The active game phase.
|
||||
*/
|
||||
public GamePhase getGamePhase() {
|
||||
return gamePhase;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve every special option associated with this whitelist.
|
||||
* @return Every special option.
|
||||
*/
|
||||
public Set<ListenerOptions> getOptions() {
|
||||
return Collections.unmodifiableSet(options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hashCode(priority, types, gamePhase, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if any of the given IDs can be found in the whitelist.
|
||||
* @param whitelist - whitelist to test.
|
||||
* @param idList - list of packet IDs to find.
|
||||
* @return TRUE if any of the packets in the list can be found in the whitelist, FALSE otherwise.
|
||||
*/
|
||||
public static boolean containsAny(ListeningWhitelist whitelist, int... idList) {
|
||||
if (whitelist != null) {
|
||||
for (int i = 0; i < idList.length; i++) {
|
||||
if (whitelist.getWhitelist().contains(idList[i]))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the given whitelist is empty or not.
|
||||
* @param whitelist - the whitelist to test.
|
||||
* @return TRUE if the whitelist is empty, FALSE otherwise.
|
||||
*/
|
||||
public static boolean isEmpty(ListeningWhitelist whitelist) {
|
||||
if (whitelist == EMPTY_WHITELIST)
|
||||
return true;
|
||||
else if (whitelist == null)
|
||||
return true;
|
||||
else
|
||||
return whitelist.getTypes().isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
if (obj instanceof ListeningWhitelist) {
|
||||
final ListeningWhitelist other = (ListeningWhitelist) obj;
|
||||
return Objects.equal(priority, other.priority)
|
||||
&& Objects.equal(types, other.types)
|
||||
&& Objects.equal(gamePhase, other.gamePhase)
|
||||
&& Objects.equal(options, other.options);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (this == EMPTY_WHITELIST)
|
||||
return "EMPTY_WHITELIST";
|
||||
else
|
||||
return Objects.toStringHelper(this).
|
||||
add("priority", priority).
|
||||
add("packets", types).
|
||||
add("gamephase", gamePhase).
|
||||
add("options", options).
|
||||
toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new builder of whitelists.
|
||||
* @return New whitelist builder.
|
||||
*/
|
||||
public static Builder newBuilder() {
|
||||
return new Builder(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new builder of whitelists initialized to the same values as the template.
|
||||
* @param template - the template object.
|
||||
* @return New whitelist builder.
|
||||
*/
|
||||
public static Builder newBuilder(ListeningWhitelist template) {
|
||||
return new Builder(template);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a copy of a given enum.
|
||||
* @param options - the options to copy, or NULL to indicate the empty set.
|
||||
* @return A copy of the enum set.
|
||||
*/
|
||||
private static <T extends Enum<T>> EnumSet<T> safeEnumSet(Collection<T> options, Class<T> enumClass) {
|
||||
if (options != null && !options.isEmpty()) {
|
||||
return EnumSet.copyOf(options);
|
||||
} else {
|
||||
return EnumSet.noneOf(enumClass);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a copy of a given set.
|
||||
* @param list - the set to copy.
|
||||
* @return The copied set.
|
||||
*/
|
||||
private static <T> Set<T> safeSet(Collection<T> set) {
|
||||
if (set != null)
|
||||
return Sets.newHashSet(set);
|
||||
else
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a builder of whitelists.
|
||||
* @author Kristian
|
||||
*/
|
||||
public static class Builder {
|
||||
// Default values
|
||||
private ListenerPriority priority = ListenerPriority.NORMAL;
|
||||
private Set<PacketType> types = Sets.newHashSet();
|
||||
private GamePhase gamePhase = GamePhase.PLAYING;
|
||||
private Set<ListenerOptions> options = Sets.newHashSet();
|
||||
|
||||
/**
|
||||
* Construct a new listening whitelist template.
|
||||
* @param template - the template.
|
||||
*/
|
||||
private Builder(ListeningWhitelist template) {
|
||||
if (template != null) {
|
||||
priority(template.getPriority());
|
||||
gamePhase(template.getGamePhase());
|
||||
types(template.getTypes());
|
||||
options(template.getOptions());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the priority to use when constructing new whitelists.
|
||||
* @param priority - the priority.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public Builder priority(ListenerPriority priority) {
|
||||
this.priority = priority;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the priority of the whitelist to monitor.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public Builder monitor() {
|
||||
return priority(ListenerPriority.MONITOR);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the priority of the whitelist to normal.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public Builder normal() {
|
||||
return priority(ListenerPriority.NORMAL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the priority of the whitelist to lowest.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public Builder lowest() {
|
||||
return priority(ListenerPriority.LOWEST);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the priority of the whitelist to low.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public Builder low() {
|
||||
return priority(ListenerPriority.LOW);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the priority of the whitelist to highest.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public Builder highest() {
|
||||
return priority(ListenerPriority.HIGHEST);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the priority of the whitelist to high.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public Builder high() {
|
||||
return priority(ListenerPriority.HIGH);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the whitelist of packet IDs to copy when constructing new whitelists.
|
||||
* <p>
|
||||
* Deprecated: Use {@link #types(Collection)} instead.
|
||||
* @param whitelist - the whitelist of packets.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
@Deprecated
|
||||
public Builder whitelist(Collection<Integer> whitelist) {
|
||||
this.types = PacketRegistry.toPacketTypes(safeSet(whitelist));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the whitelist of packet types to copy when constructing new whitelists.
|
||||
* @param types - the whitelist of packets.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public Builder types(PacketType... types) {
|
||||
this.types = safeSet(Sets.newHashSet(types));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the whitelist of packet types to copy when constructing new whitelists.
|
||||
* @param types - the whitelist of packets.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public Builder types(Collection<PacketType> types) {
|
||||
this.types = safeSet(types);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the gamephase to use when constructing new whitelists.
|
||||
* @param gamePhase - the gamephase.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public Builder gamePhase(GamePhase gamePhase) {
|
||||
this.gamePhase = gamePhase;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the gamephase to {@link GamePhase#BOTH}.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public Builder gamePhaseBoth() {
|
||||
return gamePhase(GamePhase.BOTH);
|
||||
}
|
||||
/**
|
||||
* Set the options to copy when constructing new whitelists.
|
||||
* @param options - the options.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public Builder options(Set<ListenerOptions> options) {
|
||||
this.options = safeSet(options);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the options to copy when constructing new whitelists.
|
||||
* @param options - the options.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public Builder options(Collection<ListenerOptions> options) {
|
||||
this.options = safeSet(options);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the options to copy when constructing new whitelists.
|
||||
* @param serverOptions - the options array.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public Builder options(ListenerOptions[] serverOptions) {
|
||||
this.options = safeSet(Sets.newHashSet(serverOptions));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Options to merge into the current set of options.
|
||||
* @param serverOptions - the options array.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public Builder mergeOptions(ListenerOptions... serverOptions) {
|
||||
return mergeOptions(Arrays.asList(serverOptions));
|
||||
}
|
||||
|
||||
/**
|
||||
* Options to merge into the current set of options.
|
||||
* @param serverOptions - the options array.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public Builder mergeOptions(Collection<ListenerOptions> serverOptions) {
|
||||
if (options == null)
|
||||
return options(serverOptions);
|
||||
|
||||
// Merge the options
|
||||
this.options.addAll(serverOptions);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new whitelist from the values in this builder.
|
||||
* @return The new whitelist.
|
||||
*/
|
||||
public ListeningWhitelist build() {
|
||||
return new ListeningWhitelist(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,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;
|
||||
}
|
||||
}
|
||||
@@ -1,636 +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.List;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.injector.GamePhase;
|
||||
import com.comphenix.protocol.injector.packet.PacketRegistry;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Sets;
|
||||
|
||||
/**
|
||||
* Represents a packet listener with useful constructors.
|
||||
* <p>
|
||||
* Remember to override onPacketReceiving() and onPacketSending(), depending on the ConnectionSide.
|
||||
* @author Kristian
|
||||
*/
|
||||
public abstract class PacketAdapter implements PacketListener {
|
||||
protected Plugin plugin;
|
||||
protected ConnectionSide connectionSide;
|
||||
protected ListeningWhitelist receivingWhitelist = ListeningWhitelist.EMPTY_WHITELIST;
|
||||
protected ListeningWhitelist sendingWhitelist = ListeningWhitelist.EMPTY_WHITELIST;
|
||||
|
||||
/**
|
||||
* Initialize a packet adapter using a collection of parameters. Use {@link #params()} to get an instance to this builder.
|
||||
* @param params - the parameters.
|
||||
*/
|
||||
public PacketAdapter(@Nonnull AdapterParameteters params) {
|
||||
this(
|
||||
checkValidity(params).plugin, params.connectionSide, params.listenerPriority,
|
||||
params.gamePhase, params.options, params.packets
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a packet listener with the given parameters.
|
||||
* @param plugin - the plugin.
|
||||
* @param types - the packet types.
|
||||
*/
|
||||
public PacketAdapter(Plugin plugin, PacketType... types) {
|
||||
this(plugin, ListenerPriority.NORMAL, types);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a packet listener with the given parameters.
|
||||
* @param plugin - the plugin.
|
||||
* @param types - the packet types.
|
||||
*/
|
||||
public PacketAdapter(Plugin plugin, Iterable<? extends PacketType> types) {
|
||||
this(params(plugin, Iterables.toArray(types, PacketType.class)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a packet listener with the given parameters.
|
||||
* @param plugin - the plugin.
|
||||
* @param listenerPriority - the priority.
|
||||
* @param types - the packet types.
|
||||
*/
|
||||
public PacketAdapter(Plugin plugin, ListenerPriority listenerPriority, Iterable<? extends PacketType> types) {
|
||||
this(params(plugin, Iterables.toArray(types, PacketType.class)).listenerPriority(listenerPriority));
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a packet listener with the given parameters.
|
||||
* @param plugin - the plugin.
|
||||
* @param listenerPriority - the priority.
|
||||
* @param types - the packet types.
|
||||
* @param options - the options.
|
||||
*/
|
||||
public PacketAdapter(Plugin plugin, ListenerPriority listenerPriority, Iterable<? extends PacketType> types, ListenerOptions... options) {
|
||||
this(params(plugin, Iterables.toArray(types, PacketType.class)).listenerPriority(listenerPriority).options(options));
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a packet listener with the given parameters.
|
||||
* @param plugin - the plugin.
|
||||
* @param listenerPriority - the priority.
|
||||
* @param types - the packet types.
|
||||
*/
|
||||
public PacketAdapter(Plugin plugin, ListenerPriority listenerPriority, PacketType... types) {
|
||||
this(params(plugin, types).listenerPriority(listenerPriority));
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a packet listener with default priority.
|
||||
* <p>
|
||||
* Deprecated: Use {@link #params()} instead.
|
||||
* @param plugin - the plugin that spawned this listener.
|
||||
* @param connectionSide - the packet type the listener is looking for.
|
||||
* @param packets - the packet IDs the listener is looking for.
|
||||
*/
|
||||
@Deprecated
|
||||
public PacketAdapter(Plugin plugin, ConnectionSide connectionSide, Integer... packets) {
|
||||
this(plugin, connectionSide, ListenerPriority.NORMAL, packets);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a packet listener for a single connection side.
|
||||
* <p>
|
||||
* Deprecated: Use {@link #params()} instead.
|
||||
* @param plugin - the plugin that spawned this listener.
|
||||
* @param connectionSide - the packet type the listener is looking for.
|
||||
* @param listenerPriority - the event priority.
|
||||
* @param packets - the packet IDs the listener is looking for.
|
||||
*/
|
||||
@Deprecated
|
||||
public PacketAdapter(Plugin plugin, ConnectionSide connectionSide, ListenerPriority listenerPriority, Set<Integer> packets) {
|
||||
this(plugin, connectionSide, listenerPriority, GamePhase.PLAYING, packets.toArray(new Integer[0]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a packet listener for a single connection side.
|
||||
* <p>
|
||||
* The game phase is used to optimize performance. A listener should only choose BOTH or LOGIN if it's absolutely necessary.
|
||||
* <p>
|
||||
* Deprecated: Use {@link #params()} instead.
|
||||
* @param plugin - the plugin that spawned this listener.
|
||||
* @param connectionSide - the packet type the listener is looking for.
|
||||
* @param gamePhase - which game phase this listener is active under.
|
||||
* @param packets - the packet IDs the listener is looking for.
|
||||
*/
|
||||
@Deprecated
|
||||
public PacketAdapter(Plugin plugin, ConnectionSide connectionSide, GamePhase gamePhase, Set<Integer> packets) {
|
||||
this(plugin, connectionSide, ListenerPriority.NORMAL, gamePhase, packets.toArray(new Integer[0]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a packet listener for a single connection side.
|
||||
* <p>
|
||||
* The game phase is used to optimize performance. A listener should only choose BOTH or LOGIN if it's absolutely necessary.
|
||||
* <p>
|
||||
* Deprecated: Use {@link #params()} instead.
|
||||
* @param plugin - the plugin that spawned this listener.
|
||||
* @param connectionSide - the packet type the listener is looking for.
|
||||
* @param listenerPriority - the event priority.
|
||||
* @param gamePhase - which game phase this listener is active under.
|
||||
* @param packets - the packet IDs the listener is looking for.
|
||||
*/
|
||||
@Deprecated
|
||||
public PacketAdapter(Plugin plugin, ConnectionSide connectionSide, ListenerPriority listenerPriority, GamePhase gamePhase, Set<Integer> packets) {
|
||||
this(plugin, connectionSide, listenerPriority, gamePhase, packets.toArray(new Integer[0]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a packet listener for a single connection side.
|
||||
* <p>
|
||||
* Deprecated: Use {@link #params()} instead.
|
||||
* @param plugin - the plugin that spawned this listener.
|
||||
* @param connectionSide - the packet type the listener is looking for.
|
||||
* @param listenerPriority - the event priority.
|
||||
* @param packets - the packet IDs the listener is looking for.
|
||||
*/
|
||||
@Deprecated
|
||||
public PacketAdapter(Plugin plugin, ConnectionSide connectionSide, ListenerPriority listenerPriority, Integer... packets) {
|
||||
this(plugin, connectionSide, listenerPriority, GamePhase.PLAYING, packets);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a packet listener for a single connection side.
|
||||
* <p>
|
||||
* Deprecated: Use {@link #params()} instead.
|
||||
* @param plugin - the plugin that spawned this listener.
|
||||
* @param connectionSide - the packet type the listener is looking for.
|
||||
* @param options - which listener options to use.
|
||||
* @param packets - the packet IDs the listener is looking for.
|
||||
*/
|
||||
@Deprecated
|
||||
public PacketAdapter(Plugin plugin, ConnectionSide connectionSide, ListenerOptions[] options, Integer... packets) {
|
||||
this(plugin, connectionSide, ListenerPriority.NORMAL, GamePhase.PLAYING, options, packets);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a packet listener for a single connection side.
|
||||
* <p>
|
||||
* Deprecated: Use {@link #params()} instead.
|
||||
* @param plugin - the plugin that spawned this listener.
|
||||
* @param connectionSide - the packet type the listener is looking for.
|
||||
* @param gamePhase - which game phase this listener is active under.
|
||||
* @param packets - the packet IDs the listener is looking for.
|
||||
*/
|
||||
@Deprecated
|
||||
public PacketAdapter(Plugin plugin, ConnectionSide connectionSide, GamePhase gamePhase, Integer... packets) {
|
||||
this(plugin, connectionSide, ListenerPriority.NORMAL, gamePhase, packets);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a packet listener for a single connection side.
|
||||
* <p>
|
||||
* The game phase is used to optimize performance. A listener should only choose BOTH or LOGIN if it's absolutely necessary.
|
||||
* <p>
|
||||
* Deprecated: Use {@link #params()} instead.
|
||||
* @param plugin - the plugin that spawned this listener.
|
||||
* @param connectionSide - the packet type the listener is looking for.
|
||||
* @param listenerPriority - the event priority.
|
||||
* @param gamePhase - which game phase this listener is active under.
|
||||
* @param packets - the packet IDs the listener is looking for.
|
||||
*/
|
||||
@Deprecated
|
||||
public PacketAdapter(Plugin plugin, ConnectionSide connectionSide, ListenerPriority listenerPriority, GamePhase gamePhase, Integer... packets) {
|
||||
this(plugin, connectionSide, listenerPriority, gamePhase, new ListenerOptions[0], packets);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a packet listener for a single connection side.
|
||||
* <p>
|
||||
* The game phase is used to optimize performance. A listener should only choose BOTH or LOGIN if it's absolutely necessary.
|
||||
* <p>
|
||||
* Listener options must be specified in order for {@link NetworkMarker#getInputBuffer()} to function correctly.
|
||||
* <p>
|
||||
* Deprecated: Use {@link #params()} instead.
|
||||
* @param plugin - the plugin that spawned this listener.
|
||||
* @param connectionSide - the packet type the listener is looking for.
|
||||
* @param listenerPriority - the event priority.
|
||||
* @param gamePhase - which game phase this listener is active under.
|
||||
* @param options - which listener options to use.
|
||||
* @param packets - the packet IDs the listener is looking for.
|
||||
*/
|
||||
@Deprecated
|
||||
public PacketAdapter(
|
||||
Plugin plugin, ConnectionSide connectionSide, ListenerPriority listenerPriority,
|
||||
GamePhase gamePhase, ListenerOptions[] options, Integer... packets) {
|
||||
|
||||
this(plugin, connectionSide, listenerPriority, gamePhase, options,
|
||||
PacketRegistry.toPacketTypes(Sets.newHashSet(packets), connectionSide.getSender()).toArray(new PacketType[0])
|
||||
);
|
||||
}
|
||||
|
||||
// For internal use only
|
||||
private PacketAdapter(
|
||||
Plugin plugin, ConnectionSide connectionSide, ListenerPriority listenerPriority,
|
||||
GamePhase gamePhase, ListenerOptions[] options, PacketType... packets) {
|
||||
|
||||
if (plugin == null)
|
||||
throw new IllegalArgumentException("plugin cannot be null");
|
||||
if (connectionSide == null)
|
||||
throw new IllegalArgumentException("connectionSide cannot be null");
|
||||
if (listenerPriority == null)
|
||||
throw new IllegalArgumentException("listenerPriority cannot be null");
|
||||
if (gamePhase == null)
|
||||
throw new IllegalArgumentException("gamePhase cannot be NULL");
|
||||
if (packets == null)
|
||||
throw new IllegalArgumentException("packets cannot be null");
|
||||
if (options == null)
|
||||
throw new IllegalArgumentException("options cannot be null");
|
||||
|
||||
ListenerOptions[] serverOptions = options;
|
||||
ListenerOptions[] clientOptions = options;
|
||||
|
||||
// Special case that allows us to specify optionIntercept().
|
||||
if (connectionSide == ConnectionSide.BOTH) {
|
||||
serverOptions = except(serverOptions, new ListenerOptions[0],
|
||||
ListenerOptions.INTERCEPT_INPUT_BUFFER);
|
||||
}
|
||||
|
||||
// Add whitelists
|
||||
if (connectionSide.isForServer())
|
||||
sendingWhitelist = ListeningWhitelist.newBuilder().
|
||||
priority(listenerPriority).
|
||||
types(packets).
|
||||
gamePhase(gamePhase).
|
||||
options(serverOptions).
|
||||
build();
|
||||
|
||||
if (connectionSide.isForClient())
|
||||
receivingWhitelist = ListeningWhitelist.newBuilder().
|
||||
priority(listenerPriority).
|
||||
types(packets).
|
||||
gamePhase(gamePhase).
|
||||
options(clientOptions).
|
||||
build();
|
||||
|
||||
this.plugin = plugin;
|
||||
this.connectionSide = connectionSide;
|
||||
}
|
||||
|
||||
// Remove a given element from an array
|
||||
private static <T> T[] except(T[] values, T[] buffer, T except) {
|
||||
List<T> result = Lists.newArrayList(values);
|
||||
|
||||
result.remove(except);
|
||||
return result.toArray(buffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketReceiving(PacketEvent event) {
|
||||
// Lets prevent some bugs
|
||||
throw new IllegalStateException("Override onPacketReceiving to get notifcations of received packets!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketSending(PacketEvent event) {
|
||||
// Lets prevent some bugs
|
||||
throw new IllegalStateException("Override onPacketSending to get notifcations of sent packets!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListeningWhitelist getReceivingWhitelist() {
|
||||
return receivingWhitelist;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListeningWhitelist getSendingWhitelist() {
|
||||
return sendingWhitelist;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Plugin getPlugin() {
|
||||
return plugin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the name of the plugin that has been associated with the listener.
|
||||
* @param listener - the listener.
|
||||
* @return Name of the associated plugin.
|
||||
*/
|
||||
public static String getPluginName(PacketListener listener) {
|
||||
return getPluginName(listener.getPlugin());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the name of the given plugin.
|
||||
* @param plugin - the plugin.
|
||||
* @return Name of the given plugin.
|
||||
*/
|
||||
public static String getPluginName(Plugin plugin) {
|
||||
if (plugin == null)
|
||||
return "UNKNOWN";
|
||||
|
||||
try {
|
||||
return plugin.getName();
|
||||
} catch (NoSuchMethodError e) {
|
||||
return plugin.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
// This is used by the error reporter
|
||||
return String.format("PacketAdapter[plugin=%s, sending=%s, receiving=%s]",
|
||||
getPluginName(this),
|
||||
sendingWhitelist,
|
||||
receivingWhitelist);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a helper object for passing parameters to the packet adapter.
|
||||
* <p>
|
||||
* This is often simpler and better than passing them directly to each constructor.
|
||||
* @return Helper object.
|
||||
*/
|
||||
public static AdapterParameteters params() {
|
||||
return new AdapterParameteters();
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a helper object for passing parameters to the packet adapter.
|
||||
* <p>
|
||||
* This is often simpler and better than passing them directly to each constructor.
|
||||
* Deprecated: Use {@link #params(Plugin, PacketType...)} instead.
|
||||
* @param plugin - the plugin that spawned this listener.
|
||||
* @param packets - the packet IDs the listener is looking for.
|
||||
* @return Helper object.
|
||||
*/
|
||||
@Deprecated
|
||||
public static AdapterParameteters params(Plugin plugin, Integer... packets) {
|
||||
return new AdapterParameteters().plugin(plugin).packets(packets);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a helper object for passing parameters to the packet adapter.
|
||||
* <p>
|
||||
* This is often simpler and better than passing them directly to each constructor.
|
||||
* @param plugin - the plugin that spawned this listener.
|
||||
* @param packets - the packet types the listener is looking for.
|
||||
* @return Helper object.
|
||||
*/
|
||||
public static AdapterParameteters params(Plugin plugin, PacketType... packets) {
|
||||
return new AdapterParameteters().plugin(plugin).types(packets);
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a builder for passing parameters to the packet adapter constructor.
|
||||
* <p>
|
||||
* Note: Never make spelling mistakes in a public API!
|
||||
* @author Kristian
|
||||
*/
|
||||
public static class AdapterParameteters {
|
||||
private Plugin plugin;
|
||||
private ConnectionSide connectionSide;
|
||||
private PacketType[] packets;
|
||||
|
||||
// Parameters with default values
|
||||
private GamePhase gamePhase = GamePhase.PLAYING;
|
||||
private ListenerOptions[] options = new ListenerOptions[0];
|
||||
private ListenerPriority listenerPriority = ListenerPriority.NORMAL;
|
||||
|
||||
/**
|
||||
* Set the plugin that spawned this listener. This parameter is required.
|
||||
* @param plugin - the plugin.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public AdapterParameteters plugin(@Nonnull Plugin plugin) {
|
||||
this.plugin = Preconditions.checkNotNull(plugin, "plugin cannot be NULL.");
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the packet types this listener is looking for. This parameter is required.
|
||||
* @param connectionSide - the new packet type.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public AdapterParameteters connectionSide(@Nonnull ConnectionSide connectionSide) {
|
||||
this.connectionSide = Preconditions.checkNotNull(connectionSide, "connectionside cannot be NULL.");
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set this adapter to also look for client-side packets.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public AdapterParameteters clientSide() {
|
||||
return connectionSide(ConnectionSide.add(connectionSide, ConnectionSide.CLIENT_SIDE));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set this adapter to also look for server-side packets.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public AdapterParameteters serverSide() {
|
||||
return connectionSide(ConnectionSide.add(connectionSide, ConnectionSide.SERVER_SIDE));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the the event priority, where the execution is in ascending order from lowest to highest.
|
||||
* <p>
|
||||
* Default is {@link ListenerPriority#NORMAL}.
|
||||
* @param listenerPriority - the new event priority.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public AdapterParameteters listenerPriority(@Nonnull ListenerPriority listenerPriority) {
|
||||
this.listenerPriority = Preconditions.checkNotNull(listenerPriority, "listener priority cannot be NULL.");
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set which game phase this listener is active under. This is a hint for ProtocolLib to start intercepting login packets.
|
||||
* <p>
|
||||
* Default is {@link GamePhase#PLAYING}, which will not intercept login packets.
|
||||
* @param gamePhase - the new game phase.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public AdapterParameteters gamePhase(@Nonnull GamePhase gamePhase) {
|
||||
this.gamePhase = Preconditions.checkNotNull(gamePhase, "gamePhase cannot be NULL.");
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the game phase to {@link GamePhase#LOGIN}, allowing ProtocolLib to intercept login packets.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public AdapterParameteters loginPhase() {
|
||||
return gamePhase(GamePhase.LOGIN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set listener options that decide whether or not to intercept the raw packet data as read from the network stream.
|
||||
* <p>
|
||||
* The default is to disable this raw packet interception.
|
||||
* @param options - every option to use.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public AdapterParameteters options(@Nonnull ListenerOptions... options) {
|
||||
this.options = Preconditions.checkNotNull(options, "options cannot be NULL.");
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set listener options that decide whether or not to intercept the raw packet data as read from the network stream.
|
||||
* <p>
|
||||
* The default is to disable this raw packet interception.
|
||||
* @param options - every option to use.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public AdapterParameteters options(@Nonnull Set<? extends ListenerOptions> options) {
|
||||
Preconditions.checkNotNull(options, "options cannot be NULL.");
|
||||
this.options = options.toArray(new ListenerOptions[0]);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a given option to the current builder.
|
||||
* @param option - the option to add.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
private AdapterParameteters addOption(ListenerOptions option) {
|
||||
if (options == null) {
|
||||
return options(option);
|
||||
} else {
|
||||
Set<ListenerOptions> current = Sets.newHashSet(options);
|
||||
current.add(option);
|
||||
return options(current);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the listener option to {@link ListenerOptions#INTERCEPT_INPUT_BUFFER}, causing ProtocolLib to read the raw packet data from the network stream.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public AdapterParameteters optionIntercept() {
|
||||
return addOption(ListenerOptions.INTERCEPT_INPUT_BUFFER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the listener option to {@link ListenerOptions#DISABLE_GAMEPHASE_DETECTION}, causing ProtocolLib to ignore automatic game phase detection.
|
||||
* <p>
|
||||
* This is no longer relevant in 1.7.2.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public AdapterParameteters optionManualGamePhase() {
|
||||
return addOption(ListenerOptions.DISABLE_GAMEPHASE_DETECTION);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the listener option to {@link ListenerOptions#ASYNC}, indicating that our listener is thread safe.
|
||||
* <p>
|
||||
* This allows ProtocolLib to perform certain optimizations.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public AdapterParameteters optionAsync() {
|
||||
return addOption(ListenerOptions.ASYNC);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the packet IDs of the packets the listener is looking for.
|
||||
* <p>
|
||||
* This parameter is required.
|
||||
* <p>
|
||||
* Deprecated: Use {@link #types(PacketType...)} instead.
|
||||
* @param packets - the packet IDs to look for.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
@Deprecated
|
||||
public AdapterParameteters packets(@Nonnull Integer... packets) {
|
||||
Preconditions.checkNotNull(packets, "packets cannot be NULL");
|
||||
PacketType[] types = new PacketType[packets.length];
|
||||
|
||||
for (int i = 0; i < types.length; i++) {
|
||||
types[i] = PacketType.findLegacy(packets[i]);
|
||||
}
|
||||
this.packets = types;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the packet IDs of the packets the listener is looking for.
|
||||
* <p>
|
||||
* This parameter is required.
|
||||
* @param packets - a set of the packet IDs to look for.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
@Deprecated
|
||||
public AdapterParameteters packets(@Nonnull Set<Integer> packets) {
|
||||
return packets(packets.toArray(new Integer[0]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the packet types the listener is looking for.
|
||||
* <p>
|
||||
* This parameter is required.
|
||||
* @param packets - the packet types to look for.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public AdapterParameteters types(@Nonnull PacketType... packets) {
|
||||
// Set the connection side as well
|
||||
if (connectionSide == null) {
|
||||
for (PacketType type : packets) {
|
||||
this.connectionSide = ConnectionSide.add(this.connectionSide, type.getSender().toSide());
|
||||
}
|
||||
}
|
||||
this.packets = Preconditions.checkNotNull(packets, "packets cannot be NULL");
|
||||
|
||||
if (packets.length == 0)
|
||||
throw new IllegalArgumentException("Passed an empty packet type array.");
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the packet types the listener is looking for.
|
||||
* <p>
|
||||
* This parameter is required.
|
||||
* @param packets - a set of packet types to look for.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public AdapterParameteters types(@Nonnull Set<PacketType> packets) {
|
||||
return types(packets.toArray(new PacketType[0]));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the required parameters are set.
|
||||
*/
|
||||
private static AdapterParameteters checkValidity(AdapterParameteters params) {
|
||||
if (params == null)
|
||||
throw new IllegalArgumentException("params cannot be NULL.");
|
||||
if (params.plugin == null)
|
||||
throw new IllegalStateException("Plugin was never set in the parameters.");
|
||||
if (params.connectionSide == null)
|
||||
throw new IllegalStateException("Connection side was never set in the parameters.");
|
||||
if (params.packets == null)
|
||||
throw new IllegalStateException("Packet IDs was never set in the parameters.");
|
||||
return params;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,455 +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.lang.ref.WeakReference;
|
||||
import java.util.EventObject;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.Cancellable;
|
||||
|
||||
import com.comphenix.protocol.Application;
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.ProtocolLibrary;
|
||||
import com.comphenix.protocol.async.AsyncMarker;
|
||||
import com.comphenix.protocol.error.Report;
|
||||
import com.comphenix.protocol.error.ReportType;
|
||||
import com.google.common.base.Objects;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.HashMultimap;
|
||||
import com.google.common.collect.Multimaps;
|
||||
import com.google.common.collect.SetMultimap;
|
||||
|
||||
/**
|
||||
* Represents a packet sending or receiving event. Changes to the packet will be
|
||||
* reflected in the final version to be sent or received. It is also possible to cancel an event.
|
||||
* @author Kristian
|
||||
*/
|
||||
public class PacketEvent extends EventObject implements Cancellable {
|
||||
public static final ReportType REPORT_CHANGING_PACKET_TYPE_IS_CONFUSING = new ReportType(
|
||||
"Plugin %s changed packet type from %s to %s in packet listener. This is confusing for other plugins! (Not an error, though!)");
|
||||
|
||||
private static final SetMultimap<PacketType, PacketType> CHANGE_WARNINGS =
|
||||
Multimaps.synchronizedSetMultimap(HashMultimap.<PacketType, PacketType>create());
|
||||
|
||||
/**
|
||||
* Automatically generated by Eclipse.
|
||||
*/
|
||||
private static final long serialVersionUID = -5360289379097430620L;
|
||||
|
||||
private transient WeakReference<Player> playerReference;
|
||||
private transient Player offlinePlayer;
|
||||
|
||||
private PacketContainer packet;
|
||||
private boolean serverPacket;
|
||||
private boolean cancel;
|
||||
|
||||
private AsyncMarker asyncMarker;
|
||||
private boolean asynchronous;
|
||||
|
||||
// Network input and output handlers
|
||||
NetworkMarker networkMarker;
|
||||
|
||||
// Whether or not a packet event is read only
|
||||
private boolean readOnly;
|
||||
private boolean filtered;
|
||||
|
||||
/**
|
||||
* Use the static constructors to create instances of this event.
|
||||
* @param source - the event source.
|
||||
*/
|
||||
public PacketEvent(Object source) {
|
||||
super(source);
|
||||
this.filtered = true;
|
||||
}
|
||||
|
||||
private PacketEvent(Object source, PacketContainer packet, Player player, boolean serverPacket) {
|
||||
this(source, packet, null, player, serverPacket, true);
|
||||
}
|
||||
|
||||
private PacketEvent(Object source, PacketContainer packet, NetworkMarker marker, Player player, boolean serverPacket, boolean filtered) {
|
||||
super(source);
|
||||
this.packet = packet;
|
||||
this.playerReference = new WeakReference<Player>(player);
|
||||
this.networkMarker = marker;
|
||||
this.serverPacket = serverPacket;
|
||||
this.filtered = filtered;
|
||||
}
|
||||
|
||||
private PacketEvent(PacketEvent origial, AsyncMarker asyncMarker) {
|
||||
super(origial.source);
|
||||
this.packet = origial.packet;
|
||||
this.playerReference = origial.playerReference;
|
||||
this.cancel = origial.cancel;
|
||||
this.serverPacket = origial.serverPacket;
|
||||
this.filtered = origial.filtered;
|
||||
this.networkMarker = origial.networkMarker;
|
||||
this.asyncMarker = asyncMarker;
|
||||
this.asynchronous = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an event representing a client packet transmission.
|
||||
* @param source - the event source.
|
||||
* @param packet - the packet.
|
||||
* @param client - the client that sent the packet.
|
||||
* @return The event.
|
||||
*/
|
||||
public static PacketEvent fromClient(Object source, PacketContainer packet, Player client) {
|
||||
return new PacketEvent(source, packet, client, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an event representing a client packet transmission.
|
||||
* @param source - the event source.
|
||||
* @param packet - the packet.
|
||||
* @param marker - the network marker.
|
||||
* @param client - the client that sent the packet.
|
||||
* @return The event.
|
||||
*/
|
||||
public static PacketEvent fromClient(Object source, PacketContainer packet, NetworkMarker marker, Player client) {
|
||||
return new PacketEvent(source, packet, marker, client, false, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an event representing a client packet transmission.
|
||||
* <p>
|
||||
* If <i>filtered</i> is FALSE, then this event is only processed by packet monitors.
|
||||
* @param source - the event source.
|
||||
* @param packet - the packet.
|
||||
* @param marker - the network marker.
|
||||
* @param client - the client that sent the packet.
|
||||
* @param filtered - whether or not this packet event is processed by every packet listener.
|
||||
* @return The event.
|
||||
*/
|
||||
public static PacketEvent fromClient(Object source, PacketContainer packet, NetworkMarker marker, Player client, boolean filtered) {
|
||||
return new PacketEvent(source, packet, marker, client, false, filtered);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an event representing a server packet transmission.
|
||||
* @param source - the event source.
|
||||
* @param packet - the packet.
|
||||
* @param recipient - the client that will receieve the packet.
|
||||
* @return The event.
|
||||
*/
|
||||
public static PacketEvent fromServer(Object source, PacketContainer packet, Player recipient) {
|
||||
return new PacketEvent(source, packet, recipient, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an event representing a server packet transmission.
|
||||
* @param source - the event source.
|
||||
* @param packet - the packet.
|
||||
* @param marker - the network marker.
|
||||
* @param recipient - the client that will receieve the packet.
|
||||
* @return The event.
|
||||
*/
|
||||
public static PacketEvent fromServer(Object source, PacketContainer packet, NetworkMarker marker, Player recipient) {
|
||||
return new PacketEvent(source, packet, marker, recipient, true, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an event representing a server packet transmission.
|
||||
* <p>
|
||||
* If <i>filtered</i> is FALSE, then this event is only processed by packet monitors.
|
||||
* @param source - the event source.
|
||||
* @param packet - the packet.
|
||||
* @param marker - the network marker.
|
||||
* @param recipient - the client that will receieve the packet.
|
||||
* @param filtered - whether or not this packet event is processed by every packet listener.
|
||||
* @return The event.
|
||||
*/
|
||||
public static PacketEvent fromServer(Object source, PacketContainer packet, NetworkMarker marker, Player recipient, boolean filtered) {
|
||||
return new PacketEvent(source, packet, marker, recipient, true, filtered);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an asynchronous packet event from a synchronous event and a async marker.
|
||||
* @param event - the original synchronous event.
|
||||
* @param marker - the asynchronous marker.
|
||||
* @return The new packet event.
|
||||
*/
|
||||
public static PacketEvent fromSynchronous(PacketEvent event, AsyncMarker marker) {
|
||||
return new PacketEvent(event, marker);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if we are executing the packet event in an asynchronous thread.
|
||||
* <p>
|
||||
* If so, you must synchronize all calls to the Bukkit API.
|
||||
* <p>
|
||||
* Generally, most server packets are executed on the main thread, whereas client packets
|
||||
* are all executed asynchronously.
|
||||
* @return TRUE if we are, FALSE otherwise.
|
||||
*/
|
||||
public boolean isAsync() {
|
||||
return !Application.isPrimaryThread();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the packet that will be sent to the player.
|
||||
* @return Packet to send to the player.
|
||||
*/
|
||||
public PacketContainer getPacket() {
|
||||
return packet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace the packet that will be sent to the player.
|
||||
* @param packet - the packet that will be sent instead.
|
||||
*/
|
||||
public void setPacket(PacketContainer packet) {
|
||||
if (readOnly)
|
||||
throw new IllegalStateException("The packet event is read-only.");
|
||||
if (packet == null)
|
||||
throw new IllegalArgumentException("Cannot set packet to NULL. Use setCancelled() instead.");
|
||||
|
||||
// Change warnings
|
||||
final PacketType oldType = this.packet.getType();
|
||||
final PacketType newType = packet.getType();
|
||||
if (this.packet != null && !Objects.equal(oldType, newType)) {
|
||||
// Only report this once
|
||||
if (CHANGE_WARNINGS.put(oldType, newType)) {
|
||||
ProtocolLibrary.getErrorReporter().reportWarning(this,
|
||||
Report.newBuilder(REPORT_CHANGING_PACKET_TYPE_IS_CONFUSING).
|
||||
messageParam(oldType, newType).
|
||||
build());
|
||||
}
|
||||
}
|
||||
this.packet = packet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the packet ID.
|
||||
* <p>
|
||||
* Deprecated: Use {@link #getPacketType()} instead.
|
||||
* @return The current packet ID.
|
||||
*/
|
||||
@Deprecated
|
||||
public int getPacketID() {
|
||||
return packet.getID();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the packet type.
|
||||
* @return The type.
|
||||
*/
|
||||
public PacketType getPacketType() {
|
||||
return packet.getType();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves whether or not the packet should be cancelled.
|
||||
* @return TRUE if it should be cancelled, FALSE otherwise.
|
||||
*/
|
||||
@Override
|
||||
public boolean isCancelled() {
|
||||
return cancel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the object responsible for managing the serialized input and output of a packet.
|
||||
* <p>
|
||||
* Note that the serialized input data is only available for client-side packets, and the output handlers
|
||||
* can only be applied to server-side packets.
|
||||
* @return The network manager.
|
||||
*/
|
||||
public NetworkMarker getNetworkMarker() {
|
||||
if (networkMarker == null) {
|
||||
if (isServerPacket()) {
|
||||
networkMarker = new NetworkMarker.EmptyBufferMarker(
|
||||
serverPacket ? ConnectionSide.SERVER_SIDE : ConnectionSide.CLIENT_SIDE);
|
||||
} else {
|
||||
throw new IllegalStateException("Add the option ListenerOptions.INTERCEPT_INPUT_BUFFER to your listener.");
|
||||
}
|
||||
}
|
||||
return networkMarker;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the network manager.
|
||||
* <p>
|
||||
* This method is internal - do not call.
|
||||
* @param networkMarker - the new network manager.
|
||||
*/
|
||||
public void setNetworkMarker(NetworkMarker networkMarker) {
|
||||
this.networkMarker = Preconditions.checkNotNull(networkMarker, "marker cannot be NULL");
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether or not the packet should be cancelled. Uncancelling is possible.
|
||||
* <p>
|
||||
* <b>Warning</b>: A cancelled packet should never be re-transmitted. Use the asynchronous
|
||||
* packet manager if you need to perform extensive processing. It should also be used
|
||||
* if you need to synchronize with the main thread.
|
||||
* <p>
|
||||
* This ensures that other plugins can work with the same packet.
|
||||
* <p>
|
||||
* An asynchronous listener can also delay a packet indefinitely without having to block its thread.
|
||||
*
|
||||
* @param cancel - TRUE if it should be cancelled, FALSE otherwise.
|
||||
*/
|
||||
@Override
|
||||
public void setCancelled(boolean cancel) {
|
||||
if (readOnly)
|
||||
throw new IllegalStateException("The packet event is read-only.");
|
||||
this.cancel = cancel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the player that has sent the packet or is recieving it.
|
||||
* @return The player associated with this event.
|
||||
*/
|
||||
public Player getPlayer() {
|
||||
return playerReference.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if this packet is filtered by every packet listener.
|
||||
* <p>
|
||||
* If not, it will only be intercepted by monitor packets.
|
||||
* @return TRUE if it is, FALSE otherwise.
|
||||
*/
|
||||
public boolean isFiltered() {
|
||||
return filtered;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not this packet was created by the server.
|
||||
* <p>
|
||||
* Most listeners can deduce this by noting which listener method was invoked.
|
||||
* @return TRUE if the packet was created by the server, FALSE if it was created by a client.
|
||||
*/
|
||||
public boolean isServerPacket() {
|
||||
return serverPacket;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the asynchronous marker.
|
||||
* <p>
|
||||
* If the packet is synchronous, this marker will be used to schedule an asynchronous event. In the following
|
||||
* asynchronous event, the marker is used to correctly pass the packet around to the different threads.
|
||||
* <p>
|
||||
* Note that if there are no asynchronous events that can receive this packet, the marker is NULL.
|
||||
* @return The current asynchronous marker, or NULL.
|
||||
*/
|
||||
public AsyncMarker getAsyncMarker() {
|
||||
return asyncMarker;
|
||||
}
|
||||
/**
|
||||
* Set the asynchronous marker.
|
||||
* <p>
|
||||
* If the marker is non-null at the end of an synchronous event processing, the packet will be scheduled
|
||||
* to be processed asynchronously with the given settings.
|
||||
* <p>
|
||||
* Note that if there are no asynchronous events that can receive this packet, the marker should be NULL.
|
||||
* @param asyncMarker - the new asynchronous marker, or NULL.
|
||||
* @throws IllegalStateException If the current event is asynchronous.
|
||||
*/
|
||||
public void setAsyncMarker(AsyncMarker asyncMarker) {
|
||||
if (isAsynchronous())
|
||||
throw new IllegalStateException("The marker is immutable for asynchronous events");
|
||||
if (readOnly)
|
||||
throw new IllegalStateException("The packet event is read-only.");
|
||||
this.asyncMarker = asyncMarker;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the current packet event is read only.
|
||||
* <p>
|
||||
* This is used to ensure that a monitor listener doesn't accidentally alter the state of the event. However,
|
||||
* it is still possible to modify the packet itself, as it would require too many resources to verify its integrity.
|
||||
* <p>
|
||||
* Thus, the packet is considered immutable if the packet event is read only.
|
||||
* @return TRUE if it is, FALSE otherwise.
|
||||
*/
|
||||
public boolean isReadOnly() {
|
||||
return readOnly;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the read-only state of this packet event.
|
||||
* <p>
|
||||
* This will be reset for every packet listener.
|
||||
* @param readOnly - TRUE if it is read-only, FALSE otherwise.
|
||||
*/
|
||||
public void setReadOnly(boolean readOnly) {
|
||||
this.readOnly = readOnly;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the packet event has been executed asynchronously or not.
|
||||
* @return TRUE if this packet event is asynchronous, FALSE otherwise.
|
||||
*/
|
||||
public boolean isAsynchronous() {
|
||||
return asynchronous;
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule a packet for sending or receiving after the current packet event is successful.
|
||||
* <p>
|
||||
* The packet will be added to {@link NetworkMarker#getScheduledPackets()}.
|
||||
* @param scheduled - the packet to transmit or receive.
|
||||
*/
|
||||
public void schedule(ScheduledPacket scheduled) {
|
||||
getNetworkMarker().getScheduledPackets().add(scheduled);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unschedule a specific packet.
|
||||
* @param scheduled - the scheduled packet.
|
||||
* @return TRUE if it was unscheduled, FALSE otherwise.
|
||||
*/
|
||||
public boolean unschedule(ScheduledPacket scheduled) {
|
||||
if (networkMarker != null) {
|
||||
return networkMarker.getScheduledPackets().remove(scheduled);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void writeObject(ObjectOutputStream output) throws IOException {
|
||||
// Default serialization
|
||||
output.defaultWriteObject();
|
||||
|
||||
// Write the name of the player (or NULL if it's not set)
|
||||
output.writeObject(playerReference.get() != null ? new SerializedOfflinePlayer(playerReference.get()) : null);
|
||||
}
|
||||
|
||||
private void readObject(ObjectInputStream input) throws ClassNotFoundException, IOException {
|
||||
// Default deserialization
|
||||
input.defaultReadObject();
|
||||
|
||||
final SerializedOfflinePlayer serialized = (SerializedOfflinePlayer) input.readObject();
|
||||
|
||||
// Better than nothing
|
||||
if (serialized != null) {
|
||||
// Store it, to prevent weak reference from cleaning up the reference
|
||||
offlinePlayer = serialized.getPlayer();
|
||||
playerReference = new WeakReference<Player>(offlinePlayer);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "PacketEvent[player=" + getPlayer() + ", packet=" + packet + "]";
|
||||
}
|
||||
}
|
||||
@@ -1,71 +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 org.bukkit.plugin.Plugin;
|
||||
|
||||
/**
|
||||
* Represents a listener that receives notifications when packets are sending or being received.
|
||||
* <p>
|
||||
* Use {@link PacketAdapter} for a simple wrapper around this interface.
|
||||
* @author Kristian
|
||||
*/
|
||||
public interface PacketListener {
|
||||
|
||||
/**
|
||||
* Invoked right before a packet is transmitted from the server to the client.
|
||||
* <p>
|
||||
* Note that the packet may be replaced, if needed.
|
||||
* <p>
|
||||
* This method is executed on the main thread in 1.6.4 and earlier, and thus the Bukkit API is safe to use.
|
||||
* <p>
|
||||
* In Minecraft 1.7.2 and later, this method MAY be executed asynchronously, but only if {@link ListenerOptions#ASYNC}
|
||||
* have been specified in the listener. This is off by default.
|
||||
* @param event - the packet that should be sent.
|
||||
*/
|
||||
public void onPacketSending(PacketEvent event);
|
||||
|
||||
/**
|
||||
* Invoked right before a received packet from a client is being processed.
|
||||
* <p>
|
||||
* <b>WARNING</b>: <br>
|
||||
* This method will be called <i>asynchronously</i>! You should synchronize with the main
|
||||
* thread using {@link org.bukkit.scheduler.BukkitScheduler#scheduleSyncDelayedTask(Plugin, Runnable, long) scheduleSyncDelayedTask}
|
||||
* if you need to call the Bukkit API.
|
||||
* @param event - the packet that has been received.
|
||||
*/
|
||||
public void onPacketReceiving(PacketEvent event);
|
||||
|
||||
/**
|
||||
* Retrieve which packets sent by the server this listener will observe.
|
||||
* @return List of server packets to observe, along with the priority.
|
||||
*/
|
||||
public ListeningWhitelist getSendingWhitelist();
|
||||
|
||||
/**
|
||||
* Retrieve which packets sent by the client this listener will observe.
|
||||
* @return List of server packets to observe, along with the priority.
|
||||
*/
|
||||
public ListeningWhitelist getReceivingWhitelist();
|
||||
|
||||
/**
|
||||
* Retrieve the plugin that created list packet listener.
|
||||
* @return The plugin, or NULL if not available.
|
||||
*/
|
||||
public Plugin getPlugin();
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
package com.comphenix.protocol.events;
|
||||
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
/**
|
||||
* Represents an adapter version of the output handler interface.
|
||||
* @author Kristian
|
||||
*/
|
||||
public abstract class PacketOutputAdapter implements PacketOutputHandler {
|
||||
private final Plugin plugin;
|
||||
private final ListenerPriority priority;
|
||||
|
||||
/**
|
||||
* Construct a new packet output adapter with the given values.
|
||||
* @param priority - the output handler priority.
|
||||
* @param plugin - the owner plugin.
|
||||
*/
|
||||
public PacketOutputAdapter(Plugin plugin, ListenerPriority priority) {
|
||||
this.priority = priority;
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Plugin getPlugin() {
|
||||
return plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListenerPriority getPriority() {
|
||||
return priority;
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
package com.comphenix.protocol.events;
|
||||
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
/**
|
||||
* Represents a custom packet serializer onto the network stream.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public interface PacketOutputHandler {
|
||||
/**
|
||||
* Retrieve the priority that decides the order each network handler is allowed to manipulate the output buffer.
|
||||
* <p>
|
||||
* Higher priority is executed before lower.
|
||||
* @return The handler priority.
|
||||
*/
|
||||
public ListenerPriority getPriority();
|
||||
|
||||
/**
|
||||
* The plugin that owns this output handler.
|
||||
* @return The owner plugin.
|
||||
*/
|
||||
public Plugin getPlugin();
|
||||
|
||||
/**
|
||||
* Invoked when a given packet is to be written to the output stream.
|
||||
* <p>
|
||||
* Note that the buffer is initially filled with the output from the default write method.
|
||||
* <p>
|
||||
* In Minecraft 1.6.4, the header is always excluded, whereas it MUST be included in Minecraft 1.7.2. Call
|
||||
* {@link NetworkMarker#requireOutputHeader()} to determine this.
|
||||
* @param event - the packet that will be outputted.
|
||||
* @param buffer - the data that is currently scheduled to be outputted.
|
||||
* @return The modified byte array to write. NULL is not permitted.
|
||||
*/
|
||||
public byte[] handle(PacketEvent event, byte[] buffer);
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package com.comphenix.protocol.events;
|
||||
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
/**
|
||||
* Represents a packet listener that is invoked after a packet has been sent or received.
|
||||
* @author Kristian
|
||||
*/
|
||||
public interface PacketPostListener {
|
||||
/**
|
||||
* Retrieve the plugin this listener belongs to.
|
||||
* @return The assoicated plugin.
|
||||
*/
|
||||
public Plugin getPlugin();
|
||||
|
||||
/**
|
||||
* Invoked after a packet has been sent or received.
|
||||
* <p>
|
||||
* Note that this is invoked asynchronously.
|
||||
* @param event - the packet event.
|
||||
*/
|
||||
public void onPostEvent(PacketEvent event);
|
||||
}
|
||||
@@ -1,147 +0,0 @@
|
||||
package com.comphenix.protocol.events;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import com.comphenix.protocol.PacketStream;
|
||||
import com.comphenix.protocol.PacketType.Sender;
|
||||
import com.comphenix.protocol.ProtocolLibrary;
|
||||
import com.google.common.base.Objects;
|
||||
import com.google.common.base.Preconditions;
|
||||
|
||||
/**
|
||||
* Represents a packet that is scheduled for transmission at a later stage.
|
||||
* @author Kristian
|
||||
*/
|
||||
public class ScheduledPacket {
|
||||
protected PacketContainer packet;
|
||||
protected Player target;
|
||||
protected boolean filtered;
|
||||
|
||||
/**
|
||||
* Construct a new scheduled packet.
|
||||
* <p>
|
||||
* Note that the sender is infered from the packet type.
|
||||
* @param packet - the packet.
|
||||
* @param target - the target player.
|
||||
* @param filtered - whether or not to
|
||||
*/
|
||||
public ScheduledPacket(PacketContainer packet, Player target, boolean filtered) {
|
||||
setPacket(packet);
|
||||
setTarget(target);
|
||||
setFiltered(filtered);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new scheduled packet that will not be processed by any packet listeners (except MONITOR).
|
||||
* @param packet - the packet.
|
||||
* @param target - the target player.
|
||||
* @return The scheduled packet.
|
||||
*/
|
||||
public static ScheduledPacket fromSilent(PacketContainer packet, Player target) {
|
||||
return new ScheduledPacket(packet, target, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new scheduled packet that will be processed by any packet listeners.
|
||||
* @param packet - the packet.
|
||||
* @param target - the target player.
|
||||
* @return The scheduled packet.
|
||||
*/
|
||||
public static ScheduledPacket fromFiltered(PacketContainer packet, Player target) {
|
||||
return new ScheduledPacket(packet, target, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the packet that will be sent or transmitted.
|
||||
* @return The sent or received packet.
|
||||
*/
|
||||
public PacketContainer getPacket() {
|
||||
return packet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the packet that will be sent or transmitted.
|
||||
* @param packet - the new packet, cannot be NULL.
|
||||
*/
|
||||
public void setPacket(PacketContainer packet) {
|
||||
this.packet = Preconditions.checkNotNull(packet, "packet cannot be NULL");
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the target player.
|
||||
* @return The target player.
|
||||
*/
|
||||
public Player getTarget() {
|
||||
return target;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the target player.
|
||||
* @param target - the new target, cannot be NULL.
|
||||
*/
|
||||
public void setTarget(Player target) {
|
||||
this.target = Preconditions.checkNotNull(target, "target cannot be NULL");
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if this packet will be processed by any of the packet listeners.
|
||||
* @return TRUE if it will, FALSE otherwise.
|
||||
*/
|
||||
public boolean isFiltered() {
|
||||
return filtered;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether or not this packet will be processed by packet listeners (except MONITOR listeners).
|
||||
* @param filtered - TRUE if it should be processed by listeners, FALSE otherwise.
|
||||
*/
|
||||
public void setFiltered(boolean filtered) {
|
||||
this.filtered = filtered;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the sender of this packet.
|
||||
* @return The sender.
|
||||
*/
|
||||
public Sender getSender() {
|
||||
return packet.getType().getSender();
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule the packet transmission or reception.
|
||||
*/
|
||||
public void schedule() {
|
||||
schedule(ProtocolLibrary.getProtocolManager());
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule the packet transmission or reception.
|
||||
* @param stream - the packet stream.
|
||||
*/
|
||||
public void schedule(PacketStream stream) {
|
||||
Preconditions.checkNotNull(stream, "stream cannot be NULL");
|
||||
|
||||
try {
|
||||
if (getSender() == Sender.CLIENT) {
|
||||
stream.recieveClientPacket(getTarget(), getPacket(), isFiltered());
|
||||
} else {
|
||||
stream.sendServerPacket(getTarget(), getPacket(), isFiltered());
|
||||
}
|
||||
} catch (InvocationTargetException e) {
|
||||
throw new RuntimeException("Cannot send packet " + this + " to " + stream);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException("Cannot send packet " + this + " to " + stream);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return Objects.toStringHelper(this)
|
||||
.add("packet", packet)
|
||||
.add("target", target)
|
||||
.add("filtered", filtered)
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
@@ -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,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.injector;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import com.comphenix.protocol.ProtocolLibrary;
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.error.Report;
|
||||
import com.comphenix.protocol.error.ReportType;
|
||||
import com.comphenix.protocol.injector.PacketConstructor.Unwrapper;
|
||||
import com.comphenix.protocol.reflect.FieldUtils;
|
||||
import com.comphenix.protocol.reflect.instances.DefaultInstances;
|
||||
import com.google.common.primitives.Primitives;
|
||||
|
||||
/**
|
||||
* Represents an object capable of converting wrapped Bukkit objects into NMS objects.
|
||||
* <p>
|
||||
* Typical conversions include:
|
||||
* <ul>
|
||||
* <li>org.bukkit.entity.Player to net.minecraft.server.EntityPlayer</li>
|
||||
* <li>org.bukkit.World to net.minecraft.server.WorldServer</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public class BukkitUnwrapper implements Unwrapper {
|
||||
private static BukkitUnwrapper DEFAULT;
|
||||
|
||||
public static final ReportType REPORT_ILLEGAL_ARGUMENT = new ReportType("Illegal argument.");
|
||||
public static final ReportType REPORT_SECURITY_LIMITATION = new ReportType("Security limitation.");
|
||||
public static final ReportType REPORT_CANNOT_FIND_UNWRAP_METHOD = new ReportType("Cannot find method.");
|
||||
|
||||
public static final ReportType REPORT_CANNOT_READ_FIELD_HANDLE = new ReportType("Cannot read field 'handle'.");
|
||||
|
||||
private static Map<Class<?>, Unwrapper> unwrapperCache = new ConcurrentHashMap<Class<?>, Unwrapper>();
|
||||
|
||||
// The current error reporter
|
||||
private final ErrorReporter reporter;
|
||||
|
||||
/**
|
||||
* Retrieve the default instance of the Bukkit unwrapper.
|
||||
* @return The default instance.
|
||||
*/
|
||||
public static BukkitUnwrapper getInstance() {
|
||||
ErrorReporter currentReporter = ProtocolLibrary.getErrorReporter();
|
||||
|
||||
// Also recreate the unwrapper if the error reporter has changed
|
||||
if (DEFAULT == null || DEFAULT.reporter != currentReporter) {
|
||||
DEFAULT = new BukkitUnwrapper(currentReporter);
|
||||
}
|
||||
return DEFAULT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new Bukkit unwrapper with ProtocolLib's default error reporter.
|
||||
*/
|
||||
public BukkitUnwrapper() {
|
||||
this(ProtocolLibrary.getErrorReporter());
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new Bukkit unwrapper with the given error reporter.
|
||||
* @param reporter - the error reporter to use.
|
||||
*/
|
||||
public BukkitUnwrapper(ErrorReporter reporter) {
|
||||
this.reporter = reporter;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Object unwrapItem(Object wrappedObject) {
|
||||
// Special case
|
||||
if (wrappedObject == null)
|
||||
return null;
|
||||
Class<?> currentClass = PacketConstructor.getClass(wrappedObject);
|
||||
|
||||
// No need to unwrap primitives
|
||||
if (currentClass.isPrimitive() || currentClass.equals(String.class))
|
||||
return null;
|
||||
|
||||
// Next, check for types that doesn't have a getHandle()
|
||||
if (wrappedObject instanceof Collection) {
|
||||
return handleCollection((Collection<Object>) wrappedObject);
|
||||
} else if (Primitives.isWrapperType(currentClass) || wrappedObject instanceof String) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Unwrapper specificUnwrapper = getSpecificUnwrapper(currentClass);
|
||||
|
||||
// Retrieve the handle
|
||||
if (specificUnwrapper != null)
|
||||
return specificUnwrapper.unwrapItem(wrappedObject);
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
// Handle a collection of items
|
||||
private Object handleCollection(Collection<Object> wrappedObject) {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Collection<Object> copy = DefaultInstances.DEFAULT.getDefault(wrappedObject.getClass());
|
||||
|
||||
if (copy != null) {
|
||||
// Unwrap every element
|
||||
for (Object element : wrappedObject) {
|
||||
copy.add(unwrapItem(element));
|
||||
}
|
||||
return copy;
|
||||
|
||||
} else {
|
||||
// Impossible
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a cached class unwrapper for the given class.
|
||||
* @param type - the type of the class.
|
||||
* @return An unwrapper for the given class.
|
||||
*/
|
||||
private Unwrapper getSpecificUnwrapper(final Class<?> type) {
|
||||
// See if we're already determined this
|
||||
if (unwrapperCache.containsKey(type)) {
|
||||
// We will never remove from the cache, so this ought to be thread safe
|
||||
return unwrapperCache.get(type);
|
||||
}
|
||||
|
||||
try {
|
||||
final Method find = type.getMethod("getHandle");
|
||||
|
||||
// It's thread safe, as getMethod should return the same handle
|
||||
Unwrapper methodUnwrapper = new Unwrapper() {
|
||||
@Override
|
||||
public Object unwrapItem(Object wrappedObject) {
|
||||
try {
|
||||
if (wrappedObject instanceof Class)
|
||||
return checkClass((Class<?>) wrappedObject, type, find.getReturnType());
|
||||
return find.invoke(wrappedObject);
|
||||
|
||||
} catch (IllegalArgumentException e) {
|
||||
reporter.reportDetailed(this,
|
||||
Report.newBuilder(REPORT_ILLEGAL_ARGUMENT).error(e).callerParam(wrappedObject, find)
|
||||
);
|
||||
} catch (IllegalAccessException e) {
|
||||
// Should not occur either
|
||||
return null;
|
||||
} catch (InvocationTargetException e) {
|
||||
// This is really bad
|
||||
throw new RuntimeException("Minecraft error.", e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
unwrapperCache.put(type, methodUnwrapper);
|
||||
return methodUnwrapper;
|
||||
|
||||
} catch (SecurityException e) {
|
||||
reporter.reportDetailed(this,
|
||||
Report.newBuilder(REPORT_SECURITY_LIMITATION).error(e).callerParam(type)
|
||||
);
|
||||
} catch (NoSuchMethodException e) {
|
||||
// Try getting the field unwrapper too
|
||||
Unwrapper fieldUnwrapper = getFieldUnwrapper(type);
|
||||
|
||||
if (fieldUnwrapper != null)
|
||||
return fieldUnwrapper;
|
||||
else
|
||||
reporter.reportDetailed(this,
|
||||
Report.newBuilder(REPORT_CANNOT_FIND_UNWRAP_METHOD).error(e).callerParam(type));
|
||||
}
|
||||
|
||||
// Default method
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a cached unwrapper using the handle field.
|
||||
* @param type - a cached field unwrapper.
|
||||
* @return The cached field unwrapper.
|
||||
*/
|
||||
private Unwrapper getFieldUnwrapper(final Class<?> type) {
|
||||
final Field find = FieldUtils.getField(type, "handle", true);
|
||||
|
||||
// See if we succeeded
|
||||
if (find != null) {
|
||||
Unwrapper fieldUnwrapper = new Unwrapper() {
|
||||
@Override
|
||||
public Object unwrapItem(Object wrappedObject) {
|
||||
try {
|
||||
if (wrappedObject instanceof Class)
|
||||
return checkClass((Class<?>) wrappedObject, type, find.getType());
|
||||
return FieldUtils.readField(find, wrappedObject, true);
|
||||
} catch (IllegalAccessException e) {
|
||||
reporter.reportDetailed(this,
|
||||
Report.newBuilder(REPORT_CANNOT_READ_FIELD_HANDLE).error(e).callerParam(wrappedObject, find)
|
||||
);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
unwrapperCache.put(type, fieldUnwrapper);
|
||||
return fieldUnwrapper;
|
||||
|
||||
} else {
|
||||
// Inform about this too
|
||||
reporter.reportDetailed(this,
|
||||
Report.newBuilder(REPORT_CANNOT_READ_FIELD_HANDLE).callerParam(find)
|
||||
);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static Class<?> checkClass(Class<?> input, Class<?> expected, Class<?> result) {
|
||||
if (expected.isAssignableFrom(input)) {
|
||||
return result;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -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,56 +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;
|
||||
|
||||
/**
|
||||
* The current player phase. This is used to limit the number of different injections.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public enum GamePhase {
|
||||
/**
|
||||
* Only listen for packets sent or received before a player has logged in.
|
||||
*/
|
||||
LOGIN,
|
||||
|
||||
/**
|
||||
* Only listen for packets sent or received after a player has logged in.
|
||||
*/
|
||||
PLAYING,
|
||||
|
||||
/**
|
||||
* Listen for every sent and received packet.
|
||||
*/
|
||||
BOTH;
|
||||
|
||||
/**
|
||||
* Determine if the current value represents the login phase.
|
||||
* @return TRUE if it does, FALSE otherwise.
|
||||
*/
|
||||
public boolean hasLogin() {
|
||||
return this == LOGIN || this == BOTH;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the current value represents the playing phase.
|
||||
* @return TRUE if it does, FALSE otherwise.
|
||||
*/
|
||||
public boolean hasPlaying() {
|
||||
return this == PLAYING || this == BOTH;
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
package com.comphenix.protocol.injector;
|
||||
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.bukkit.plugin.PluginManager;
|
||||
|
||||
import com.comphenix.protocol.ProtocolManager;
|
||||
import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks;
|
||||
|
||||
/**
|
||||
* Yields access to the internal hook configuration.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public interface InternalManager extends ProtocolManager {
|
||||
/**
|
||||
* Retrieves how the server packets are read.
|
||||
* @return Injection method for reading server packets.
|
||||
*/
|
||||
public PlayerInjectHooks getPlayerHook();
|
||||
|
||||
/**
|
||||
* Sets how the server packets are read.
|
||||
* @param playerHook - the new injection method for reading server packets.
|
||||
*/
|
||||
public void setPlayerHook(PlayerInjectHooks playerHook);
|
||||
|
||||
/**
|
||||
* Register this protocol manager on Bukkit.
|
||||
* @param manager - Bukkit plugin manager that provides player join/leave events.
|
||||
* @param plugin - the parent plugin.
|
||||
*/
|
||||
public void registerEvents(PluginManager manager, final Plugin plugin);
|
||||
|
||||
/**
|
||||
* Called when ProtocolLib is closing.
|
||||
*/
|
||||
public void close();
|
||||
|
||||
/**
|
||||
* Determine if debug mode is enabled.
|
||||
* @return TRUE if it is, FALSE otherwise.
|
||||
*/
|
||||
public boolean isDebug();
|
||||
|
||||
/**
|
||||
* Set whether or not debug mode is enabled.
|
||||
* @param debug - TRUE if it is, FALSE otherwise.
|
||||
*/
|
||||
public void setDebug(boolean debug);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Loaded 100 of 677 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user