Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
11cb603130 | ||
|
|
0dd20f8d08 | ||
|
|
7630007833 | ||
|
|
de8bb2bd66 | ||
|
|
83c2943b48 | ||
|
|
c368251954 | ||
|
|
39998eedf8 | ||
|
|
8e8cf54ddd | ||
|
|
5a5cd77bac | ||
|
|
125834205a | ||
|
|
0c705ef59d | ||
|
|
b17af68716 | ||
|
|
3e69dd69ed | ||
|
|
395d77e721 | ||
|
|
e84fca699d | ||
|
|
880cf29fc7 | ||
|
|
7dff86cb48 | ||
|
|
4bb917e596 | ||
|
|
bd28035024 | ||
|
|
c40d9c1e6c | ||
|
|
cd7894f9f1 | ||
|
|
245433b29a | ||
|
|
9433ea5e48 | ||
|
|
0d3867c6f1 | ||
|
|
4e16792450 | ||
|
|
a2452cba67 | ||
|
|
fc28a9242c | ||
|
|
368ed76677 | ||
|
|
e6c87129bd | ||
|
|
3166241397 | ||
|
|
8576b7a057 | ||
|
|
0b63ae0354 | ||
|
|
0159752687 | ||
|
|
7226e199e5 | ||
|
|
4c97c3c7ce | ||
|
|
ae47ae3b89 | ||
|
|
900b31e0c9 | ||
|
|
f77ff6a0a2 | ||
|
|
714e631dd0 | ||
|
|
b50133d322 | ||
|
|
0dd37bb801 | ||
|
|
5e703d91db | ||
|
|
c7dab69cba | ||
|
|
7861a57d37 | ||
|
|
7b9e0cdb65 | ||
|
|
74b1d93209 | ||
|
|
f825acb4d6 | ||
|
|
47632e1fb1 | ||
|
|
b32d0d5fcd | ||
|
|
92fabb31b2 | ||
|
|
d012296ee0 | ||
|
|
fbc596d57b | ||
|
|
1b4a8ec0aa | ||
|
|
63babc4ca1 | ||
|
|
3f5abe8719 | ||
|
|
84b502cba7 | ||
|
|
9fe5df3cfe | ||
|
|
e0f7c72e18 | ||
|
|
b1b5408c53 | ||
|
|
62ebcbb66d | ||
|
|
cb0fdcff9d | ||
|
|
ee3db1e25a | ||
|
|
165b4d3126 | ||
|
|
25bdcd6bad | ||
|
|
49fb3339be | ||
|
|
325b91fb1f | ||
|
|
9ce277cd76 | ||
|
|
6af2d37319 | ||
|
|
273b470140 | ||
|
|
bba1054f97 | ||
|
|
241003b56a | ||
|
|
e6b5d53234 | ||
|
|
c3dacd06aa | ||
|
|
6e553bc59d | ||
|
|
f5f3eef988 | ||
|
|
4e7bff40c0 | ||
|
|
06eac8f67b | ||
|
|
2c5b3a2a5b | ||
|
|
24d5b1739a | ||
|
|
0d18bd2734 | ||
|
|
423cc320f6 | ||
|
|
594b7ad075 | ||
|
|
270cdfca2f | ||
|
|
20247747b5 | ||
|
|
a718cc9067 | ||
|
|
9f2141331a | ||
|
|
80fbd3ebc2 | ||
|
|
e0d4110cd2 | ||
|
|
99dea686a9 | ||
|
|
eca168edcf | ||
|
|
f89b71c5b4 | ||
|
|
abfb1ce9ac | ||
|
|
27f3f76c5e | ||
|
|
36313eabb1 | ||
|
|
1da65317c4 | ||
|
|
e4d1f8d992 | ||
|
|
166c615725 | ||
|
|
0cc182f93d | ||
|
|
0ed76d7228 | ||
|
|
ce3069d490 | ||
|
|
e31599073f | ||
|
|
a2c9240011 | ||
|
|
0958c042c9 | ||
|
|
1c6641748b | ||
|
|
889d1f6bc4 | ||
|
|
c981ba8d62 | ||
|
|
5d80173f44 | ||
|
|
a5689a5550 | ||
|
|
16111449b4 | ||
|
|
d96ed73e82 | ||
|
|
992be51646 | ||
|
|
0abe72d72b | ||
|
|
24e543b33b | ||
|
|
d44741dd17 | ||
|
|
addfacb19c | ||
|
|
2c743e1c89 | ||
|
|
ae8ba16a7e | ||
|
|
16c85ac8b4 | ||
|
|
37123c0aa1 | ||
|
|
923f4f4c4a | ||
|
|
0b4ce155ac | ||
|
|
cb5633aba8 | ||
|
|
a078ddb6e6 | ||
|
|
c7b9600c21 | ||
|
|
ac14843d96 | ||
|
|
9a0c421b52 | ||
|
|
f72a6d8695 | ||
|
|
7291837b9d | ||
|
|
b0662b05c5 | ||
|
|
0d4071f11e | ||
|
|
115b5e8daa | ||
|
|
66be4900a3 | ||
|
|
a3784b3cf1 | ||
|
|
ac54cef2fd | ||
|
|
391970b185 | ||
|
|
bfb0683b1f | ||
|
|
704fe60bd1 | ||
|
|
1d711315e4 | ||
|
|
8846b28469 | ||
|
|
3dda5c6a0a | ||
|
|
5e5eff5e5f | ||
|
|
dd5bfd5f16 | ||
|
|
266e9e47cc | ||
|
|
489c72418d | ||
|
|
6ddbbfb428 | ||
|
|
09e2312d80 | ||
|
|
a1b989cb3a | ||
|
|
a748a95197 | ||
|
|
79da4f92a1 | ||
|
|
5605c3817e | ||
|
|
10f9c4c9f9 | ||
|
|
7d5e1b9c34 | ||
|
|
687db088ec | ||
|
|
6f3329cae6 | ||
|
|
2ed358405d | ||
|
|
0af57b853e | ||
|
|
776eeb898f | ||
|
|
5f4867eb1f | ||
|
|
f145d32016 | ||
|
|
821c3a1d48 | ||
|
|
2a51e16ef0 | ||
|
|
4c1fac9aa3 | ||
|
|
a60e2522c9 | ||
|
|
74297da901 | ||
|
|
0bc1b7ecaf | ||
|
|
f6e0bcf6f5 | ||
|
|
2c017c6dac | ||
|
|
58302f2477 | ||
|
|
37d4fadbf1 | ||
|
|
701885e4eb | ||
|
|
f14623897b | ||
|
|
9a9c5446cc | ||
|
|
1a368299b4 | ||
|
|
d6b94e658a | ||
|
|
dabbe3fca9 | ||
|
|
5b9acb72fb | ||
|
|
30bff505c4 | ||
|
|
fa1d419b66 | ||
|
|
ea734965bc | ||
|
|
954a37a261 | ||
|
|
98eb6958e0 | ||
|
|
18a45aace0 | ||
|
|
1c8e3b5ee3 | ||
|
|
d7087c2da5 | ||
|
|
7b08bb9e1b | ||
|
|
6abd0cfdaa | ||
|
|
3ac9793553 | ||
|
|
dfc1808aca | ||
|
|
30008ddd2c | ||
|
|
2074c2cab1 | ||
|
|
9cab57fa1c | ||
|
|
c2fdd15677 | ||
|
|
a501e50237 | ||
|
|
38cf8f4888 | ||
|
|
1f6b873f84 | ||
|
|
d5a63e8e38 | ||
|
|
670e8f034c | ||
|
|
b9818cace7 | ||
|
|
bd8f2dcc41 | ||
|
|
9b88847c28 | ||
|
|
075ef28b5e | ||
|
|
077ea6ea08 |
No files matched your search
@@ -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,11 @@
|
||||
_Follow this template except for feature requests. Use pastebin when providing /protocol dump and any relevant errors._
|
||||
|
||||
Make sure you've done the following:
|
||||
- [ ] You're using the latest build for your server version
|
||||
- [ ] This isn't an issue caused by another plugin
|
||||
- [ ] You've checked for duplicate issues
|
||||
- [ ] You didn't use `/reload`
|
||||
|
||||
Debug paste link:
|
||||
|
||||
Description and relevant errors:
|
||||
@@ -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,30 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Version Info**
|
||||
Provide your ProtocolLib install info with `/protocol dump` through pastebin.
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
@@ -0,0 +1,26 @@
|
||||
name: ProtocolLib full build lifecycle
|
||||
|
||||
on:
|
||||
- push
|
||||
- pull_request
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Setup java
|
||||
uses: actions/setup-java@v2
|
||||
with:
|
||||
distribution: 'adopt'
|
||||
java-version: '16'
|
||||
check-latest: true
|
||||
|
||||
- name: Run maven build lifecycle
|
||||
run: mvn --batch-mode clean test package
|
||||
|
||||
- name: Upload plugin file
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: ProtocolLib
|
||||
path: target/ProtocolLib.jar
|
||||
+25
-8
@@ -2,21 +2,25 @@
|
||||
## Eclipse
|
||||
#################
|
||||
|
||||
*.pydevproject
|
||||
# Generic project files
|
||||
.project
|
||||
.metadata
|
||||
.classpath
|
||||
.settings/
|
||||
bin/
|
||||
.idea/
|
||||
*.iml
|
||||
.ipr
|
||||
.iws
|
||||
|
||||
*.pydevproject
|
||||
.metadata
|
||||
tmp/
|
||||
*.tmp
|
||||
*.bak
|
||||
*.swp
|
||||
*~.nib
|
||||
local.properties
|
||||
.classpath
|
||||
.settings/
|
||||
.loadpath
|
||||
target/
|
||||
.gitignore
|
||||
|
||||
# External tool builders
|
||||
.externalToolBuilders/
|
||||
@@ -30,6 +34,12 @@ target/
|
||||
# PDT-specific
|
||||
.buildpath
|
||||
|
||||
#################
|
||||
## Maven
|
||||
#################
|
||||
|
||||
target/
|
||||
dependency-reduced-pom.xml
|
||||
|
||||
#################
|
||||
## Visual Studio
|
||||
@@ -155,11 +165,18 @@ pip-log.txt
|
||||
.coverage
|
||||
.tox
|
||||
|
||||
#Translations
|
||||
# Translations
|
||||
*.mo
|
||||
|
||||
#Mr Developer
|
||||
# Mr Developer
|
||||
.mr.developer.cfg
|
||||
|
||||
# Mac crap
|
||||
.DS_Store
|
||||
|
||||
# Log4J files
|
||||
logs/
|
||||
|
||||
.gradle/
|
||||
build/
|
||||
logs/
|
||||
@@ -1,165 +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
|
||||
@@ -1,72 +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>
|
||||
<groupId>com.comphenix.itemdisguise</groupId>
|
||||
<artifactId>ItemDisguise</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<name>Item Disguise</name>
|
||||
<description>Change the appearance of inventory items.</description>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>cp1252</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>bukkit-rep</id>
|
||||
<url>http://repo.bukkit.org/content/groups/public</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>comphenix-rep</id>
|
||||
<name>Comphenix Maven Releases</name>
|
||||
<url>http://repo.comphenix.net/content/groups/public</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<build>
|
||||
<sourceDirectory>src/main/java</sourceDirectory>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>src/main/java</directory>
|
||||
<excludes>
|
||||
<exclude>**/*.java</exclude>
|
||||
</excludes>
|
||||
</resource>
|
||||
</resources>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>2.3.2</version>
|
||||
<configuration>
|
||||
<source>1.6</source>
|
||||
<target>1.6</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<scm>
|
||||
<connection>scm:git:git://github.com/aadnk/ProtocolLib.git</connection>
|
||||
<developerConnection>scm:git:git@github.com:aadnk/ProtocolLib.git</developerConnection>
|
||||
<url>https://github.com/aadnk/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>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.bukkit</groupId>
|
||||
<artifactId>bukkit</artifactId>
|
||||
<version>1.3.2-R1.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.comphenix.protocol</groupId>
|
||||
<artifactId>ProtocolLib</artifactId>
|
||||
<version>1.3.2</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
-122
@@ -1,122 +0,0 @@
|
||||
/*
|
||||
* ItemDisguise - A simple Bukkit plugin that illustrates how to use ProtocolLib.
|
||||
* 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.itemdisguise;
|
||||
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.enchantments.Enchantment;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import com.comphenix.protocol.ProtocolManager;
|
||||
import com.comphenix.protocol.events.ConnectionSide;
|
||||
import com.comphenix.protocol.events.PacketAdapter;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.reflect.FieldAccessException;
|
||||
|
||||
public class HideEnchantmentsListener {
|
||||
|
||||
private final Server server;
|
||||
private final Logger logger;
|
||||
|
||||
public HideEnchantmentsListener(Server server, Logger logger) {
|
||||
this.server = server;
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
public void addListener(ProtocolManager protocolManager, JavaPlugin plugin) {
|
||||
|
||||
// Hide all enchantments
|
||||
protocolManager.addPacketListener(new PacketAdapter(plugin, ConnectionSide.SERVER_SIDE, 0x67, 0x68, 0x3E) {
|
||||
@Override
|
||||
public void onPacketSending(PacketEvent event) {
|
||||
PacketContainer packet = event.getPacket();
|
||||
|
||||
try {
|
||||
// Item packets
|
||||
switch (event.getPacketID()) {
|
||||
case 0x67: // Set slot
|
||||
removeEnchantments(packet.getItemModifier().read(0));
|
||||
break;
|
||||
|
||||
case 0x68: // Set Window Items
|
||||
ItemStack[] elements = packet.getItemArrayModifier().read(0);
|
||||
|
||||
for (int i = 0; i < elements.length; i++) {
|
||||
if (elements[i] != null) {
|
||||
removeEnchantments(elements[i]);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x3E: // Sound effect
|
||||
event.setCancelled(true);
|
||||
break;
|
||||
}
|
||||
|
||||
} catch (FieldAccessException e) {
|
||||
logger.log(Level.SEVERE, "Couldn't access field.", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Censor
|
||||
protocolManager.addPacketListener(new PacketAdapter(plugin, ConnectionSide.CLIENT_SIDE, 0x3) {
|
||||
@Override
|
||||
public void onPacketReceiving(PacketEvent event) {
|
||||
if (event.getPacketID() == 0x3) {
|
||||
try {
|
||||
String message = event.getPacket().
|
||||
getSpecificModifier(String.class).read(0);
|
||||
|
||||
if (message.contains("shit") || message.contains("fuck")) {
|
||||
event.setCancelled(true);
|
||||
event.getPlayer().sendMessage("Bad manners!");
|
||||
}
|
||||
|
||||
} catch (FieldAccessException e) {
|
||||
logger.log(Level.SEVERE, "Couldn't access field.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public Server getServer() {
|
||||
return server;
|
||||
}
|
||||
|
||||
public void removeListener(ProtocolManager protocolManager, JavaPlugin plugin) {
|
||||
// Just remove every adapter with this plugin
|
||||
protocolManager.removePacketListeners(plugin);
|
||||
}
|
||||
|
||||
private void removeEnchantments(ItemStack stack) {
|
||||
if (stack == null)
|
||||
return;
|
||||
|
||||
Object[] copy = stack.getEnchantments().keySet().toArray();
|
||||
|
||||
for (Object enchantment : copy) {
|
||||
stack.removeEnchantment((Enchantment) enchantment);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
/*
|
||||
* ItemDisguise - A simple Bukkit plugin that illustrates how to use ProtocolLib.
|
||||
* 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.itemdisguise;
|
||||
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import com.comphenix.protocol.ProtocolLibrary;
|
||||
import com.comphenix.protocol.ProtocolManager;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
|
||||
public class ItemDisguiseMod extends JavaPlugin {
|
||||
|
||||
private ProtocolManager protocolManager;
|
||||
private Logger logger;
|
||||
|
||||
private HideEnchantmentsListener enchantmentsListener;
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
|
||||
logger = getLoggerSafely();
|
||||
protocolManager = ProtocolLibrary.getProtocolManager();
|
||||
|
||||
enchantmentsListener = new HideEnchantmentsListener(getServer(), logger);
|
||||
enchantmentsListener.addListener(protocolManager, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
enchantmentsListener.removeListener(protocolManager, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
if (sender instanceof Player) {
|
||||
|
||||
Player player = (Player) sender;
|
||||
|
||||
if (label.equalsIgnoreCase("explosion")) {
|
||||
PacketContainer fakeExplosion = protocolManager.createPacket(0x3C);
|
||||
|
||||
// Set the coordinates
|
||||
try {
|
||||
fakeExplosion.getSpecificModifier(double.class).
|
||||
write(0, player.getLocation().getX()).
|
||||
write(1, player.getLocation().getY()).
|
||||
write(2, player.getLocation().getZ());
|
||||
fakeExplosion.getSpecificModifier(float.class).
|
||||
write(0, 3.0F);
|
||||
|
||||
protocolManager.sendServerPacket(player, fakeExplosion);
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get the Bukkit logger first, before we try to create our own
|
||||
private Logger getLoggerSafely() {
|
||||
|
||||
Logger log = null;
|
||||
|
||||
try {
|
||||
log = getLogger();
|
||||
} catch (Throwable e) {
|
||||
// We'll handle it
|
||||
}
|
||||
|
||||
if (log == null)
|
||||
log = Logger.getLogger("Minecraft");
|
||||
return log;
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
name: ItemDisguiseMod
|
||||
version: 1.0.0
|
||||
description: Change the appearance of inventory items.
|
||||
author: Comphenix
|
||||
website: http://www.comphenix.net/ItemDisguise
|
||||
|
||||
main: com.comphenix.itemdisguise.ItemDisguiseMod
|
||||
depends: [ProtocolLib]
|
||||
database: false
|
||||
|
||||
commands:
|
||||
explosion:
|
||||
description: Creates a fake explosion around the caller.
|
||||
usage: /<command>
|
||||
@@ -1,165 +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
|
||||
@@ -1,375 +0,0 @@
|
||||
package com.comphenix.tinyprotocol;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
|
||||
/**
|
||||
* An utility class that simplifies reflection in Bukkit plugins.
|
||||
* @author Kristian
|
||||
*/
|
||||
public final class Reflection {
|
||||
/**
|
||||
* An interface for invoking a specific constructor.
|
||||
*/
|
||||
public interface ConstructorInvoker {
|
||||
/**
|
||||
* Invoke a constructor for a specific class.
|
||||
* @param arguments - the arguments to pass to the constructor.
|
||||
* @return The constructed object.
|
||||
*/
|
||||
public Object invoke(Object... arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* An interface for invoking a specific method.
|
||||
*/
|
||||
public interface MethodInvoker {
|
||||
/**
|
||||
* Invoke a method on a specific target object.
|
||||
* @param target - the target object, or NULL for a static method.
|
||||
* @param arguments - the arguments to pass to the method.
|
||||
* @return The return value, or NULL if is void.
|
||||
*/
|
||||
public Object invoke(Object target, Object... arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* An interface for retrieving the field content.
|
||||
* @param <T> - field type.
|
||||
*/
|
||||
public interface FieldAccessor<T> {
|
||||
/**
|
||||
* Retrieve the content of a field.
|
||||
* @param target - the target object, or NULL for a static field.
|
||||
* @return The value of the field.
|
||||
*/
|
||||
public T get(Object target);
|
||||
|
||||
/**
|
||||
* Set the content of a field.
|
||||
* @param target - the target object, or NULL for a static field.
|
||||
* @param value - the new value of the field.
|
||||
*/
|
||||
public void set(Object target, Object value);
|
||||
|
||||
/**
|
||||
* Determine if the given object has this field.
|
||||
* @param target - the object to test.
|
||||
* @return TRUE if it does, FALSE otherwise.
|
||||
*/
|
||||
public boolean hasField(Object target);
|
||||
}
|
||||
|
||||
// Deduce the net.minecraft.server.v* package
|
||||
private static String OBC_PREFIX = Bukkit.getServer().getClass().getPackage().getName();
|
||||
private static String NMS_PREFIX = OBC_PREFIX.replace("org.bukkit.craftbukkit", "net.minecraft.server");
|
||||
private static String VERSION = OBC_PREFIX.replace("org.bukkit.craftbukkit", "").replace(".", "");
|
||||
|
||||
// Variable replacement
|
||||
private static Pattern MATCH_VARIABLE = Pattern.compile("\\{([^\\}]+)\\}");
|
||||
|
||||
private Reflection() {
|
||||
// Seal class
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a field accessor for a specific field type and name.
|
||||
* @param target - the target type.
|
||||
* @param name - the name of the field, or NULL to ignore.
|
||||
* @param fieldType - a compatible field type.
|
||||
* @return The field accessor.
|
||||
*/
|
||||
public static <T> FieldAccessor<T> getField(Class<?> target, String name, Class<T> fieldType) {
|
||||
return getField(target, name, fieldType, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a field accessor for a specific field type and name.
|
||||
* @param className - lookup name of the class, see {@link #getClass(String)}.
|
||||
* @param name - the name of the field, or NULL to ignore.
|
||||
* @param fieldType - a compatible field type.
|
||||
* @return The field accessor.
|
||||
*/
|
||||
public static <T> FieldAccessor<T> getField(String className, String name, Class<T> fieldType) {
|
||||
return getField(getClass(className), name, fieldType, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a field accessor for a specific field type and name.
|
||||
* @param target - the target type.
|
||||
* @param fieldType - a compatible field type.
|
||||
* @param index - the number of compatible fields to skip.
|
||||
* @return The field accessor.
|
||||
*/
|
||||
public static <T> FieldAccessor<T> getField(Class<?> target, Class<T> fieldType, int index) {
|
||||
return getField(target, null, fieldType, index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a field accessor for a specific field type and name.
|
||||
* @param className - lookup name of the class, see {@link #getClass(String)}.
|
||||
* @param fieldType - a compatible field type.
|
||||
* @param index - the number of compatible fields to skip.
|
||||
* @return The field accessor.
|
||||
*/
|
||||
public static <T> FieldAccessor<T> getField(String className, Class<T> fieldType, int index) {
|
||||
return getField(getClass(className), fieldType, index);
|
||||
}
|
||||
|
||||
// Common method
|
||||
private static <T> FieldAccessor<T> getField(Class<?> target, String name, Class<T> fieldType, int index) {
|
||||
for (final Field field : target.getDeclaredFields()) {
|
||||
if ((name == null || field.getName().equals(name)) &&
|
||||
fieldType.isAssignableFrom(field.getType()) && index-- <= 0) {
|
||||
field.setAccessible(true);
|
||||
|
||||
// A function for retrieving a specific field value
|
||||
return new FieldAccessor<T>() {
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public T get(Object target) {
|
||||
try {
|
||||
return (T) field.get(target);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException("Cannot access reflection.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(Object target, Object value) {
|
||||
try {
|
||||
field.set(target, value);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException("Cannot access reflection.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasField(Object target) {
|
||||
// target instanceof DeclaringClass
|
||||
return field.getDeclaringClass().isAssignableFrom(target.getClass());
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Search in parent classes
|
||||
if (target.getSuperclass() != null)
|
||||
return getField(target.getSuperclass(), name, fieldType, index);
|
||||
throw new IllegalArgumentException("Cannot find field with type " + fieldType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for the first publically and privately defined method of the given name and parameter count.
|
||||
* @param className - lookup name of the class, see {@link #getClass(String)}.
|
||||
* @param methodName - the method name, or NULL to skip.
|
||||
* @param params - the expected parameters.
|
||||
* @return An object that invokes this specific method.
|
||||
* @throws IllegalStateException If we cannot find this method.
|
||||
*/
|
||||
public static MethodInvoker getMethod(String className, String methodName, Class<?>... params) {
|
||||
return getTypedMethod(getClass(className), methodName, null, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for the first publically and privately defined method of the given name and parameter count.
|
||||
* @param clazz - a class to start with.
|
||||
* @param methodName - the method name, or NULL to skip.
|
||||
* @param params - the expected parameters.
|
||||
* @return An object that invokes this specific method.
|
||||
* @throws IllegalStateException If we cannot find this method.
|
||||
*/
|
||||
public static MethodInvoker getMethod(Class<?> clazz, String methodName, Class<?>... params) {
|
||||
return getTypedMethod(clazz, methodName, null, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for the first publically and privately defined method of the given name and parameter count.
|
||||
* @param clazz - a class to start with.
|
||||
* @param methodName - the method name, or NULL to skip.
|
||||
* @param returnType - the expected return type, or NULL to ignore.
|
||||
* @param params - the expected parameters.
|
||||
* @return An object that invokes this specific method.
|
||||
* @throws IllegalStateException If we cannot find this method.
|
||||
*/
|
||||
public static MethodInvoker getTypedMethod(Class<?> clazz, String methodName, Class<?> returnType, Class<?>... params) {
|
||||
for (final Method method : clazz.getDeclaredMethods()) {
|
||||
if ((methodName == null || method.getName().equals(methodName)) &&
|
||||
(returnType == null) || method.getReturnType().equals(returnType) &&
|
||||
Arrays.equals(method.getParameterTypes(), params)) {
|
||||
|
||||
method.setAccessible(true);
|
||||
return new MethodInvoker() {
|
||||
@Override
|
||||
public Object invoke(Object target, Object... arguments) {
|
||||
try {
|
||||
return method.invoke(target, arguments);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Cannot invoke method " + method, e);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
// Search in every superclass
|
||||
if (clazz.getSuperclass() != null)
|
||||
return getMethod(clazz.getSuperclass(), methodName, params);
|
||||
throw new IllegalStateException(String.format(
|
||||
"Unable to find method %s (%s).", methodName, Arrays.asList(params)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for the first publically and privately defined constructor of the given name and parameter count.
|
||||
* @param className - lookup name of the class, see {@link #getClass(String)}.
|
||||
* @param params - the expected parameters.
|
||||
* @return An object that invokes this constructor.
|
||||
* @throws IllegalStateException If we cannot find this method.
|
||||
*/
|
||||
public static ConstructorInvoker getConstructor(String className, Class<?>... params) {
|
||||
return getConstructor(getClass(className), params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for the first publically and privately defined constructor of the given name and parameter count.
|
||||
* @param clazz - a class to start with.
|
||||
* @param params - the expected parameters.
|
||||
* @return An object that invokes this constructor.
|
||||
* @throws IllegalStateException If we cannot find this method.
|
||||
*/
|
||||
public static ConstructorInvoker getConstructor(Class<?> clazz, Class<?>... params) {
|
||||
for (final Constructor<?> constructor : clazz.getDeclaredConstructors()) {
|
||||
if (Arrays.equals(constructor.getParameterTypes(), params)) {
|
||||
|
||||
constructor.setAccessible(true);
|
||||
return new ConstructorInvoker() {
|
||||
@Override
|
||||
public Object invoke(Object... arguments) {
|
||||
try {
|
||||
return constructor.newInstance(arguments);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Cannot invoke constructor " + constructor, e);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
throw new IllegalStateException(String.format(
|
||||
"Unable to find constructor for %s (%s).", clazz, Arrays.asList(params)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a class from its full name, without knowing its type on compile time.
|
||||
* <p>
|
||||
* This is useful when looking up fields by a NMS or OBC type.
|
||||
* <p>
|
||||
* @see {@link #getClass()} for more information.
|
||||
* @param lookupName - the class name with variables.
|
||||
* @return The class.
|
||||
*/
|
||||
public static Class<Object> getUntypedClass(String lookupName) {
|
||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||
Class<Object> clazz = (Class<Object>)(Class)getClass(lookupName);
|
||||
return clazz;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a class from its full name.
|
||||
* <p>
|
||||
* Strings enclosed with curly brackets - such as {TEXT} - will be replaced according
|
||||
* to the following table:
|
||||
* <p>
|
||||
* <table border="1">
|
||||
* <tr>
|
||||
* <th>Variable</th>
|
||||
* <th>Content</th>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>{nms}</td>
|
||||
* <td>Actual package name of net.minecraft.server.VERSION</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>{obc}</td>
|
||||
* <td>Actual pacakge name of org.bukkit.craftbukkit.VERSION</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>{version}</td>
|
||||
* <td>The current Minecraft package VERSION, if any.</td>
|
||||
* </tr>
|
||||
* </table>
|
||||
* @param lookupName - the class name with variables.
|
||||
* @return The looked up class.
|
||||
* @throws IllegalArgumentException If a variable or class could not be found.
|
||||
*/
|
||||
public static Class<?> getClass(String lookupName) {
|
||||
return getCanonicalClass(expandVariables(lookupName));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a class in the net.minecraft.server.VERSION.* package.
|
||||
* @param name - the name of the class, excluding the package.
|
||||
* @throws IllegalArgumentException If the class doesn't exist.
|
||||
*/
|
||||
public static Class<?> getMinecraftClass(String name) {
|
||||
return getCanonicalClass(NMS_PREFIX + "." + name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a class in the org.bukkit.craftbukkit.VERSION.* package.
|
||||
* @param name - the name of the class, excluding the package.
|
||||
* @throws IllegalArgumentException If the class doesn't exist.
|
||||
*/
|
||||
public static Class<?> getCraftBukkitClass(String name) {
|
||||
return getCanonicalClass(OBC_PREFIX + "." + name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a class by its canonical name.
|
||||
* @param canonicalName - the canonical name.
|
||||
* @return The class.
|
||||
*/
|
||||
private static Class<?> getCanonicalClass(String canonicalName) {
|
||||
try {
|
||||
return Class.forName(canonicalName);
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new IllegalArgumentException("Cannot find " + canonicalName, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand variables such as "{nms}" and "{obc}" to their corresponding packages.
|
||||
* @param name - the full name of the class.
|
||||
* @return The expanded string.
|
||||
*/
|
||||
private static String expandVariables(String name) {
|
||||
StringBuffer output = new StringBuffer();
|
||||
Matcher matcher = MATCH_VARIABLE.matcher(name);
|
||||
|
||||
while (matcher.find()) {
|
||||
String variable = matcher.group(1);
|
||||
String replacement = "";
|
||||
|
||||
// Expand all detected variables
|
||||
if ("nms".equalsIgnoreCase(variable))
|
||||
replacement = NMS_PREFIX;
|
||||
else if ("obc".equalsIgnoreCase(variable))
|
||||
replacement = OBC_PREFIX;
|
||||
else if ("version".equalsIgnoreCase(variable))
|
||||
replacement = VERSION;
|
||||
else
|
||||
throw new IllegalArgumentException("Unknown variable: " + variable);
|
||||
|
||||
// Assume the expanded variables are all packages, and append a dot
|
||||
if (replacement.length() > 0 && matcher.end() < name.length() && name.charAt(matcher.end()) != '.')
|
||||
replacement += ".";
|
||||
matcher.appendReplacement(output, Matcher.quoteReplacement(replacement));
|
||||
}
|
||||
matcher.appendTail(output);
|
||||
return output.toString();
|
||||
}
|
||||
}
|
||||
@@ -1,165 +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
|
||||
@@ -1,269 +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>
|
||||
<groupId>com.comphenix.protocol</groupId>
|
||||
<artifactId>ProtocolLib-Legacy</artifactId>
|
||||
<version>3.7.0</version>
|
||||
<name>ProtocolLib-Legacy</name>
|
||||
|
||||
<description>Provides read/write access to the Minecraft protocol.</description>
|
||||
<url>http://www.spigotmc.org/resources/protocollib.1997/</url>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>cp1252</project.build.sourceEncoding>
|
||||
<powermock.version>1.5</powermock.version>
|
||||
</properties>
|
||||
|
||||
<distributionManagement>
|
||||
<repository>
|
||||
<id>dmulloy2-releases</id>
|
||||
<url>http://repo.dmulloy2.net/content/repositories/releases/</url>
|
||||
</repository>
|
||||
<snapshotRepository>
|
||||
<id>dmulloy2-snapshots</id>
|
||||
<url>http://repo.dmulloy2.net/content/repositories/snapshots/</url>
|
||||
</snapshotRepository>
|
||||
</distributionManagement>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>dmulloy2-repo</id>
|
||||
<url>http://repo.dmulloy2.net/content/groups/public/</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>md_5-repo</id>
|
||||
<url>http://repo.md-5.net/content/repositories/public/</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<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.0</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>2.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.1</version>
|
||||
<configuration>
|
||||
<archive>
|
||||
<addMavenDescriptor>false</addMavenDescriptor>
|
||||
</archive>
|
||||
<finalName>${project.name}</finalName>
|
||||
</configuration>
|
||||
</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>
|
||||
</plugins>
|
||||
</reporting>
|
||||
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>release-sign-artifacts</id>
|
||||
<activation>
|
||||
<property>
|
||||
<name>performRelease</name>
|
||||
<value>true</value>
|
||||
</property>
|
||||
</activation>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-source-plugin</artifactId>
|
||||
<version>2.2</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-sources</id>
|
||||
<goals>
|
||||
<goal>jar</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<version>2.9</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-javadocs</id>
|
||||
<goals>
|
||||
<goal>jar</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-gpg-plugin</artifactId>
|
||||
<version>1.4</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>sign-artifacts</id>
|
||||
<phase>verify</phase>
|
||||
<goals>
|
||||
<goal>sign</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
</profiles>
|
||||
|
||||
<scm>
|
||||
<connection>scm:git:git://github.com/aadnk/ProtocolLib.git</connection>
|
||||
<developerConnection>scm:git:git@github.com:aadnk/ProtocolLib.git</developerConnection>
|
||||
<url>https://github.com/aadnk/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>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>org.spigotmc</groupId>
|
||||
<artifactId>spigot-api</artifactId>
|
||||
<version>1.7.10-R0.1-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.spigotmc</groupId>
|
||||
<artifactId>spigot</artifactId>
|
||||
<version>1.7.10-R0.1-SNAPSHOT</version>
|
||||
</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>${powermock.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.powermock</groupId>
|
||||
<artifactId>powermock-api-mockito</artifactId>
|
||||
<version>${powermock.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -1,58 +0,0 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
|
||||
/**
|
||||
* Ignore this class.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public class Application {
|
||||
private static Thread mainThread;
|
||||
private static boolean primaryMethod = true;
|
||||
|
||||
public static void main(String[] args) {
|
||||
// For now, though we might consider making a proper application
|
||||
System.out.println("This is a Bukkit library. Place it in the plugin-folder and restart the server!");
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if we are running on the main thread.
|
||||
* @return TRUE if we are, FALSE otherwise.
|
||||
*/
|
||||
public static boolean isPrimaryThread() {
|
||||
if (primaryMethod) {
|
||||
try {
|
||||
return Bukkit.isPrimaryThread();
|
||||
} catch (LinkageError e) {
|
||||
primaryMethod = false;
|
||||
}
|
||||
}
|
||||
// Fallback method
|
||||
return Thread.currentThread().equals(mainThread);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the calling thread as the primary thread.
|
||||
*/
|
||||
static void registerPrimaryThread() {
|
||||
mainThread = Thread.currentThread();
|
||||
}
|
||||
}
|
||||
@@ -1,170 +0,0 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.comphenix.protocol.async.AsyncListenerHandler;
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.error.Report;
|
||||
import com.comphenix.protocol.error.ReportType;
|
||||
import com.comphenix.protocol.events.ListeningWhitelist;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.injector.BukkitUnwrapper;
|
||||
import com.comphenix.protocol.injector.server.AbstractInputStreamLookup;
|
||||
import com.comphenix.protocol.injector.server.TemporaryPlayerFactory;
|
||||
import com.comphenix.protocol.injector.spigot.SpigotPacketInjector;
|
||||
import com.comphenix.protocol.reflect.FieldUtils;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.comphenix.protocol.reflect.MethodUtils;
|
||||
import com.comphenix.protocol.reflect.ObjectWriter;
|
||||
import com.comphenix.protocol.reflect.compiler.BackgroundCompiler;
|
||||
import com.comphenix.protocol.reflect.compiler.StructureCompiler;
|
||||
import com.comphenix.protocol.reflect.instances.CollectionGenerator;
|
||||
import com.comphenix.protocol.reflect.instances.DefaultInstances;
|
||||
import com.comphenix.protocol.reflect.instances.PrimitiveGenerator;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
import com.comphenix.protocol.wrappers.ChunkPosition;
|
||||
import com.comphenix.protocol.wrappers.WrappedDataWatcher;
|
||||
import com.comphenix.protocol.wrappers.WrappedWatchableObject;
|
||||
import com.comphenix.protocol.wrappers.nbt.io.NbtBinarySerializer;
|
||||
|
||||
/**
|
||||
* Used to fix ClassLoader leaks that may lead to filling up the permanent generation.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
class CleanupStaticMembers {
|
||||
// Reports
|
||||
public final static ReportType REPORT_CANNOT_RESET_FIELD = new ReportType("Unable to reset field %s: %s");
|
||||
public final static ReportType REPORT_CANNOT_UNLOAD_CLASS = new ReportType("Unable to unload class %s.");
|
||||
|
||||
private ClassLoader loader;
|
||||
private ErrorReporter reporter;
|
||||
|
||||
public CleanupStaticMembers(ClassLoader loader, ErrorReporter reporter) {
|
||||
this.loader = loader;
|
||||
this.reporter = reporter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that the previous ClassLoader is not leaking.
|
||||
*/
|
||||
public void resetAll() {
|
||||
// This list must always be updated
|
||||
@SuppressWarnings("deprecation")
|
||||
Class<?>[] publicClasses = {
|
||||
AsyncListenerHandler.class, ListeningWhitelist.class, PacketContainer.class,
|
||||
BukkitUnwrapper.class, DefaultInstances.class, CollectionGenerator.class,
|
||||
PrimitiveGenerator.class, FuzzyReflection.class, MethodUtils.class,
|
||||
BackgroundCompiler.class, StructureCompiler.class,
|
||||
ObjectWriter.class, Packets.Server.class, Packets.Client.class,
|
||||
ChunkPosition.class, WrappedDataWatcher.class, WrappedWatchableObject.class,
|
||||
AbstractInputStreamLookup.class, TemporaryPlayerFactory.class, SpigotPacketInjector.class,
|
||||
MinecraftReflection.class, NbtBinarySerializer.class
|
||||
};
|
||||
|
||||
String[] internalClasses = {
|
||||
"com.comphenix.protocol.events.SerializedOfflinePlayer",
|
||||
"com.comphenix.protocol.injector.player.InjectedServerConnection",
|
||||
"com.comphenix.protocol.injector.player.NetworkFieldInjector",
|
||||
"com.comphenix.protocol.injector.player.NetworkObjectInjector",
|
||||
"com.comphenix.protocol.injector.player.NetworkServerInjector",
|
||||
"com.comphenix.protocol.injector.player.PlayerInjector",
|
||||
"com.comphenix.protocol.injector.EntityUtilities",
|
||||
"com.comphenix.protocol.injector.packet.PacketRegistry",
|
||||
"com.comphenix.protocol.injector.packet.PacketInjector",
|
||||
"com.comphenix.protocol.injector.packet.ReadPacketModifier",
|
||||
"com.comphenix.protocol.injector.StructureCache",
|
||||
"com.comphenix.protocol.reflect.compiler.BoxingHelper",
|
||||
"com.comphenix.protocol.reflect.compiler.MethodDescriptor",
|
||||
"com.comphenix.protocol.wrappers.nbt.WrappedElement",
|
||||
};
|
||||
|
||||
resetClasses(publicClasses);
|
||||
resetClasses(getClasses(loader, internalClasses));
|
||||
}
|
||||
|
||||
private void resetClasses(Class<?>[] classes) {
|
||||
// Reset each class one by one
|
||||
for (Class<?> clazz : classes) {
|
||||
resetClass(clazz);
|
||||
}
|
||||
}
|
||||
|
||||
private void resetClass(Class<?> clazz) {
|
||||
for (Field field : clazz.getFields()) {
|
||||
Class<?> type = field.getType();
|
||||
|
||||
// Only check static non-primitive fields. We also skip strings.
|
||||
if (Modifier.isStatic(field.getModifiers()) &&
|
||||
!type.isPrimitive() && !type.equals(String.class) &&
|
||||
!type.equals(ReportType.class)) {
|
||||
|
||||
try {
|
||||
setFinalStatic(field, null);
|
||||
} catch (IllegalAccessException e) {
|
||||
// Just inform the player
|
||||
reporter.reportWarning(this,
|
||||
Report.newBuilder(REPORT_CANNOT_RESET_FIELD).error(e).messageParam(field.getName(), e.getMessage())
|
||||
);
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// HACK! HAACK!
|
||||
private static void setFinalStatic(Field field, Object newValue) throws IllegalAccessException {
|
||||
int modifier = field.getModifiers();
|
||||
boolean isFinal = Modifier.isFinal(modifier);
|
||||
|
||||
Field modifiersField = isFinal ? FieldUtils.getField(Field.class, "modifiers", true) : null;
|
||||
|
||||
// We have to remove the final field first
|
||||
if (isFinal) {
|
||||
FieldUtils.writeField(modifiersField, field, modifier & ~Modifier.FINAL, true);
|
||||
}
|
||||
|
||||
// Now we can safely modify the field
|
||||
FieldUtils.writeStaticField(field, newValue, true);
|
||||
|
||||
// Revert modifier
|
||||
if (isFinal) {
|
||||
FieldUtils.writeField(modifiersField, field, modifier, true);
|
||||
}
|
||||
}
|
||||
|
||||
private Class<?>[] getClasses(ClassLoader loader, String[] names) {
|
||||
List<Class<?>> output = new ArrayList<Class<?>>();
|
||||
|
||||
for (String name : names) {
|
||||
try {
|
||||
output.add(loader.loadClass(name));
|
||||
} catch (ClassNotFoundException e) {
|
||||
// Warn the user
|
||||
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_UNLOAD_CLASS).error(e).messageParam(name));
|
||||
}
|
||||
}
|
||||
|
||||
return output.toArray(new Class<?>[0]);
|
||||
}
|
||||
}
|
||||
@@ -1,146 +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 org.bukkit.ChatColor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.events.PacketListener;
|
||||
import com.comphenix.protocol.timing.TimedListenerManager;
|
||||
import com.comphenix.protocol.timing.TimingReportGenerator;
|
||||
|
||||
/**
|
||||
* 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 ProtocolConfig config;
|
||||
|
||||
public CommandProtocol(ErrorReporter reporter, Plugin plugin, ProtocolConfig config) {
|
||||
super(reporter, CommandBase.PERMISSION_ADMIN, NAME, 1);
|
||||
this.plugin = plugin;
|
||||
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("timings"))
|
||||
toggleTimings(sender, args);
|
||||
else if (subCommand.equalsIgnoreCase("listeners"))
|
||||
printListeners(sender, args);
|
||||
else
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Display every listener on the server
|
||||
private void printListeners(final CommandSender sender, String[] args) {
|
||||
ProtocolManager manager = ProtocolLibrary.getProtocolManager();
|
||||
|
||||
for (PacketListener listener : manager.getPacketListeners()) {
|
||||
sender.sendMessage(ChatColor.GOLD + "Packet listeners:");
|
||||
sender.sendMessage(ChatColor.GOLD + " " + listener);
|
||||
}
|
||||
// Along with every asynchronous listener
|
||||
for (PacketListener listener : manager.getAsynchronousManager().getAsyncHandlers()) {
|
||||
sender.sendMessage(ChatColor.GOLD + "Asynchronous listeners:");
|
||||
sender.sendMessage(ChatColor.GOLD + " " + listener);
|
||||
}
|
||||
}
|
||||
|
||||
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!");
|
||||
}
|
||||
}
|
||||
@@ -1,951 +0,0 @@
|
||||
package com.comphenix.protocol;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
|
||||
import com.comphenix.protocol.annotations.Spigot;
|
||||
import com.comphenix.protocol.events.ConnectionSide;
|
||||
import com.comphenix.protocol.injector.packet.PacketRegistry;
|
||||
import com.comphenix.protocol.reflect.ObjectEnum;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
import com.comphenix.protocol.utility.MinecraftVersion;
|
||||
import com.google.common.base.Objects;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.ComparisonChain;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.util.concurrent.Futures;
|
||||
|
||||
/**
|
||||
* Represents the type of a packet in a specific protocol.
|
||||
* <p>
|
||||
* Note that vanilla Minecraft reuses packet IDs per protocol (ping, game, login), so you cannot
|
||||
* rely on IDs alone.
|
||||
* @author Kristian
|
||||
*/
|
||||
public class PacketType implements Serializable, Comparable<PacketType> {
|
||||
// Increment whenever the type changes
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* Represents an unknown legacy packet ID.
|
||||
*/
|
||||
public static final int UNKNOWN_PACKET = -1;
|
||||
|
||||
/**
|
||||
* Packets sent during handshake.
|
||||
* @author Kristian
|
||||
*/
|
||||
public static class Handshake {
|
||||
private static final Protocol PROTOCOL = Protocol.HANDSHAKING;
|
||||
|
||||
/**
|
||||
* Incoming packets.
|
||||
* @author Kristian
|
||||
*/
|
||||
public static class Client extends ObjectEnum<PacketType> {
|
||||
private final static Sender SENDER = Sender.CLIENT;
|
||||
/**
|
||||
* Legacy name: HANDSHAKE.
|
||||
*/
|
||||
public static final PacketType SET_PROTOCOL = new PacketType(PROTOCOL, SENDER, 0x00, 2);
|
||||
|
||||
private final static Client INSTANCE = new Client();
|
||||
|
||||
// Prevent accidental construction
|
||||
private Client() { super(PacketType.class); }
|
||||
|
||||
public static Client getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
public static Sender getSender() {
|
||||
return SENDER;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An empty enum, as the server will not send any packets in this protocol.
|
||||
* @author Kristian
|
||||
*/
|
||||
public static class Server extends ObjectEnum<PacketType> {
|
||||
private final static Sender SENDER = Sender.CLIENT;
|
||||
private final static Server INSTANCE = new Server();
|
||||
private Server() { super(PacketType.class); }
|
||||
|
||||
public static Server getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
public static Sender getSender() {
|
||||
return SENDER;
|
||||
}
|
||||
}
|
||||
|
||||
public static Protocol getProtocol() {
|
||||
return PROTOCOL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Packets sent and received when logged into the game.
|
||||
* @author Kristian
|
||||
*/
|
||||
public static class Play {
|
||||
private static final Protocol PROTOCOL = Protocol.PLAY;
|
||||
|
||||
/**
|
||||
* Outgoing packets.
|
||||
* @author Kristian
|
||||
*/
|
||||
public static class Server extends ObjectEnum<PacketType> {
|
||||
private final static Sender SENDER = Sender.SERVER;
|
||||
|
||||
public static final PacketType KEEP_ALIVE = new PacketType(PROTOCOL, SENDER, 0x00, 0);
|
||||
public static final PacketType LOGIN = new PacketType(PROTOCOL, SENDER, 0x01, 1);
|
||||
public static final PacketType CHAT = new PacketType(PROTOCOL, SENDER, 0x02, 3);
|
||||
public static final PacketType UPDATE_TIME = new PacketType(PROTOCOL, SENDER, 0x03, 4);
|
||||
public static final PacketType ENTITY_EQUIPMENT = new PacketType(PROTOCOL, SENDER, 0x04, 5);
|
||||
public static final PacketType SPAWN_POSITION = new PacketType(PROTOCOL, SENDER, 0x05, 6);
|
||||
public static final PacketType UPDATE_HEALTH = new PacketType(PROTOCOL, SENDER, 0x06, 8);
|
||||
public static final PacketType RESPAWN = new PacketType(PROTOCOL, SENDER, 0x07, 9);
|
||||
public static final PacketType POSITION = new PacketType(PROTOCOL, SENDER, 0x08, 13);
|
||||
public static final PacketType HELD_ITEM_SLOT = new PacketType(PROTOCOL, SENDER, 0x09, 16);
|
||||
/**
|
||||
* Note that this was Packets.Server.ENTITY_LOCATION_ACTION.
|
||||
*/
|
||||
public static final PacketType BED = new PacketType(PROTOCOL, SENDER, 0x0A, 17);
|
||||
public static final PacketType ANIMATION = new PacketType(PROTOCOL, SENDER, 0x0B, 18);
|
||||
public static final PacketType NAMED_ENTITY_SPAWN = new PacketType(PROTOCOL, SENDER, 0x0C, 20);
|
||||
public static final PacketType COLLECT = new PacketType(PROTOCOL, SENDER, 0x0D, 22);
|
||||
public static final PacketType SPAWN_ENTITY = new PacketType(PROTOCOL, SENDER, 0x0E, 23);
|
||||
public static final PacketType SPAWN_ENTITY_LIVING = new PacketType(PROTOCOL, SENDER, 0x0F, 24);
|
||||
public static final PacketType SPAWN_ENTITY_PAINTING = new PacketType(PROTOCOL, SENDER, 0x10, 25);
|
||||
public static final PacketType SPAWN_ENTITY_EXPERIENCE_ORB =
|
||||
new PacketType(PROTOCOL, SENDER, 0x11, 26);
|
||||
public static final PacketType ENTITY_VELOCITY = new PacketType(PROTOCOL, SENDER, 0x12, 28);
|
||||
public static final PacketType ENTITY_DESTROY = new PacketType(PROTOCOL, SENDER, 0x13, 29);
|
||||
public static final PacketType ENTITY = new PacketType(PROTOCOL, SENDER, 0x14, 30);
|
||||
public static final PacketType REL_ENTITY_MOVE = new PacketType(PROTOCOL, SENDER, 0x15, 31);
|
||||
public static final PacketType ENTITY_LOOK = new PacketType(PROTOCOL, SENDER, 0x16, 32);
|
||||
public static final PacketType ENTITY_MOVE_LOOK = new PacketType(PROTOCOL, SENDER, 0x17, 33);
|
||||
public static final PacketType ENTITY_TELEPORT = new PacketType(PROTOCOL, SENDER, 0x18, 34);
|
||||
public static final PacketType ENTITY_HEAD_ROTATION = new PacketType(PROTOCOL, SENDER, 0x19, 35);
|
||||
public static final PacketType ENTITY_STATUS = new PacketType(PROTOCOL, SENDER, 0x1A, 38);
|
||||
public static final PacketType ATTACH_ENTITY = new PacketType(PROTOCOL, SENDER, 0x1B, 39);
|
||||
public static final PacketType ENTITY_METADATA = new PacketType(PROTOCOL, SENDER, 0x1C, 40);
|
||||
public static final PacketType ENTITY_EFFECT = new PacketType(PROTOCOL, SENDER, 0x1D, 41);
|
||||
public static final PacketType REMOVE_ENTITY_EFFECT = new PacketType(PROTOCOL, SENDER, 0x1E, 42);
|
||||
public static final PacketType EXPERIENCE = new PacketType(PROTOCOL, SENDER, 0x1F, 43);
|
||||
public static final PacketType UPDATE_ATTRIBUTES = new PacketType(PROTOCOL, SENDER, 0x20, 44);
|
||||
public static final PacketType MAP_CHUNK = new PacketType(PROTOCOL, SENDER, 0x21, 51);
|
||||
public static final PacketType MULTI_BLOCK_CHANGE = new PacketType(PROTOCOL, SENDER, 0x22, 52);
|
||||
public static final PacketType BLOCK_CHANGE = new PacketType(PROTOCOL, SENDER, 0x23, 53);
|
||||
public static final PacketType BLOCK_ACTION = new PacketType(PROTOCOL, SENDER, 0x24, 54);
|
||||
public static final PacketType BLOCK_BREAK_ANIMATION = new PacketType(PROTOCOL, SENDER, 0x25, 55);
|
||||
public static final PacketType MAP_CHUNK_BULK = new PacketType(PROTOCOL, SENDER, 0x26, 56);
|
||||
public static final PacketType EXPLOSION = new PacketType(PROTOCOL, SENDER, 0x27, 60);
|
||||
public static final PacketType WORLD_EVENT = new PacketType(PROTOCOL, SENDER, 0x28, 61);
|
||||
public static final PacketType NAMED_SOUND_EFFECT = new PacketType(PROTOCOL, SENDER, 0x29, 62);
|
||||
public static final PacketType WORLD_PARTICLES = new PacketType(PROTOCOL, SENDER, 0x2A, 63);
|
||||
/**
|
||||
* Note that this was Packets.Server.BED.
|
||||
*/
|
||||
public static final PacketType GAME_STATE_CHANGE = new PacketType(PROTOCOL, SENDER, 0x2B, 70);
|
||||
public static final PacketType SPAWN_ENTITY_WEATHER = new PacketType(PROTOCOL, SENDER, 0x2C, 71);
|
||||
public static final PacketType OPEN_WINDOW = new PacketType(PROTOCOL, SENDER, 0x2D, 100);
|
||||
public static final PacketType CLOSE_WINDOW = new PacketType(PROTOCOL, SENDER, 0x2E, 101);
|
||||
public static final PacketType SET_SLOT = new PacketType(PROTOCOL, SENDER, 0x2F, 103);
|
||||
public static final PacketType WINDOW_ITEMS = new PacketType(PROTOCOL, SENDER, 0x30, 104);
|
||||
public static final PacketType CRAFT_PROGRESS_BAR = new PacketType(PROTOCOL, SENDER, 0x31, 105);
|
||||
public static final PacketType TRANSACTION = new PacketType(PROTOCOL, SENDER, 0x32, 106);
|
||||
public static final PacketType UPDATE_SIGN = new PacketType(PROTOCOL, SENDER, 0x33, 130);
|
||||
public static final PacketType MAP = new PacketType(PROTOCOL, SENDER, 0x34, 131);
|
||||
public static final PacketType TILE_ENTITY_DATA = new PacketType(PROTOCOL, SENDER, 0x35, 132);
|
||||
public static final PacketType OPEN_SIGN_ENTITY = new PacketType(PROTOCOL, SENDER, 0x36, 133);
|
||||
public static final PacketType STATISTICS = new PacketType(PROTOCOL, SENDER, 0x37, 200);
|
||||
public static final PacketType PLAYER_INFO = new PacketType(PROTOCOL, SENDER, 0x38, 201);
|
||||
public static final PacketType ABILITIES = new PacketType(PROTOCOL, SENDER, 0x39, 202);
|
||||
public static final PacketType TAB_COMPLETE = new PacketType(PROTOCOL, SENDER, 0x3A, 203);
|
||||
public static final PacketType SCOREBOARD_OBJECTIVE = new PacketType(PROTOCOL, SENDER, 0x3B, 206);
|
||||
public static final PacketType SCOREBOARD_SCORE = new PacketType(PROTOCOL, SENDER, 0x3C, 207);
|
||||
public static final PacketType SCOREBOARD_DISPLAY_OBJECTIVE =
|
||||
new PacketType(PROTOCOL, SENDER, 0x3D, 208);
|
||||
public static final PacketType SCOREBOARD_TEAM = new PacketType(PROTOCOL, SENDER, 0x3E, 209);
|
||||
public static final PacketType CUSTOM_PAYLOAD = new PacketType(PROTOCOL, SENDER, 0x3F, 250);
|
||||
public static final PacketType KICK_DISCONNECT = new PacketType(PROTOCOL, SENDER, 0x40, 255);
|
||||
|
||||
@Spigot(minimumBuild = 1628)
|
||||
public static final PacketType TITLE = new PacketType(PROTOCOL, SENDER, 0x45, -1);
|
||||
@Spigot(minimumBuild = 1628)
|
||||
public static final PacketType TAB_HEADER = new PacketType(PROTOCOL, SENDER, 0x47, -1);
|
||||
@Spigot(minimumBuild = 1628)
|
||||
public static final PacketType RESOURCE_PACK_SEND = new PacketType(PROTOCOL, SENDER, 0x48, -1);
|
||||
|
||||
// The instance must
|
||||
private final static Server INSTANCE = new Server();
|
||||
|
||||
// Prevent accidental construction
|
||||
private Server() { super(PacketType.class); }
|
||||
|
||||
public static Sender getSender() {
|
||||
return SENDER;
|
||||
}
|
||||
public static Server getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Incoming packets.
|
||||
* @author Kristian
|
||||
*/
|
||||
public static class Client extends ObjectEnum<PacketType> {
|
||||
private final static Sender SENDER = Sender.CLIENT;
|
||||
|
||||
public static final PacketType KEEP_ALIVE = new PacketType(PROTOCOL, SENDER, 0x00, 0);
|
||||
public static final PacketType CHAT = new PacketType(PROTOCOL, SENDER, 0x01, 3);
|
||||
public static final PacketType USE_ENTITY = new PacketType(PROTOCOL, SENDER, 0x02, 7);
|
||||
public static final PacketType FLYING = new PacketType(PROTOCOL, SENDER, 0x03, 10);
|
||||
public static final PacketType POSITION = new PacketType(PROTOCOL, SENDER, 0x04, 11);
|
||||
public static final PacketType LOOK = new PacketType(PROTOCOL, SENDER, 0x05, 12);
|
||||
public static final PacketType POSITION_LOOK = new PacketType(PROTOCOL, SENDER, 0x06, 13);
|
||||
public static final PacketType BLOCK_DIG = new PacketType(PROTOCOL, SENDER, 0x07, 14);
|
||||
public static final PacketType BLOCK_PLACE = new PacketType(PROTOCOL, SENDER, 0x08, 15);
|
||||
public static final PacketType HELD_ITEM_SLOT = new PacketType(PROTOCOL, SENDER, 0x09, 16);
|
||||
public static final PacketType ARM_ANIMATION = new PacketType(PROTOCOL, SENDER, 0x0A, 18);
|
||||
public static final PacketType ENTITY_ACTION = new PacketType(PROTOCOL, SENDER, 0x0B, 19);
|
||||
public static final PacketType STEER_VEHICLE = new PacketType(PROTOCOL, SENDER, 0x0C, 27);
|
||||
public static final PacketType CLOSE_WINDOW = new PacketType(PROTOCOL, SENDER, 0x0D, 101);
|
||||
public static final PacketType WINDOW_CLICK = new PacketType(PROTOCOL, SENDER, 0x0E, 102);
|
||||
public static final PacketType TRANSACTION = new PacketType(PROTOCOL, SENDER, 0x0F, 106);
|
||||
public static final PacketType SET_CREATIVE_SLOT = new PacketType(PROTOCOL, SENDER, 0x10, 107);
|
||||
public static final PacketType ENCHANT_ITEM = new PacketType(PROTOCOL, SENDER, 0x11, 108);
|
||||
public static final PacketType UPDATE_SIGN = new PacketType(PROTOCOL, SENDER, 0x12, 130);
|
||||
public static final PacketType ABILITIES = new PacketType(PROTOCOL, SENDER, 0x13, 202);
|
||||
public static final PacketType TAB_COMPLETE = new PacketType(PROTOCOL, SENDER, 0x14, 203);
|
||||
public static final PacketType SETTINGS = new PacketType(PROTOCOL, SENDER, 0x15, 204);
|
||||
public static final PacketType CLIENT_COMMAND = new PacketType(PROTOCOL, SENDER, 0x16, 205);
|
||||
public static final PacketType CUSTOM_PAYLOAD = new PacketType(PROTOCOL, SENDER, 0x17, 250);
|
||||
|
||||
@Spigot(minimumBuild = 1628)
|
||||
public static final PacketType RESOURCE_PACK_STATUS = new PacketType(PROTOCOL, SENDER, 0x19, -1);
|
||||
|
||||
private final static Client INSTANCE = new Client();
|
||||
|
||||
// Prevent accidental construction
|
||||
private Client() { super(PacketType.class); }
|
||||
|
||||
public static Sender getSender() {
|
||||
return SENDER;
|
||||
}
|
||||
public static Client getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
}
|
||||
|
||||
public static Protocol getProtocol() {
|
||||
return PROTOCOL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Packets sent and received when querying the server in the multiplayer menu.
|
||||
* @author Kristian
|
||||
*/
|
||||
public static class Status {
|
||||
private static final Protocol PROTOCOL = Protocol.STATUS;
|
||||
|
||||
/**
|
||||
* Outgoing packets.
|
||||
* @author Kristian
|
||||
*/
|
||||
public static class Server extends ObjectEnum<PacketType> {
|
||||
private final static Sender SENDER = Sender.SERVER;
|
||||
|
||||
public static final PacketType OUT_SERVER_INFO = new PacketType(PROTOCOL, SENDER, 0x00, 255);
|
||||
@SuppressWarnings("deprecation")
|
||||
public static final PacketType OUT_PING = new PacketType(PROTOCOL, SENDER, 0x01, Packets.Server.PING_TIME);
|
||||
|
||||
private final static Server INSTANCE = new Server();
|
||||
|
||||
// Prevent accidental construction
|
||||
private Server() { super(PacketType.class); }
|
||||
|
||||
public static Sender getSender() {
|
||||
return SENDER;
|
||||
}
|
||||
public static Server getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Incoming packets.
|
||||
* @author Kristian
|
||||
*/
|
||||
public static class Client extends ObjectEnum<PacketType> {
|
||||
private final static Sender SENDER = Sender.CLIENT;
|
||||
|
||||
public static final PacketType IN_START = new PacketType(PROTOCOL, SENDER, 0x00, 254);
|
||||
@SuppressWarnings("deprecation")
|
||||
public static final PacketType IN_PING = new PacketType(PROTOCOL, SENDER, 0x01, Packets.Client.PING_TIME);
|
||||
|
||||
private final static Client INSTANCE = new Client();
|
||||
|
||||
// Prevent accidental construction
|
||||
private Client() { super(PacketType.class); }
|
||||
|
||||
public static Sender getSender() {
|
||||
return SENDER;
|
||||
}
|
||||
public static Client getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
}
|
||||
|
||||
public static Protocol getProtocol() {
|
||||
return PROTOCOL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Packets sent and received when logging in to the server.
|
||||
* @author Kristian
|
||||
*/
|
||||
public static class Login {
|
||||
private static final Protocol PROTOCOL = Protocol.LOGIN;
|
||||
|
||||
/**
|
||||
* Outgoing packets.
|
||||
* @author Kristian
|
||||
*/
|
||||
public static class Server extends ObjectEnum<PacketType> {
|
||||
private final static Sender SENDER = Sender.SERVER;
|
||||
|
||||
public static final PacketType DISCONNECT = new PacketType(PROTOCOL, SENDER, 0x00, 255);
|
||||
public static final PacketType ENCRYPTION_BEGIN = new PacketType(PROTOCOL, SENDER, 0x01, 253);
|
||||
@SuppressWarnings("deprecation")
|
||||
public static final PacketType SUCCESS = new PacketType(PROTOCOL, SENDER, 0x02, Packets.Server.LOGIN_SUCCESS);
|
||||
|
||||
@Spigot(minimumBuild = 1628)
|
||||
public static final PacketType LOGIN_COMPRESSION = new PacketType(PROTOCOL, SENDER, 0x03, -1);
|
||||
|
||||
private final static Server INSTANCE = new Server();
|
||||
|
||||
// Prevent accidental construction
|
||||
private Server() { super(PacketType.class); }
|
||||
|
||||
public static Sender getSender() {
|
||||
return SENDER;
|
||||
}
|
||||
public static Server getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Incoming packets.
|
||||
* @author Kristian
|
||||
*/
|
||||
public static class Client extends ObjectEnum<PacketType> {
|
||||
private final static Sender SENDER = Sender.CLIENT;
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public static final PacketType START = new PacketType(PROTOCOL, SENDER, 0x00, Packets.Client.LOGIN_START);
|
||||
public static final PacketType ENCRYPTION_BEGIN = new PacketType(PROTOCOL, SENDER, 0x01, 252);
|
||||
|
||||
private final static Client INSTANCE = new Client();
|
||||
|
||||
// Prevent accidental construction
|
||||
private Client() { super(PacketType.class); }
|
||||
|
||||
public static Sender getSender() {
|
||||
return SENDER;
|
||||
}
|
||||
public static Client getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
}
|
||||
|
||||
public static Protocol getProtocol() {
|
||||
return PROTOCOL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Contains every packet Minecraft 1.6.4 packet removed in Minecraft 1.7.2.
|
||||
* @author Kristian
|
||||
*/
|
||||
public static class Legacy {
|
||||
private static final Protocol PROTOCOL = Protocol.LEGACY;
|
||||
|
||||
/**
|
||||
* Outgoing packets.
|
||||
* @author Kristian
|
||||
*/
|
||||
// Missing server packets: [10, 11, 12, 21, 107, 252]
|
||||
public static class Server extends ObjectEnum<PacketType> {
|
||||
private final static Sender SENDER = Sender.SERVER;
|
||||
|
||||
public static final PacketType PLAYER_FLYING = PacketType.newLegacy(SENDER, 10);
|
||||
public static final PacketType PLAYER_POSITION = PacketType.newLegacy(SENDER, 11);
|
||||
public static final PacketType PLAYER_POSITON_LOOK = PacketType.newLegacy(SENDER, 12);
|
||||
/**
|
||||
* Removed in Minecraft 1.4.6.
|
||||
*/
|
||||
public static final PacketType PICKUP_SPAWN = PacketType.newLegacy(SENDER, 21);
|
||||
/**
|
||||
* Removed in Minecraft 1.7.2
|
||||
*/
|
||||
public static final PacketType SET_CREATIVE_SLOT = PacketType.newLegacy(SENDER, 107);
|
||||
|
||||
/**
|
||||
* Removed in Minecraft 1.7.2
|
||||
*/
|
||||
public static final PacketType KEY_RESPONSE = PacketType.newLegacy(SENDER, 252);
|
||||
|
||||
private final static Server INSTANCE = new Server();
|
||||
|
||||
// Prevent accidental construction
|
||||
private Server() {
|
||||
super(PacketType.class);
|
||||
}
|
||||
|
||||
public static Sender getSender() {
|
||||
return SENDER;
|
||||
}
|
||||
public static Server getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Incoming packets.
|
||||
* @author Kristian
|
||||
*/
|
||||
// Missing client packets: [1, 9, 255]
|
||||
public static class Client extends ObjectEnum<PacketType> {
|
||||
private final static Sender SENDER = Sender.CLIENT;
|
||||
|
||||
public static final PacketType LOGIN = PacketType.newLegacy(SENDER, 1);
|
||||
public static final PacketType RESPAWN = PacketType.newLegacy(SENDER, 9);
|
||||
public static final PacketType DISCONNECT = PacketType.newLegacy(SENDER, 255);
|
||||
|
||||
private final static Client INSTANCE = new Client();
|
||||
|
||||
// Prevent accidental construction
|
||||
private Client() { super(PacketType.class); }
|
||||
|
||||
public static Sender getSender() {
|
||||
return SENDER;
|
||||
}
|
||||
public static Client getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
}
|
||||
|
||||
public static Protocol getProtocol() {
|
||||
return PROTOCOL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the different protocol or connection states.
|
||||
* @author Kristian
|
||||
*/
|
||||
public enum Protocol {
|
||||
HANDSHAKING,
|
||||
PLAY,
|
||||
STATUS,
|
||||
LOGIN,
|
||||
|
||||
/**
|
||||
* Only for packets removed in Minecraft 1.7.2
|
||||
*/
|
||||
LEGACY;
|
||||
|
||||
/**
|
||||
* Retrieve the correct protocol enum from a given vanilla enum instance.
|
||||
* @param vanilla - the vanilla protocol enum instance.
|
||||
* @return The corresponding protocol.
|
||||
*/
|
||||
public static Protocol fromVanilla(Enum<?> vanilla) {
|
||||
String name = vanilla.name();
|
||||
|
||||
if ("HANDSHAKING".equals(name))
|
||||
return HANDSHAKING;
|
||||
if ("PLAY".equals(name))
|
||||
return PLAY;
|
||||
if ("STATUS".equals(name))
|
||||
return STATUS;
|
||||
if ("LOGIN".equals(name))
|
||||
return LOGIN;
|
||||
throw new IllegalArgumentException("Unrecognized vanilla enum " + vanilla);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the sender of this packet type.
|
||||
* @author Kristian
|
||||
*
|
||||
*/
|
||||
public enum Sender {
|
||||
/**
|
||||
* Indicates that packets of this type will be sent by connected clients.
|
||||
*/
|
||||
CLIENT,
|
||||
|
||||
/**
|
||||
* Indicate that packets of this type will be sent by the current server.
|
||||
*/
|
||||
SERVER;
|
||||
|
||||
/**
|
||||
* Retrieve the equivialent connection side.
|
||||
* @return The connection side.
|
||||
*/
|
||||
public ConnectionSide toSide() {
|
||||
return this == CLIENT ? ConnectionSide.CLIENT_SIDE : ConnectionSide.SERVER_SIDE;
|
||||
}
|
||||
}
|
||||
|
||||
// Lookup of packet types
|
||||
private static PacketTypeLookup LOOKUP;
|
||||
|
||||
/**
|
||||
* Protocol version of all the current IDs.
|
||||
*/
|
||||
private static final MinecraftVersion PROTOCOL_VERSION = MinecraftVersion.WORLD_UPDATE;
|
||||
|
||||
private final Protocol protocol;
|
||||
private final Sender sender;
|
||||
private final int currentId;
|
||||
private final int legacyId;
|
||||
private final MinecraftVersion version;
|
||||
|
||||
/**
|
||||
* Retrieve the current packet/legacy lookup.
|
||||
* @return The packet type lookup.
|
||||
*/
|
||||
private static PacketTypeLookup getLookup() {
|
||||
if (LOOKUP == null) {
|
||||
LOOKUP = new PacketTypeLookup().
|
||||
addPacketTypes(Handshake.Client.getInstance()).
|
||||
addPacketTypes(Handshake.Server.getInstance()).
|
||||
addPacketTypes(Play.Client.getInstance()).
|
||||
addPacketTypes(Play.Server.getInstance()).
|
||||
addPacketTypes(Status.Client.getInstance()).
|
||||
addPacketTypes(Status.Server.getInstance()).
|
||||
addPacketTypes(Login.Client.getInstance()).
|
||||
addPacketTypes(Login.Server.getInstance()).
|
||||
addPacketTypes(Legacy.Client.getInstance()).
|
||||
addPacketTypes(Legacy.Server.getInstance());
|
||||
}
|
||||
return LOOKUP;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find every packet type known to the current version of ProtocolLib.
|
||||
* @return Every packet type.
|
||||
*/
|
||||
public static Iterable<PacketType> values() {
|
||||
List<Iterable<? extends PacketType>> sources = Lists.newArrayList();
|
||||
sources.add(Handshake.Client.getInstance());
|
||||
sources.add(Handshake.Server.getInstance());
|
||||
sources.add(Play.Client.getInstance());
|
||||
sources.add(Play.Server.getInstance());
|
||||
sources.add(Status.Client.getInstance());
|
||||
sources.add(Status.Server.getInstance());
|
||||
sources.add(Login.Client.getInstance());
|
||||
sources.add(Login.Server.getInstance());
|
||||
|
||||
// Add the missing types in earlier versions
|
||||
if (!MinecraftReflection.isUsingNetty()) {
|
||||
sources.add(Legacy.Client.getInstance());
|
||||
sources.add(Legacy.Server.getInstance());
|
||||
}
|
||||
return Iterables.concat(sources);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @throws IllegalArgumentException If the legacy packet could not be found.
|
||||
*/
|
||||
public static PacketType findLegacy(int packetId) {
|
||||
PacketType type = getLookup().getFromLegacy(packetId);
|
||||
|
||||
if (type != null)
|
||||
return type;
|
||||
throw new IllegalArgumentException("Cannot find legacy packet " + packetId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a packet type from a legacy (1.6.4 and below) packet ID.
|
||||
* @param packetId - the legacy packet ID.
|
||||
* @param preference - the preferred sender, or NULL for any arbitrary sender.
|
||||
* @return The corresponding packet type.
|
||||
* @throws IllegalArgumentException If the legacy packet could not be found.
|
||||
*/
|
||||
public static PacketType findLegacy(int packetId, Sender preference) {
|
||||
if (preference == null)
|
||||
return findLegacy(packetId);
|
||||
PacketType type = getLookup().getFromLegacy(packetId, preference);
|
||||
|
||||
if (type != null)
|
||||
return type;
|
||||
throw new IllegalArgumentException("Cannot find legacy packet " + packetId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the given legacy packet exists.
|
||||
* @param packetId - the legacy packet ID.
|
||||
* @param preference - the sender preference.
|
||||
* @return TRUE if it does, FALSE otherwise.
|
||||
*/
|
||||
public static boolean hasLegacy(int packetId) {
|
||||
return getLookup().getFromLegacy(packetId) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a packet type from a protocol, sender and packet ID.
|
||||
* <p>
|
||||
* It is usually better to access the packet types statically, like so:
|
||||
* <ul>
|
||||
* <li>{@link PacketType.Play.Server#SPAWN_ENTITY}
|
||||
* </ul>
|
||||
* @param protocol - the current protocol.
|
||||
* @param sender - the sender.
|
||||
* @param packetId - the packet ID.
|
||||
* @return The corresponding packet type.
|
||||
* @throws IllegalArgumentException If the current packet could not be found.
|
||||
*/
|
||||
public static PacketType findCurrent(Protocol protocol, Sender sender, int packetId) {
|
||||
PacketType type = getLookup().getFromCurrent(protocol, sender, packetId);
|
||||
|
||||
if (type != null)
|
||||
return type;
|
||||
throw new IllegalArgumentException("Cannot find packet " + packetId +
|
||||
"(Protocol: " + protocol + ", Sender: " + sender + ")");
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the given packet exists.
|
||||
* @param protocol - the protocol.
|
||||
* @param sender - the sender.
|
||||
* @param packetId - the packet ID.
|
||||
* @return TRUE if it exists, FALSE otherwise.
|
||||
*/
|
||||
public static boolean hasCurrent(Protocol protocol, Sender sender, int packetId) {
|
||||
return getLookup().getFromCurrent(protocol, sender, packetId) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a packet type from a legacy ID.
|
||||
* <p>
|
||||
* If no associated packet type could be found, a new will be registered under LEGACY.
|
||||
* @param id - the legacy ID.
|
||||
* @param sender - the sender of the packet, or NULL if unknown.
|
||||
* @return The packet type.
|
||||
* @throws IllegalArgumentException If the sender is NULL and the packet doesn't exist.
|
||||
*/
|
||||
public static PacketType fromLegacy(int id, Sender sender) {
|
||||
PacketType type = getLookup().getFromLegacy(id, sender);
|
||||
|
||||
if (type == null) {
|
||||
if (sender == null)
|
||||
throw new IllegalArgumentException("Cannot find legacy packet " + id);
|
||||
type = newLegacy(sender, id);
|
||||
|
||||
// As below
|
||||
scheduleRegister(type, "Dynamic-" + UUID.randomUUID().toString());
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a packet type from a protocol, sender and packet ID.
|
||||
* <p>
|
||||
* The packet will automatically be registered if its missing.
|
||||
* @param protocol - the current protocol.
|
||||
* @param sender - the sender.
|
||||
* @param packetId - the packet ID. Can be UNKNOWN_PACKET.
|
||||
* @param legacyId - the legacy packet ID. Can be UNKNOWN_PACKET.
|
||||
* @return The corresponding packet type.
|
||||
*/
|
||||
public static PacketType fromCurrent(Protocol protocol, Sender sender, int packetId, int legacyId) {
|
||||
PacketType type = getLookup().getFromCurrent(protocol, sender, packetId);
|
||||
|
||||
if (type == null) {
|
||||
type = new PacketType(protocol, sender, packetId, legacyId);
|
||||
|
||||
// Many may be scheduled, but only the first will be executed
|
||||
scheduleRegister(type, "Dynamic-" + UUID.randomUUID().toString());
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup a packet type from a packet class.
|
||||
* @param packetClass - the packet class.
|
||||
* @return The corresponding packet type, or NULL if not found.
|
||||
*/
|
||||
public static PacketType fromClass(Class<?> packetClass) {
|
||||
PacketType type = PacketRegistry.getPacketType(packetClass);
|
||||
|
||||
if (type != null)
|
||||
return type;
|
||||
throw new IllegalArgumentException("Class " + packetClass + " is not a registered packet.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve every packet type with the given UPPER_CAMEL_CASE name.
|
||||
* <p>
|
||||
* Note that the collection is unmodiable.
|
||||
* @param name - the name.
|
||||
* @return Every packet type, or an empty collection.
|
||||
*/
|
||||
public static Collection<PacketType> fromName(String name) {
|
||||
return getLookup().getFromName(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a given class represents a packet class.
|
||||
* @param packetClass - the class to lookup.
|
||||
* @return TRUE if this is a packet class, FALSE otherwise.
|
||||
*/
|
||||
public static boolean hasClass(Class<?> packetClass) {
|
||||
return PacketRegistry.getPacketType(packetClass) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a particular packet type.
|
||||
* <p>
|
||||
* Note that the registration will be performed on the main thread.
|
||||
* @param type - the type to register.
|
||||
* @param name - the name of the packet.
|
||||
* @return A future telling us if our instance was registered.
|
||||
*/
|
||||
public static Future<Boolean> scheduleRegister(final PacketType type, final String name) {
|
||||
Callable<Boolean> callable = new Callable<Boolean>() {
|
||||
@Override
|
||||
public Boolean call() throws Exception {
|
||||
ObjectEnum<PacketType> objEnum;
|
||||
|
||||
// A bit ugly, but performance is critical
|
||||
objEnum = getObjectEnum(type);
|
||||
|
||||
if (objEnum.registerMember(type, name)) {
|
||||
getLookup().addPacketTypes(Arrays.asList(type));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// Execute in the main thread if possible
|
||||
if (Bukkit.getServer() == null || Application.isPrimaryThread()) {
|
||||
try {
|
||||
return Futures.immediateFuture(callable.call());
|
||||
} catch (Exception e) {
|
||||
return Futures.immediateFailedFuture(e);
|
||||
}
|
||||
}
|
||||
return ProtocolLibrary.getExecutorSync().submit(callable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the correct object enum from a specific packet type.
|
||||
* @param type - the packet type.
|
||||
* @return The corresponding object enum.
|
||||
*/
|
||||
public static ObjectEnum<PacketType> getObjectEnum(final PacketType type) {
|
||||
switch (type.getProtocol()) {
|
||||
case HANDSHAKING:
|
||||
return type.isClient() ? Handshake.Client.getInstance() : Handshake.Server.getInstance();
|
||||
case PLAY:
|
||||
return type.isClient() ? Play.Client.getInstance() : Play.Server.getInstance();
|
||||
case STATUS:
|
||||
return type.isClient() ? Status.Client.getInstance() : Status.Server.getInstance();
|
||||
case LOGIN:
|
||||
return type.isClient() ? Login.Client.getInstance() : Login.Server.getInstance();
|
||||
case LEGACY:
|
||||
return type.isClient() ? Legacy.Client.getInstance() : Legacy.Server.getInstance();
|
||||
default:
|
||||
throw new IllegalStateException("Unexpected protocol: " + type.getProtocol());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new packet type.
|
||||
* @param protocol - the current protocol.
|
||||
* @param sender - client or server.
|
||||
* @param currentId - the current packet ID, or
|
||||
* @param legacyId - the legacy packet ID.
|
||||
*/
|
||||
public PacketType(Protocol protocol, Sender sender, int currentId, int legacyId) {
|
||||
this(protocol, sender, currentId, legacyId, PROTOCOL_VERSION);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new packet type.
|
||||
* @param protocol - the current protocol.
|
||||
* @param sender - client or server.
|
||||
* @param currentId - the current packet ID.
|
||||
* @param legacyId - the legacy packet ID.
|
||||
* @param version - the version of the current ID.
|
||||
*/
|
||||
public PacketType(Protocol protocol, Sender sender, int currentId, int legacyId, MinecraftVersion version) {
|
||||
this.protocol = Preconditions.checkNotNull(protocol, "protocol cannot be NULL");
|
||||
this.sender = Preconditions.checkNotNull(sender, "sender cannot be NULL");
|
||||
this.currentId = currentId;
|
||||
this.legacyId = legacyId;
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a legacy packet type.
|
||||
* @param sender - client or server.
|
||||
* @param legacyId - the legacy packet ID.
|
||||
*/
|
||||
public static PacketType newLegacy(Sender sender, int legacyId) {
|
||||
return new PacketType(Protocol.LEGACY, sender, PacketType.UNKNOWN_PACKET, legacyId, MinecraftVersion.WORLD_UPDATE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if this packet is supported on the current server.
|
||||
* @return Whether or not the packet is supported.
|
||||
*/
|
||||
public boolean isSupported() {
|
||||
return PacketRegistry.isSupported(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the protocol (the connection state) the packet type belongs.
|
||||
* @return The protocol of this type.
|
||||
*/
|
||||
public Protocol getProtocol() {
|
||||
return protocol;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve which sender will transmit packets of this type.
|
||||
* @return The sender of these packets.
|
||||
*/
|
||||
public Sender getSender() {
|
||||
return sender;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if this packet was sent by the client.
|
||||
* @return TRUE if it was, FALSE otherwise.
|
||||
*/
|
||||
public boolean isClient() {
|
||||
return sender == Sender.CLIENT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if this packet was sent by the server.
|
||||
* @return TRUE if it was, FALSE otherwise.
|
||||
*/
|
||||
public boolean isServer() {
|
||||
return sender == Sender.SERVER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the current protocol ID for this packet type.
|
||||
* <p>
|
||||
* This is only unique within a specific protocol and target.
|
||||
* <p>
|
||||
* It is only unknown if the packet was removed in Minecraft 1.7.2.
|
||||
* @return The current ID, or {@link #UNKNOWN_PACKET} if unknown.
|
||||
*/
|
||||
public int getCurrentId() {
|
||||
return currentId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the equivalent packet class.
|
||||
* @return The packet class, or NULL if not found.
|
||||
*/
|
||||
public Class<?> getPacketClass() {
|
||||
try {
|
||||
return PacketRegistry.getPacketClassFromType(this);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the declared enum name of this packet type.
|
||||
* @return The enum name.
|
||||
*/
|
||||
public String name() {
|
||||
return getObjectEnum(this).getDeclaredName(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the Minecraft version for the current ID.
|
||||
* @return The Minecraft version.
|
||||
*/
|
||||
public MinecraftVersion getCurrentVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the legacy (1.6.4 or below) protocol ID of the packet type.
|
||||
* <p>
|
||||
* This ID is globally unique.
|
||||
* @return The legacy ID, or {@link #UNKNOWN_PACKET} if unknown.
|
||||
*/
|
||||
public int getLegacyId() {
|
||||
return legacyId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hashCode(protocol, sender, currentId, legacyId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == this)
|
||||
return true;
|
||||
|
||||
if (obj instanceof PacketType) {
|
||||
PacketType other = (PacketType) obj;
|
||||
return protocol == other.protocol &&
|
||||
sender == other.sender &&
|
||||
currentId == other.currentId &&
|
||||
legacyId == other.legacyId;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(PacketType other) {
|
||||
return ComparisonChain.start().
|
||||
compare(protocol, other.getProtocol()).
|
||||
compare(sender, other.getSender()).
|
||||
compare(currentId, other.getCurrentId()).
|
||||
compare(legacyId, other.getLegacyId()).
|
||||
result();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
Class<?> clazz = getPacketClass();;
|
||||
|
||||
if (clazz == null)
|
||||
return "UNREGISTERED [" + protocol + ", " + sender + ", " + currentId + ", legacy: " + legacyId + "]";
|
||||
else
|
||||
return clazz.getSimpleName() + "[" + currentId + ", legacy: " + legacyId + "]";
|
||||
}
|
||||
}
|
||||
@@ -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,27 +0,0 @@
|
||||
package com.comphenix.protocol.annotations;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Indicate that this API and its descendants are only valid on Spigot.
|
||||
* @author Kristian
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PACKAGE,
|
||||
ElementType.PARAMETER, ElementType.TYPE, ElementType.FIELD})
|
||||
public @interface Spigot {
|
||||
/**
|
||||
* The minimum build number of Spigot where this is valid.
|
||||
* @return The minimum build.
|
||||
*/
|
||||
int minimumBuild();
|
||||
|
||||
/**
|
||||
* The maximum build number of Spigot where this is valid, or Integer.MAX_VALUE if not set.
|
||||
* @return The maximum build number.
|
||||
*/
|
||||
int maximumBuild() default Integer.MAX_VALUE;
|
||||
}
|
||||
@@ -1,864 +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.DataInput;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutput;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Array;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import net.minecraft.util.com.mojang.authlib.GameProfile;
|
||||
import net.minecraft.util.io.netty.buffer.ByteBuf;
|
||||
import net.minecraft.util.io.netty.buffer.UnpooledByteBufAllocator;
|
||||
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.WorldType;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.PacketType.Protocol;
|
||||
import com.comphenix.protocol.injector.StructureCache;
|
||||
import com.comphenix.protocol.reflect.EquivalentConverter;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.comphenix.protocol.reflect.ObjectWriter;
|
||||
import com.comphenix.protocol.reflect.StructureModifier;
|
||||
import com.comphenix.protocol.reflect.cloning.AggregateCloner;
|
||||
import com.comphenix.protocol.reflect.cloning.AggregateCloner.BuilderParameters;
|
||||
import com.comphenix.protocol.reflect.cloning.BukkitCloner;
|
||||
import com.comphenix.protocol.reflect.cloning.Cloner;
|
||||
import com.comphenix.protocol.reflect.cloning.CollectionCloner;
|
||||
import com.comphenix.protocol.reflect.cloning.FieldCloner;
|
||||
import com.comphenix.protocol.reflect.cloning.ImmutableDetector;
|
||||
import com.comphenix.protocol.reflect.cloning.SerializableCloner;
|
||||
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
|
||||
import com.comphenix.protocol.reflect.instances.DefaultInstances;
|
||||
import com.comphenix.protocol.utility.MinecraftMethods;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
import com.comphenix.protocol.utility.StreamSerializer;
|
||||
import com.comphenix.protocol.wrappers.BukkitConverters;
|
||||
import com.comphenix.protocol.wrappers.ChunkCoordIntPair;
|
||||
import com.comphenix.protocol.wrappers.ChunkPosition;
|
||||
import com.comphenix.protocol.wrappers.EnumWrappers;
|
||||
import com.comphenix.protocol.wrappers.EnumWrappers.ChatVisibility;
|
||||
import com.comphenix.protocol.wrappers.EnumWrappers.ClientCommand;
|
||||
import com.comphenix.protocol.wrappers.EnumWrappers.Difficulty;
|
||||
import com.comphenix.protocol.wrappers.EnumWrappers.EntityUseAction;
|
||||
import com.comphenix.protocol.wrappers.EnumWrappers.NativeGameMode;
|
||||
import com.comphenix.protocol.wrappers.WrappedAttribute;
|
||||
import com.comphenix.protocol.wrappers.WrappedChatComponent;
|
||||
import com.comphenix.protocol.wrappers.WrappedDataWatcher;
|
||||
import com.comphenix.protocol.wrappers.WrappedGameProfile;
|
||||
import com.comphenix.protocol.wrappers.WrappedServerPing;
|
||||
import com.comphenix.protocol.wrappers.WrappedStatistic;
|
||||
import com.comphenix.protocol.wrappers.WrappedWatchableObject;
|
||||
import com.comphenix.protocol.wrappers.nbt.NbtBase;
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Sets;
|
||||
|
||||
/**
|
||||
* Represents a Minecraft packet indirectly.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public class PacketContainer implements Serializable {
|
||||
private static final long serialVersionUID = 3;
|
||||
|
||||
protected PacketType type;
|
||||
protected transient Object handle;
|
||||
|
||||
// Current structure modifier
|
||||
protected transient StructureModifier<Object> structureModifier;
|
||||
|
||||
// Support for serialization
|
||||
private static ConcurrentMap<Class<?>, Method> writeMethods = Maps.newConcurrentMap();
|
||||
private static ConcurrentMap<Class<?>, Method> readMethods = Maps.newConcurrentMap();
|
||||
|
||||
// Used to clone packets
|
||||
private static final AggregateCloner DEEP_CLONER = AggregateCloner.newBuilder().
|
||||
instanceProvider(DefaultInstances.DEFAULT).
|
||||
andThen(BukkitCloner.class).
|
||||
andThen(ImmutableDetector.class).
|
||||
andThen(CollectionCloner.class).
|
||||
andThen(getSpecializedDeepClonerFactory()).
|
||||
build();
|
||||
|
||||
private static final AggregateCloner SHALLOW_CLONER = AggregateCloner.newBuilder().
|
||||
instanceProvider(DefaultInstances.DEFAULT).
|
||||
andThen(new Function<BuilderParameters, Cloner>() {
|
||||
@Override
|
||||
public Cloner apply(@Nullable BuilderParameters param) {
|
||||
if (param == null)
|
||||
throw new IllegalArgumentException("Cannot be NULL.");
|
||||
|
||||
return new FieldCloner(param.getAggregateCloner(), param.getInstanceProvider()) {{
|
||||
// Use a default writer with no concept of cloning
|
||||
writer = new ObjectWriter();
|
||||
}};
|
||||
}
|
||||
}).
|
||||
build();
|
||||
|
||||
// Packets that cannot be cloned by our default deep cloner
|
||||
private static final Set<PacketType> CLONING_UNSUPPORTED = Sets.newHashSet(
|
||||
PacketType.Play.Server.UPDATE_ATTRIBUTES, PacketType.Status.Server.OUT_SERVER_INFO);
|
||||
|
||||
/**
|
||||
* Creates a packet container for a new packet.
|
||||
* <p>
|
||||
* Deprecated: Use {@link #PacketContainer(PacketType)} instead.
|
||||
* @param id - ID of the packet to create.
|
||||
*/
|
||||
@Deprecated
|
||||
public PacketContainer(int id) {
|
||||
this(PacketType.findLegacy(id), StructureCache.newPacket(PacketType.findLegacy(id)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a packet container for an existing packet.
|
||||
* <p>
|
||||
* Deprecated: Use {@link #PacketContainer(PacketType, Object))} instead.
|
||||
* @param id - ID of the given packet.
|
||||
* @param handle - contained packet.
|
||||
*/
|
||||
@Deprecated
|
||||
public PacketContainer(int id, Object handle) {
|
||||
this(PacketType.findLegacy(id), handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a packet container for an existing packet.
|
||||
* <p>
|
||||
* Deprecated: Use {@link #PacketContainer(PacketType, Object, StructureModifier))} instead.
|
||||
* @param id - ID of the given packet.
|
||||
* @param handle - contained packet.
|
||||
* @param structure - structure modifier.
|
||||
*/
|
||||
@Deprecated
|
||||
public PacketContainer(int id, Object handle, StructureModifier<Object> structure) {
|
||||
this(PacketType.findLegacy(id), handle, structure);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a packet container for a new packet.
|
||||
* @param type - the type of the packet to create.
|
||||
*/
|
||||
public PacketContainer(PacketType type) {
|
||||
this(type, StructureCache.newPacket(type));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a packet container for an existing packet.
|
||||
* @param id - ID of the given packet.
|
||||
* @param handle - contained packet.
|
||||
*/
|
||||
public PacketContainer(PacketType type, Object handle) {
|
||||
this(type, handle, StructureCache.getStructure(type).withTarget(handle));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a packet container for an existing packet.
|
||||
* @param id - ID of the given packet.
|
||||
* @param handle - contained packet.
|
||||
* @param structure - structure modifier.
|
||||
*/
|
||||
public PacketContainer(PacketType type, Object handle, StructureModifier<Object> structure) {
|
||||
if (handle == null)
|
||||
throw new IllegalArgumentException("handle cannot be null.");
|
||||
if (type == null)
|
||||
throw new IllegalArgumentException("type cannot be null.");
|
||||
|
||||
this.type = type;
|
||||
this.handle = handle;
|
||||
this.structureModifier = structure;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new packet container from a given handle.
|
||||
* @param packet - the NMS packet.
|
||||
* @return The packet container.
|
||||
*/
|
||||
public static PacketContainer fromPacket(Object packet) {
|
||||
PacketType type = PacketType.fromClass(packet.getClass());
|
||||
return new PacketContainer(type, packet);
|
||||
}
|
||||
|
||||
/**
|
||||
* For serialization.
|
||||
*/
|
||||
protected PacketContainer() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the underlying Minecraft packet.
|
||||
* @return Underlying Minecraft packet.
|
||||
*/
|
||||
public Object getHandle() {
|
||||
return handle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the generic structure modifier for this packet.
|
||||
* @return Structure modifier.
|
||||
*/
|
||||
public StructureModifier<Object> getModifier() {
|
||||
return structureModifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a read/write structure for every field with the given type.
|
||||
* @param primitiveType - the type to find.
|
||||
* @return A modifier for this specific type.
|
||||
*/
|
||||
public <T> StructureModifier<T> getSpecificModifier(Class<T> primitiveType) {
|
||||
return structureModifier.withType(primitiveType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a read/write structure for every byte field.
|
||||
* @return A modifier for every byte field.
|
||||
*/
|
||||
public StructureModifier<Byte> getBytes() {
|
||||
return structureModifier.withType(byte.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a read/write structure for every boolean field.
|
||||
* @return A modifier for every boolean field.
|
||||
*/
|
||||
public StructureModifier<Boolean> getBooleans() {
|
||||
return structureModifier.withType(boolean.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a read/write structure for every short field.
|
||||
* @return A modifier for every short field.
|
||||
*/
|
||||
public StructureModifier<Short> getShorts() {
|
||||
return structureModifier.withType(short.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a read/write structure for every integer field.
|
||||
* @return A modifier for every integer field.
|
||||
*/
|
||||
public StructureModifier<Integer> getIntegers() {
|
||||
return structureModifier.withType(int.class);
|
||||
}
|
||||
/**
|
||||
* Retrieves a read/write structure for every long field.
|
||||
* @return A modifier for every long field.
|
||||
*/
|
||||
public StructureModifier<Long> getLongs() {
|
||||
return structureModifier.withType(long.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a read/write structure for every float field.
|
||||
* @return A modifier for every float field.
|
||||
*/
|
||||
public StructureModifier<Float> getFloat() {
|
||||
return structureModifier.withType(float.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a read/write structure for every double field.
|
||||
* @return A modifier for every double field.
|
||||
*/
|
||||
public StructureModifier<Double> getDoubles() {
|
||||
return structureModifier.withType(double.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a read/write structure for every String field.
|
||||
* @return A modifier for every String field.
|
||||
*/
|
||||
public StructureModifier<String> getStrings() {
|
||||
return structureModifier.withType(String.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a read/write structure for every String array field.
|
||||
* @return A modifier for every String array field.
|
||||
*/
|
||||
public StructureModifier<String[]> getStringArrays() {
|
||||
return structureModifier.withType(String[].class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a read/write structure for every byte array field.
|
||||
* @return A modifier for every byte array field.
|
||||
*/
|
||||
public StructureModifier<byte[]> getByteArrays() {
|
||||
return structureModifier.withType(byte[].class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a serializer for reading and writing ItemStacks stored in a byte array.
|
||||
* @return A instance of the serializer.
|
||||
*/
|
||||
public StreamSerializer getByteArraySerializer() {
|
||||
return new StreamSerializer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a read/write structure for every int array field.
|
||||
* @return A modifier for every int array field.
|
||||
*/
|
||||
public StructureModifier<int[]> getIntegerArrays() {
|
||||
return structureModifier.withType(int[].class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a read/write structure for ItemStack.
|
||||
* <p>
|
||||
* This modifier will automatically marshall between the Bukkit ItemStack and the
|
||||
* internal Minecraft ItemStack.
|
||||
* @return A modifier for ItemStack fields.
|
||||
*/
|
||||
public StructureModifier<ItemStack> getItemModifier() {
|
||||
// Convert to and from the Bukkit wrapper
|
||||
return structureModifier.<ItemStack>withType(
|
||||
MinecraftReflection.getItemStackClass(), BukkitConverters.getItemStackConverter());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a read/write structure for arrays of ItemStacks.
|
||||
* <p>
|
||||
* This modifier will automatically marshall between the Bukkit ItemStack and the
|
||||
* internal Minecraft ItemStack.
|
||||
* @return A modifier for ItemStack array fields.
|
||||
*/
|
||||
public StructureModifier<ItemStack[]> getItemArrayModifier() {
|
||||
// Convert to and from the Bukkit wrapper
|
||||
return structureModifier.<ItemStack[]>withType(
|
||||
MinecraftReflection.getItemStackArrayClass(),
|
||||
BukkitConverters.getIgnoreNull(new ItemStackArrayConverter()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a read/write structure for maps of statistics.
|
||||
* <p>
|
||||
* Note that you must write back the changed map to persist it.
|
||||
* @return A modifier for maps of statistics.
|
||||
*/
|
||||
public StructureModifier<Map<WrappedStatistic, Integer>> getStatisticMaps() {
|
||||
return structureModifier.withType(Map.class,
|
||||
BukkitConverters.<WrappedStatistic, Integer>getMapConverter(
|
||||
MinecraftReflection.getStatisticClass(),
|
||||
BukkitConverters.getWrappedStatisticConverter()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a read/write structure for the world type enum.
|
||||
* <p>
|
||||
* This modifier will automatically marshall between the Bukkit world type and the
|
||||
* internal Minecraft world type.
|
||||
* @return A modifier for world type fields.
|
||||
*/
|
||||
public StructureModifier<WorldType> getWorldTypeModifier() {
|
||||
// Convert to and from the Bukkit wrapper
|
||||
return structureModifier.<WorldType>withType(
|
||||
MinecraftReflection.getWorldTypeClass(),
|
||||
BukkitConverters.getWorldTypeConverter());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a read/write structure for data watchers.
|
||||
* @return A modifier for data watchers.
|
||||
*/
|
||||
public StructureModifier<WrappedDataWatcher> getDataWatcherModifier() {
|
||||
// Convert to and from the Bukkit wrapper
|
||||
return structureModifier.<WrappedDataWatcher>withType(
|
||||
MinecraftReflection.getDataWatcherClass(),
|
||||
BukkitConverters.getDataWatcherConverter());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a read/write structure for entity objects.
|
||||
* <p>
|
||||
* Note that entities are transmitted by integer ID, and the type may not be enough
|
||||
* to distinguish between entities and other values. Thus, this structure modifier
|
||||
* MAY return null or invalid entities for certain fields. Using the correct index
|
||||
* is essential.
|
||||
*
|
||||
* @param world - the world each entity is currently occupying.
|
||||
* @return A modifier entity types.
|
||||
*/
|
||||
public StructureModifier<Entity> getEntityModifier(@Nonnull World world) {
|
||||
Preconditions.checkNotNull(world, "world cannot be NULL.");
|
||||
// Convert to and from the Bukkit wrapper
|
||||
return structureModifier.<Entity>withType(
|
||||
int.class, BukkitConverters.getEntityConverter(world));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a read/write structure for entity objects.
|
||||
* <p>
|
||||
* Note that entities are transmitted by integer ID, and the type may not be enough
|
||||
* to distinguish between entities and other values. Thus, this structure modifier
|
||||
* MAY return null or invalid entities for certain fields. Using the correct index
|
||||
* is essential.
|
||||
*
|
||||
* @param event - the original packet event.
|
||||
* @return A modifier entity types.
|
||||
*/
|
||||
public StructureModifier<Entity> getEntityModifier(@Nonnull PacketEvent event) {
|
||||
Preconditions.checkNotNull(event, "event cannot be NULL.");
|
||||
return getEntityModifier(event.getPlayer().getWorld());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a read/write structure for chunk positions.
|
||||
* @return A modifier for a ChunkPosition.
|
||||
*/
|
||||
public StructureModifier<ChunkPosition> getPositionModifier() {
|
||||
// Convert to and from the Bukkit wrapper
|
||||
return structureModifier.withType(
|
||||
MinecraftReflection.getChunkPositionClass(),
|
||||
ChunkPosition.getConverter());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a read/write structure for wrapped ChunkCoordIntPairs.
|
||||
* @return A modifier for ChunkCoordIntPair.
|
||||
*/
|
||||
public StructureModifier<ChunkCoordIntPair> getChunkCoordIntPairs() {
|
||||
// Allow access to the NBT class in packet 130
|
||||
return structureModifier.withType(
|
||||
MinecraftReflection.getChunkCoordIntPair(),
|
||||
ChunkCoordIntPair.getConverter());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a read/write structure for NBT classes.
|
||||
* @return A modifier for NBT classes.
|
||||
*/
|
||||
public StructureModifier<NbtBase<?>> getNbtModifier() {
|
||||
// Allow access to the NBT class in packet 130
|
||||
return structureModifier.withType(
|
||||
MinecraftReflection.getNBTBaseClass(),
|
||||
BukkitConverters.getNbtConverter());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a read/write structure for collections of attribute snapshots.
|
||||
* <p>
|
||||
* This modifier will automatically marshall between the visible ProtocolLib WrappedAttribute and the
|
||||
* internal Minecraft AttributeSnapshot.
|
||||
* @return A modifier for AttributeSnapshot collection fields.
|
||||
*/
|
||||
public StructureModifier<List<WrappedAttribute>> getAttributeCollectionModifier() {
|
||||
// Convert to and from the ProtocolLib wrapper
|
||||
return structureModifier.withType(
|
||||
Collection.class,
|
||||
BukkitConverters.getListConverter(
|
||||
MinecraftReflection.getAttributeSnapshotClass(),
|
||||
BukkitConverters.getWrappedAttributeConverter())
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a read/write structure for collections of chunk positions.
|
||||
* <p>
|
||||
* This modifier will automatically marshall between the visible ProtocolLib ChunkPosition and the
|
||||
* internal Minecraft ChunkPosition.
|
||||
* @return A modifier for ChunkPosition list fields.
|
||||
*/
|
||||
public StructureModifier<List<ChunkPosition>> getPositionCollectionModifier() {
|
||||
// Convert to and from the ProtocolLib wrapper
|
||||
return structureModifier.withType(
|
||||
Collection.class,
|
||||
BukkitConverters.getListConverter(
|
||||
MinecraftReflection.getChunkPositionClass(),
|
||||
ChunkPosition.getConverter())
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a read/write structure for collections of watchable objects.
|
||||
* <p>
|
||||
* This modifier will automatically marshall between the visible WrappedWatchableObject and the
|
||||
* internal Minecraft WatchableObject.
|
||||
* @return A modifier for watchable object list fields.
|
||||
*/
|
||||
public StructureModifier<List<WrappedWatchableObject>> getWatchableCollectionModifier() {
|
||||
// Convert to and from the ProtocolLib wrapper
|
||||
return structureModifier.withType(
|
||||
Collection.class,
|
||||
BukkitConverters.getListConverter(
|
||||
MinecraftReflection.getWatchableObjectClass(),
|
||||
BukkitConverters.getWatchableObjectConverter())
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieves a read/write structure for block fields.
|
||||
* <p>
|
||||
* This modifier will automatically marshall between Material and the
|
||||
* internal Minecraft Block.
|
||||
* @return A modifier for GameProfile fields.
|
||||
*/
|
||||
public StructureModifier<Material> getBlocks() {
|
||||
// Convert to and from the Bukkit wrapper
|
||||
return structureModifier.<Material>withType(
|
||||
MinecraftReflection.getBlockClass(), BukkitConverters.getBlockConverter());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a read/write structure for game profiles in Minecraft 1.7.2.
|
||||
* <p>
|
||||
* This modifier will automatically marshall between WrappedGameProfile and the
|
||||
* internal Minecraft GameProfile.
|
||||
* @return A modifier for GameProfile fields.
|
||||
*/
|
||||
public StructureModifier<WrappedGameProfile> getGameProfiles() {
|
||||
// Convert to and from the Bukkit wrapper
|
||||
return structureModifier.<WrappedGameProfile>withType(
|
||||
GameProfile.class, BukkitConverters.getWrappedGameProfileConverter());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a read/write structure for chat components in Minecraft 1.7.2.
|
||||
* <p>
|
||||
* This modifier will automatically marshall between WrappedChatComponent and the
|
||||
* internal Minecraft GameProfile.
|
||||
* @return A modifier for ChatComponent fields.
|
||||
*/
|
||||
public StructureModifier<WrappedChatComponent> getChatComponents() {
|
||||
// Convert to and from the Bukkit wrapper
|
||||
return structureModifier.<WrappedChatComponent>withType(
|
||||
MinecraftReflection.getIChatBaseComponentClass(), BukkitConverters.getWrappedChatComponentConverter());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a read/write structure for the ServerPing fields in the following packet: <br>
|
||||
* <ul>
|
||||
* <li>{@link PacketType.Status.Server#OUT_SERVER_INFO}
|
||||
* </ul>
|
||||
* @return A modifier for ServerPing fields.
|
||||
*/
|
||||
public StructureModifier<WrappedServerPing> getServerPings() {
|
||||
// Convert to and from the wrapper
|
||||
return structureModifier.<WrappedServerPing>withType(
|
||||
MinecraftReflection.getServerPingClass(), BukkitConverters.getWrappedServerPingConverter());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a read/write structure for the Protocol enum in 1.7.2.
|
||||
* @return A modifier for Protocol enum fields.
|
||||
*/
|
||||
public StructureModifier<Protocol> getProtocols() {
|
||||
// Convert to and from the wrapper
|
||||
return structureModifier.<Protocol>withType(
|
||||
EnumWrappers.getProtocolClass(), EnumWrappers.getProtocolConverter());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a read/write structure for the ClientCommand enum in 1.7.2.
|
||||
* @return A modifier for ClientCommand enum fields.
|
||||
*/
|
||||
public StructureModifier<ClientCommand> getClientCommands() {
|
||||
// Convert to and from the wrapper
|
||||
return structureModifier.<ClientCommand>withType(
|
||||
EnumWrappers.getClientCommandClass(), EnumWrappers.getClientCommandConverter());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a read/write structure for the ChatVisibility enum in 1.7.2.
|
||||
* @return A modifier for ChatVisibility enum fields.
|
||||
*/
|
||||
public StructureModifier<ChatVisibility> getChatVisibilities() {
|
||||
// Convert to and from the wrapper
|
||||
return structureModifier.<ChatVisibility>withType(
|
||||
EnumWrappers.getChatVisibilityClass(), EnumWrappers.getChatVisibilityConverter());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a read/write structure for the Difficulty enum in 1.7.2.
|
||||
* @return A modifier for Difficulty enum fields.
|
||||
*/
|
||||
public StructureModifier<Difficulty> getDifficulties() {
|
||||
// Convert to and from the wrapper
|
||||
return structureModifier.<Difficulty>withType(
|
||||
EnumWrappers.getDifficultyClass(), EnumWrappers.getDifficultyConverter());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a read/write structure for the EntityUse enum in 1.7.2.
|
||||
* @return A modifier for EntityUse enum fields.
|
||||
*/
|
||||
public StructureModifier<EntityUseAction> getEntityUseActions() {
|
||||
// Convert to and from the wrapper
|
||||
return structureModifier.<EntityUseAction>withType(
|
||||
EnumWrappers.getEntityUseActionClass(), EnumWrappers.getEntityUseActionConverter());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a read/write structure for the NativeGameMode enum in 1.7.2.
|
||||
* @return A modifier for NativeGameMode enum fields.
|
||||
*/
|
||||
public StructureModifier<NativeGameMode> getGameModes() {
|
||||
// Convert to and from the wrapper
|
||||
return structureModifier.<NativeGameMode>withType(
|
||||
EnumWrappers.getGameModeClass(), EnumWrappers.getGameModeConverter());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the ID of this packet.
|
||||
* <p>
|
||||
* Deprecated: Use {@link #getType()} instead.
|
||||
* @return Packet ID.
|
||||
*/
|
||||
@Deprecated
|
||||
public int getID() {
|
||||
return type.getLegacyId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the packet type of this packet.
|
||||
* @return The packet type.
|
||||
*/
|
||||
public PacketType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a shallow copy of the current packet.
|
||||
* <p>
|
||||
* This merely writes the content of each field to the new class directly,
|
||||
* without performing any expensive copies.
|
||||
*
|
||||
* @return A shallow copy of the current packet.
|
||||
*/
|
||||
public PacketContainer shallowClone() {
|
||||
Object clonedPacket = SHALLOW_CLONER.clone(getHandle());
|
||||
return new PacketContainer(getType(), clonedPacket);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a deep copy of the current packet.
|
||||
* <p>
|
||||
* This will perform a full copy of the entire object tree, only skipping
|
||||
* known immutable objects and primitive types.
|
||||
* <p>
|
||||
* Note that the inflated buffers in packet 51 and 56 will be copied directly to save memory.
|
||||
*
|
||||
* @return A deep copy of the current packet.
|
||||
*/
|
||||
public PacketContainer deepClone() {
|
||||
Object clonedPacket = null;
|
||||
|
||||
// Fall back on the alternative (but slower) method of reading and writing back the packet
|
||||
if (CLONING_UNSUPPORTED.contains(type)) {
|
||||
clonedPacket = SerializableCloner.clone(this).getHandle();
|
||||
} else {
|
||||
clonedPacket = DEEP_CLONER.clone(getHandle());
|
||||
}
|
||||
return new PacketContainer(getType(), clonedPacket);
|
||||
}
|
||||
|
||||
// To save space, we'll skip copying the inflated buffers in packet 51 and 56
|
||||
private static Function<BuilderParameters, Cloner> getSpecializedDeepClonerFactory() {
|
||||
// Look at what you've made me do Java, look at it!!
|
||||
return new Function<BuilderParameters, Cloner>() {
|
||||
@Override
|
||||
public Cloner apply(@Nullable BuilderParameters param) {
|
||||
return new FieldCloner(param.getAggregateCloner(), param.getInstanceProvider()) {{
|
||||
this.writer = new ObjectWriter() {
|
||||
protected void transformField(StructureModifier<Object> modifierSource,
|
||||
StructureModifier<Object> modifierDest, int fieldIndex) {
|
||||
// No need to clone inflated buffers
|
||||
if (modifierSource.getField(fieldIndex).getName().startsWith("inflatedBuffer"))
|
||||
modifierDest.write(fieldIndex, modifierSource.read(fieldIndex));
|
||||
else
|
||||
defaultTransform(modifierSource, modifierDest, getDefaultCloner(), fieldIndex);
|
||||
};
|
||||
};
|
||||
}};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void writeObject(ObjectOutputStream output) throws IOException {
|
||||
// Default serialization
|
||||
output.defaultWriteObject();
|
||||
|
||||
// We'll take care of NULL packets as well
|
||||
output.writeBoolean(handle != null);
|
||||
|
||||
try {
|
||||
if (MinecraftReflection.isUsingNetty()) {
|
||||
ByteBuf buffer = createPacketBuffer();
|
||||
MinecraftMethods.getPacketWriteByteBufMethod().invoke(handle, buffer);
|
||||
|
||||
output.writeInt(buffer.readableBytes());
|
||||
buffer.readBytes(output, buffer.readableBytes());
|
||||
|
||||
} else {
|
||||
// Call the write-method
|
||||
output.writeInt(-1);
|
||||
getMethodLazily(writeMethods, handle.getClass(), "write", DataOutput.class).
|
||||
invoke(handle, new DataOutputStream(output));
|
||||
}
|
||||
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new IOException("Minecraft packet doesn't support DataOutputStream", e);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException("Insufficient security privileges.", e);
|
||||
} catch (InvocationTargetException e) {
|
||||
throw new IOException("Could not serialize Minecraft packet.", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void readObject(ObjectInputStream input) throws ClassNotFoundException, IOException {
|
||||
// Default deserialization
|
||||
input.defaultReadObject();
|
||||
|
||||
// Get structure modifier
|
||||
structureModifier = StructureCache.getStructure(type);
|
||||
|
||||
// Don't read NULL packets
|
||||
if (input.readBoolean()) {
|
||||
|
||||
// Create a default instance of the packet
|
||||
handle = StructureCache.newPacket(type);
|
||||
|
||||
// Call the read method
|
||||
try {
|
||||
if (MinecraftReflection.isUsingNetty()) {
|
||||
ByteBuf buffer = createPacketBuffer();
|
||||
buffer.writeBytes(input, input.readInt());
|
||||
|
||||
MinecraftMethods.getPacketReadByteBufMethod().invoke(handle, buffer);
|
||||
} else {
|
||||
if (input.readInt() != -1)
|
||||
throw new IllegalArgumentException("Cannot load a packet from 1.7.2 in 1.6.4.");
|
||||
|
||||
getMethodLazily(readMethods, handle.getClass(), "read", DataInput.class).
|
||||
invoke(handle, new DataInputStream(input));
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new IOException("Minecraft packet doesn't support DataInputStream", e);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException("Insufficient security privileges.", e);
|
||||
} catch (InvocationTargetException e) {
|
||||
throw new IOException("Could not deserialize Minecraft packet.", e);
|
||||
}
|
||||
|
||||
// And we're done
|
||||
structureModifier = structureModifier.withTarget(handle);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new packet data serializer.
|
||||
* @return The packet data serializer.
|
||||
*/
|
||||
private ByteBuf createPacketBuffer() {
|
||||
return MinecraftReflection.getPacketDataSerializer(UnpooledByteBufAllocator.DEFAULT.buffer());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the cached method concurrently.
|
||||
* @param lookup - a lazy lookup cache.
|
||||
* @param handleClass - class type of the current packet.
|
||||
* @param methodName - name of method to retrieve.
|
||||
* @param parameterClass - the one parameter type in the method.
|
||||
* @return Reflected method.
|
||||
*/
|
||||
private Method getMethodLazily(ConcurrentMap<Class<?>, Method> lookup,
|
||||
Class<?> handleClass, String methodName, Class<?> parameterClass) {
|
||||
Method method = lookup.get(handleClass);
|
||||
|
||||
// Atomic operation
|
||||
if (method == null) {
|
||||
Method initialized = FuzzyReflection.fromClass(handleClass).getMethod(
|
||||
FuzzyMethodContract.newBuilder().
|
||||
parameterCount(1).
|
||||
parameterDerivedOf(parameterClass).
|
||||
returnTypeVoid().
|
||||
build());
|
||||
method = lookup.putIfAbsent(handleClass, initialized);
|
||||
|
||||
// Use our version if we succeeded
|
||||
if (method == null) {
|
||||
method = initialized;
|
||||
}
|
||||
}
|
||||
|
||||
return method;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents an equivalent converter for ItemStack arrays.
|
||||
* @author Kristian
|
||||
*/
|
||||
private static class ItemStackArrayConverter implements EquivalentConverter<ItemStack[]> {
|
||||
final EquivalentConverter<ItemStack> stackConverter = BukkitConverters.getItemStackConverter();
|
||||
|
||||
public Object getGeneric(Class<?>genericType, ItemStack[] specific) {
|
||||
Class<?> nmsStack = MinecraftReflection.getItemStackClass();
|
||||
Object[] result = (Object[]) Array.newInstance(nmsStack, specific.length);
|
||||
|
||||
// Unwrap every item
|
||||
for (int i = 0; i < result.length; i++) {
|
||||
result[i] = stackConverter.getGeneric(nmsStack, specific[i]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack[] getSpecific(Object generic) {
|
||||
Object[] input = (Object[]) generic;
|
||||
ItemStack[] result = new ItemStack[input.length];
|
||||
|
||||
// Add the wrapper
|
||||
for (int i = 0; i < result.length; i++) {
|
||||
result[i] = stackConverter.getSpecific(input[i]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<ItemStack[]> getSpecificType() {
|
||||
return ItemStack[].class;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,480 +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.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_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 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,279 +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.Map;
|
||||
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;
|
||||
|
||||
// Fix: PaperSpigot changed the trackedPlayers field
|
||||
private static Collection<?> getCollection(Object value) {
|
||||
if (value instanceof Collection<?>) {
|
||||
return (Collection<?>) value;
|
||||
} else if (value instanceof Map<?, ?>) {
|
||||
return ((Map<?, ?>) value).keySet();
|
||||
} else {
|
||||
throw new IllegalArgumentException("Expected Collection or Map but got " + value.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 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 {
|
||||
// Fix: Throw a more informative error
|
||||
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) {
|
||||
// This one is fairly easy
|
||||
trackedPlayersField = FuzzyReflection.fromObject(trackerEntry).getFieldByType("java\\.util\\..*");
|
||||
}
|
||||
|
||||
// Phew, finally there.
|
||||
Collection<?> trackedPlayers = getCollection(FieldUtils.readField(trackedPlayersField, trackerEntry, false));
|
||||
List<Object> nmsPlayers = unwrapBukkit(observers);
|
||||
|
||||
trackedPlayers.removeAll(nmsPlayers);
|
||||
|
||||
// We have to rely on a NAME once again. Damn it.
|
||||
if (scanPlayersMethod == null) {
|
||||
scanPlayersMethod = trackerEntry.getClass().getMethod("scanPlayers", List.class);
|
||||
}
|
||||
|
||||
scanPlayersMethod.invoke(trackerEntry, nmsPlayers);
|
||||
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw e;
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new FieldAccessException("Security limitation prevents access to 'get' method in IntHashMap", e);
|
||||
} catch (InvocationTargetException e) {
|
||||
throw new RuntimeException("Exception occurred in Minecraft.", e);
|
||||
} catch (SecurityException e) {
|
||||
throw new FieldAccessException("Security limitation prevents access to 'scanPlayers' method in trackerEntry.", e);
|
||||
} catch (NoSuchMethodException e) {
|
||||
throw new FieldAccessException("Cannot find 'scanPlayers' method. Is ProtocolLib up to date?", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve every client that is receiving information about a given entity.
|
||||
* @param entity - the entity that is being tracked.
|
||||
* @return Every client/player that is tracking the given entity.
|
||||
* @throws FieldAccessException If reflection failed.
|
||||
*/
|
||||
public static List<Player> getEntityTrackers(Entity entity) {
|
||||
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 = getCollection(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);
|
||||
} catch (InvocationTargetException e) {
|
||||
throw new FieldAccessException("Exception occurred in Minecraft.", 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, IllegalAccessException, InvocationTargetException {
|
||||
BukkitUnwrapper unwrapper = new BukkitUnwrapper();
|
||||
Object worldServer = unwrapper.unwrapItem(world);
|
||||
|
||||
if (entityTrackerField == null)
|
||||
entityTrackerField = FuzzyReflection.fromObject(worldServer).
|
||||
getFieldByType("tracker", MinecraftReflection.getEntityTrackerClass());
|
||||
|
||||
// Get the tracker
|
||||
Object tracker = null;
|
||||
|
||||
try {
|
||||
tracker = FieldUtils.readField(entityTrackerField, worldServer, false);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new FieldAccessException("Cannot access 'tracker' field due to security limitations.", e);
|
||||
}
|
||||
|
||||
if (trackedEntitiesField == null) {
|
||||
@SuppressWarnings("rawtypes")
|
||||
Set<Class> ignoredTypes = new HashSet<Class>();
|
||||
|
||||
// Well, this is more difficult. But we're looking for a Minecraft object that is not
|
||||
// created by the constructor(s).
|
||||
for (Constructor<?> constructor : tracker.getClass().getConstructors()) {
|
||||
for (Class<?> type : constructor.getParameterTypes()) {
|
||||
ignoredTypes.add(type);
|
||||
}
|
||||
}
|
||||
|
||||
// The Minecraft field that's NOT filled in by the constructor
|
||||
trackedEntitiesField = FuzzyReflection.fromObject(tracker, true).
|
||||
getFieldByType(MinecraftReflection.getMinecraftObjectRegex(), ignoredTypes);
|
||||
}
|
||||
|
||||
// Read the entity hashmap
|
||||
Object trackedEntities = null;
|
||||
|
||||
try {
|
||||
trackedEntities = FieldUtils.readField(trackedEntitiesField, tracker, true);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new FieldAccessException("Cannot access 'trackedEntities' field due to security limitations.", e);
|
||||
}
|
||||
|
||||
return WrappedIntHashMap.fromHandle(trackedEntities).get(entityID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve entity from a ID, even it it's newly created.
|
||||
* @return The asssociated entity.
|
||||
* @throws FieldAccessException Reflection error.
|
||||
*/
|
||||
public static Entity getEntityFromID(World world, int entityID) throws FieldAccessException {
|
||||
try {
|
||||
Object trackerEntry = getEntityTrackerEntry(world, entityID);
|
||||
Object tracker = null;
|
||||
|
||||
// Handle NULL cases
|
||||
if (trackerEntry != null) {
|
||||
if (trackerField == null) {
|
||||
try {
|
||||
trackerField = trackerEntry.getClass().getField("tracker");
|
||||
} catch (NoSuchFieldException e) {
|
||||
// Assume it's the first public entity field then
|
||||
trackerField = FuzzyReflection.fromObject(trackerEntry).getFieldByType(
|
||||
"tracker", MinecraftReflection.getEntityClass());
|
||||
}
|
||||
}
|
||||
|
||||
tracker = FieldUtils.readField(trackerField, trackerEntry, true);
|
||||
}
|
||||
|
||||
// If the tracker is NULL, we'll just assume this entity doesn't exist
|
||||
if (tracker != null)
|
||||
return (Entity) MinecraftReflection.getBukkitEntity(tracker);
|
||||
else
|
||||
return null;
|
||||
|
||||
} catch (Exception e) {
|
||||
throw new FieldAccessException("Cannot find entity from ID " + entityID + ".", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static List<Object> unwrapBukkit(List<Player> players) {
|
||||
|
||||
List<Object> output = Lists.newArrayList();
|
||||
BukkitUnwrapper unwrapper = new BukkitUnwrapper();
|
||||
|
||||
// Get the NMS equivalent
|
||||
for (Player player : players) {
|
||||
Object result = unwrapper.unwrapItem(player);
|
||||
|
||||
if (result != null)
|
||||
output.add(result);
|
||||
else
|
||||
throw new IllegalArgumentException("Cannot unwrap item " + player);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol.injector;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.injector.packet.InterceptWritePacket;
|
||||
|
||||
/**
|
||||
* Represents an object that initiate the packet listeners.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public interface ListenerInvoker {
|
||||
|
||||
/**
|
||||
* Invokes the given packet event for every registered listener.
|
||||
* @param event - the packet event to invoke.
|
||||
*/
|
||||
public abstract void invokePacketRecieving(PacketEvent event);
|
||||
|
||||
/**
|
||||
* Invokes the given packet event for every registered listener.
|
||||
* @param event - the packet event to invoke.
|
||||
*/
|
||||
public abstract void invokePacketSending(PacketEvent event);
|
||||
|
||||
/**
|
||||
* Retrieve the associated ID of a packet.
|
||||
* @param packet - the packet.
|
||||
* @return The packet ID.
|
||||
*/
|
||||
@Deprecated
|
||||
public abstract int getPacketID(Object packet);
|
||||
|
||||
/**
|
||||
* Retrieve the associated type of a packet.
|
||||
* @param packet - the packet.
|
||||
* @return The packet type.
|
||||
*/
|
||||
public abstract PacketType getPacketType(Object packet);
|
||||
|
||||
/**
|
||||
* Retrieve the object responsible for intercepting write packets.
|
||||
* @return Object that intercepts write packets.
|
||||
*/
|
||||
public InterceptWritePacket getInterceptWritePacket();
|
||||
|
||||
/**
|
||||
* Determine if a given packet requires input buffering.
|
||||
* @param packetId - the packet to check.
|
||||
* @return TRUE if it does, FALSE otherwise.
|
||||
*/
|
||||
@Deprecated
|
||||
public boolean requireInputBuffer(int packetId);
|
||||
|
||||
/**
|
||||
* Associate a given class with the given packet ID. Internal method.
|
||||
* @param clazz - class to associate.
|
||||
*/
|
||||
public abstract void unregisterPacketClass(Class<?> clazz);
|
||||
|
||||
/**
|
||||
* Register a given class in the packet registry. Internal method.
|
||||
* @param clazz - class to register.
|
||||
* @param packetID - the the new associated packet ID.
|
||||
*/
|
||||
@Deprecated
|
||||
public abstract void registerPacketClass(Class<?> clazz, int packetID);
|
||||
|
||||
/**
|
||||
* Retrieves the correct packet class from a given packet ID.
|
||||
* @param packetID - the packet ID.
|
||||
* @param forceVanilla - whether or not to look for vanilla classes, not injected classes.
|
||||
* @return The associated class.
|
||||
*/
|
||||
@Deprecated
|
||||
public abstract Class<?> getPacketClassFromID(int packetID, boolean forceVanilla);
|
||||
}
|
||||
@@ -1,91 +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
|
||||
if (version.compareTo(MinecraftVersion.HORSE_UPDATE) >= 0 || triesForgeIntegration()) {
|
||||
clientSide.add(Packets.Client.CUSTOM_PAYLOAD);
|
||||
}
|
||||
serverSide.add(Packets.Server.KICK_DISCONNECT);
|
||||
|
||||
// Forge uses packet 250 during login
|
||||
if (triesForgeIntegration()) {
|
||||
serverSide.add(Packets.Client.CUSTOM_PAYLOAD);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if we are runnign MCPC.
|
||||
* @return TRUE if we are, FALSE otherwise.
|
||||
*/
|
||||
private static boolean triesForgeIntegration() {
|
||||
String version = Bukkit.getVersion();
|
||||
return version.contains("MCPC-Plus") || version.contains("Cauldron") || version.contains("Thermos");
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a packet may be sent during login from a given direction.
|
||||
* @param packetId - the ID of the packet.
|
||||
* @param side - the direction.
|
||||
* @return TRUE if it may, FALSE otherwise.
|
||||
*/
|
||||
@Deprecated
|
||||
public boolean isLoginPacket(int packetId, ConnectionSide side) {
|
||||
switch (side) {
|
||||
case CLIENT_SIDE:
|
||||
return clientSide.contains(packetId);
|
||||
case SERVER_SIDE:
|
||||
return serverSide.contains(packetId);
|
||||
case BOTH:
|
||||
return clientSide.contains(packetId) ||
|
||||
serverSide.contains(packetId);
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown connection side: " + side);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a given packet may be sent during login.
|
||||
* @param type - the packet type.
|
||||
* @return TRUE if it may, FALSE otherwise.
|
||||
*/
|
||||
public boolean isLoginPacket(PacketType type) {
|
||||
if (!MinecraftReflection.isUsingNetty())
|
||||
return isLoginPacket(type.getLegacyId(), type.getSender().toSide());
|
||||
|
||||
return PacketType.Login.Client.getInstance().hasMember(type) ||
|
||||
PacketType.Login.Server.getInstance().hasMember(type) ||
|
||||
PacketType.Status.Client.getInstance().hasMember(type) ||
|
||||
PacketType.Status.Server.getInstance().hasMember(type);
|
||||
}
|
||||
}
|
||||
@@ -1,175 +0,0 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol.injector;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.injector.packet.PacketRegistry;
|
||||
import com.comphenix.protocol.reflect.StructureModifier;
|
||||
import com.comphenix.protocol.reflect.compiler.BackgroundCompiler;
|
||||
import com.comphenix.protocol.reflect.compiler.CompileListener;
|
||||
import com.comphenix.protocol.reflect.compiler.CompiledStructureModifier;
|
||||
import com.comphenix.protocol.reflect.instances.DefaultInstances;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
|
||||
/**
|
||||
* Caches structure modifiers.
|
||||
* @author Kristian
|
||||
*/
|
||||
public class StructureCache {
|
||||
// Structure modifiers
|
||||
private static ConcurrentMap<PacketType, StructureModifier<Object>> structureModifiers =
|
||||
new ConcurrentHashMap<PacketType, StructureModifier<Object>>();
|
||||
|
||||
private static Set<PacketType> compiling = new HashSet<PacketType>();
|
||||
|
||||
/**
|
||||
* Creates an empty Minecraft packet of the given id.
|
||||
* <p>
|
||||
* Decreated: Use {@link #newPacket(PacketType)} instead.
|
||||
* @param legacyId - legacy (1.6.4) packet id.
|
||||
* @return Created packet.
|
||||
*/
|
||||
@Deprecated
|
||||
public static Object newPacket(int legacyId) {
|
||||
return newPacket(PacketType.findLegacy(legacyId));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an empty Minecraft packet of the given type.
|
||||
* @param type - packet type.
|
||||
* @return Created packet.
|
||||
*/
|
||||
public static Object newPacket(PacketType type) {
|
||||
Class<?> clazz = PacketRegistry.getPacketClassFromType(type, true);
|
||||
|
||||
// Check the return value
|
||||
if (clazz != null) {
|
||||
// TODO: Optimize DefaultInstances
|
||||
Object result = DefaultInstances.DEFAULT.create(clazz);
|
||||
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Cannot find associated packet class: " + type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a cached structure modifier for the given packet id.
|
||||
* <p>
|
||||
* Deprecated: Use {@link #getStructure(PacketType)} instead.
|
||||
* @param legacyId - the legacy (1.6.4) packet ID.
|
||||
* @return A structure modifier.
|
||||
*/
|
||||
@Deprecated
|
||||
public static StructureModifier<Object> getStructure(int legacyId) {
|
||||
return getStructure(PacketType.findLegacy(legacyId));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a cached structure modifier for the given packet type.
|
||||
* @param type - packet type.
|
||||
* @return A structure modifier.
|
||||
*/
|
||||
public static StructureModifier<Object> getStructure(PacketType type) {
|
||||
// Compile structures by default
|
||||
return getStructure(type, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a cached structure modifier given a packet type.
|
||||
* @param packetType - packet type.
|
||||
* @return A structure modifier.
|
||||
*/
|
||||
public static StructureModifier<Object> getStructure(Class<?> packetType) {
|
||||
// Compile structures by default
|
||||
return getStructure(packetType, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a cached structure modifier given a packet type.
|
||||
* @param packetType - packet type.
|
||||
* @param compile - whether or not to asynchronously compile the structure modifier.
|
||||
* @return A structure modifier.
|
||||
*/
|
||||
public static StructureModifier<Object> getStructure(Class<?> packetType, boolean compile) {
|
||||
// Get the ID from the class
|
||||
return getStructure(PacketRegistry.getPacketType(packetType), compile);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a cached structure modifier for the given packet ID.
|
||||
* <p>
|
||||
* Deprecated: Use {@link #getStructure(PacketType, boolean)} instead.
|
||||
* @param legacyId - the legacy (1.6.4) packet ID.
|
||||
* @param compile - whether or not to asynchronously compile the structure modifier.
|
||||
* @return A structure modifier.
|
||||
*/
|
||||
@Deprecated
|
||||
public static StructureModifier<Object> getStructure(final int legacyId, boolean compile) {
|
||||
return getStructure(PacketType.findLegacy(legacyId), compile);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a cached structure modifier for the given packet type.
|
||||
* @param type - packet type.
|
||||
* @param compile - whether or not to asynchronously compile the structure modifier.
|
||||
* @return A structure modifier.
|
||||
*/
|
||||
public static StructureModifier<Object> getStructure(final PacketType type, boolean compile) {
|
||||
StructureModifier<Object> result = structureModifiers.get(type);
|
||||
|
||||
// We don't want to create this for every lookup
|
||||
if (result == null) {
|
||||
// Use the vanilla class definition
|
||||
final StructureModifier<Object> value = new StructureModifier<Object>(
|
||||
PacketRegistry.getPacketClassFromType(type, true), MinecraftReflection.getPacketClass(), true);
|
||||
|
||||
result = structureModifiers.putIfAbsent(type, value);
|
||||
|
||||
// We may end up creating multiple modifiers, but we'll agree on which to use
|
||||
if (result == null) {
|
||||
result = value;
|
||||
}
|
||||
}
|
||||
|
||||
// Automatically compile the structure modifier
|
||||
if (compile && !(result instanceof CompiledStructureModifier)) {
|
||||
// Compilation is many orders of magnitude slower than synchronization
|
||||
synchronized (compiling) {
|
||||
final BackgroundCompiler compiler = BackgroundCompiler.getInstance();
|
||||
|
||||
if (!compiling.contains(type) && compiler != null) {
|
||||
compiler.scheduleCompilation(result, new CompileListener<Object>() {
|
||||
@Override
|
||||
public void onCompiled(StructureModifier<Object> compiledModifier) {
|
||||
structureModifiers.put(type, compiledModifier);
|
||||
}
|
||||
});
|
||||
compiling.add(type);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
-400
@@ -1,400 +0,0 @@
|
||||
package com.comphenix.protocol.injector.netty;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import net.minecraft.util.io.netty.channel.Channel;
|
||||
import net.minecraft.util.io.netty.channel.ChannelFuture;
|
||||
import net.minecraft.util.io.netty.channel.ChannelHandler;
|
||||
import net.minecraft.util.io.netty.channel.ChannelHandlerContext;
|
||||
import net.minecraft.util.io.netty.channel.ChannelInboundHandler;
|
||||
import net.minecraft.util.io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
import net.minecraft.util.io.netty.channel.ChannelInitializer;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.concurrency.PacketTypeSet;
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.error.Report;
|
||||
import com.comphenix.protocol.error.ReportType;
|
||||
import com.comphenix.protocol.events.ConnectionSide;
|
||||
import com.comphenix.protocol.events.ListenerOptions;
|
||||
import com.comphenix.protocol.events.NetworkMarker;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.injector.ListenerInvoker;
|
||||
import com.comphenix.protocol.injector.packet.PacketInjector;
|
||||
import com.comphenix.protocol.injector.packet.PacketRegistry;
|
||||
import com.comphenix.protocol.injector.player.PlayerInjectionHandler;
|
||||
import com.comphenix.protocol.injector.server.TemporaryPlayerFactory;
|
||||
import com.comphenix.protocol.injector.spigot.AbstractPacketInjector;
|
||||
import com.comphenix.protocol.injector.spigot.AbstractPlayerHandler;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.comphenix.protocol.reflect.VolatileField;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
|
||||
public class NettyProtocolInjector implements ChannelListener {
|
||||
public static final ReportType REPORT_CANNOT_INJECT_INCOMING_CHANNEL = new ReportType("Unable to inject incoming channel %s.");
|
||||
|
||||
private volatile boolean injected;
|
||||
private volatile boolean closed;
|
||||
|
||||
// The temporary player factory
|
||||
private TemporaryPlayerFactory playerFactory = new TemporaryPlayerFactory();
|
||||
private List<VolatileField> bootstrapFields = Lists.newArrayList();
|
||||
|
||||
// The channel injector factory
|
||||
private InjectionFactory injectionFactory;
|
||||
|
||||
// List of network managers
|
||||
private volatile List<Object> networkManagers;
|
||||
|
||||
// Different sending filters
|
||||
private PacketTypeSet sendingFilters = new PacketTypeSet();
|
||||
private PacketTypeSet reveivedFilters = new PacketTypeSet();
|
||||
|
||||
// Packets that must be executed on the main thread
|
||||
private PacketTypeSet mainThreadFilters = new PacketTypeSet();
|
||||
|
||||
// Which packets are buffered
|
||||
private PacketTypeSet bufferedPackets = new PacketTypeSet();
|
||||
private ListenerInvoker invoker;
|
||||
|
||||
// Handle errors
|
||||
private ErrorReporter reporter;
|
||||
private boolean debug;
|
||||
|
||||
public NettyProtocolInjector(Plugin plugin, ListenerInvoker invoker, ErrorReporter reporter) {
|
||||
this.injectionFactory = new InjectionFactory(plugin);
|
||||
this.invoker = invoker;
|
||||
this.reporter = reporter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDebug() {
|
||||
return debug;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether or not the debug mode is enabled.
|
||||
* @param debug - TRUE if it is, FALSE otherwise.
|
||||
*/
|
||||
public void setDebug(boolean debug) {
|
||||
this.debug = debug;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inject into the spigot connection class.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public synchronized void inject() {
|
||||
if (injected)
|
||||
throw new IllegalStateException("Cannot inject twice.");
|
||||
try {
|
||||
FuzzyReflection fuzzyServer = FuzzyReflection.fromClass(MinecraftReflection.getMinecraftServerClass());
|
||||
List<Method> serverConnectionMethods = fuzzyServer.getMethodListByParameters(MinecraftReflection.getServerConnectionClass(), new Class[] {});
|
||||
|
||||
// Get the server connection
|
||||
Object server = fuzzyServer.getSingleton();
|
||||
Object serverConnection = null;
|
||||
|
||||
for (Method method : serverConnectionMethods) {
|
||||
try {
|
||||
serverConnection = method.invoke(server);
|
||||
|
||||
// Continue until we get a server connection
|
||||
if (serverConnection != null) {
|
||||
break;
|
||||
}
|
||||
} catch (Exception 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 BootstrapList(list, connectionHandler));
|
||||
}
|
||||
|
||||
injected = true;
|
||||
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Unable to inject channel futures.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasListener(Class<?> packetClass) {
|
||||
return reveivedFilters.contains(packetClass) || sendingFilters.contains(packetClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasMainThreadListener(Class<?> packetClass) {
|
||||
return mainThreadFilters.contains(packetClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ErrorReporter getReporter() {
|
||||
return reporter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inject our packet handling into a specific player.
|
||||
* @param player
|
||||
*/
|
||||
public void injectPlayer(Player player) {
|
||||
injectionFactory.fromPlayer(player, this).inject();
|
||||
}
|
||||
|
||||
private List<VolatileField> getBootstrapFields(Object serverConnection) {
|
||||
List<VolatileField> result = Lists.newArrayList();
|
||||
|
||||
// Find and (possibly) proxy every list
|
||||
for (Field field : FuzzyReflection.fromObject(serverConnection, true).getFieldListByType(List.class)) {
|
||||
VolatileField volatileField = new VolatileField(field, serverConnection, true).toSynchronized();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Object> list = (List<Object>) volatileField.getValue();
|
||||
|
||||
if (list.size() == 0 || list.get(0) instanceof ChannelFuture) {
|
||||
result.add(volatileField);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up any remaning injections.
|
||||
*/
|
||||
public synchronized void close() {
|
||||
if (!closed) {
|
||||
closed = true;
|
||||
|
||||
for (VolatileField field : bootstrapFields) {
|
||||
Object value = field.getValue();
|
||||
|
||||
// Undo the processed channels, if any
|
||||
if (value instanceof BootstrapList) {
|
||||
((BootstrapList) value).close();
|
||||
}
|
||||
field.revertValue();
|
||||
}
|
||||
// Uninject all the players
|
||||
injectionFactory.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketEvent onPacketSending(Injector injector, Object packet, NetworkMarker marker) {
|
||||
Class<?> clazz = packet.getClass();
|
||||
|
||||
if (sendingFilters.contains(clazz) || marker != null) {
|
||||
PacketContainer container = new PacketContainer(PacketRegistry.getPacketType(clazz), packet);
|
||||
return packetQueued(container, injector.getPlayer(), marker);
|
||||
}
|
||||
// Don't change anything
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketEvent onPacketReceiving(Injector injector, Object packet, NetworkMarker marker) {
|
||||
Class<?> clazz = packet.getClass();
|
||||
|
||||
if (reveivedFilters.contains(clazz) || marker != null) {
|
||||
PacketContainer container = new PacketContainer(PacketRegistry.getPacketType(clazz), packet);
|
||||
return packetReceived(container, injector.getPlayer(), marker);
|
||||
}
|
||||
// Don't change anything
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean includeBuffer(Class<?> packetClass) {
|
||||
return bufferedPackets.contains(packetClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to inform the event listeners of a queued packet.
|
||||
* @param packet - the packet that is to be sent.
|
||||
* @param receiver - the receiver of this packet.
|
||||
* @return The packet event that was used.
|
||||
*/
|
||||
private PacketEvent packetQueued(PacketContainer packet, Player receiver, NetworkMarker marker) {
|
||||
PacketEvent event = PacketEvent.fromServer(this, packet, marker, receiver);
|
||||
|
||||
invoker.invokePacketSending(event);
|
||||
return event;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to inform the event listeners of a received packet.
|
||||
* @param packet - the packet that has been receieved.
|
||||
* @param sender - the client packet.
|
||||
* @param marker - the network marker.
|
||||
* @return The packet event that was used.
|
||||
*/
|
||||
private PacketEvent packetReceived(PacketContainer packet, Player sender, NetworkMarker marker) {
|
||||
PacketEvent event = PacketEvent.fromClient(this, packet, marker, sender);
|
||||
|
||||
invoker.invokePacketRecieving(event);
|
||||
return event;
|
||||
}
|
||||
|
||||
// Server side
|
||||
public PlayerInjectionHandler getPlayerInjector() {
|
||||
return new AbstractPlayerHandler(sendingFilters) {
|
||||
private ChannelListener listener = 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 && !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();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a view of this protocol injector as a packet injector.
|
||||
* @return The packet injector.
|
||||
*/
|
||||
// Client side
|
||||
public PacketInjector getPacketInjector() {
|
||||
return new AbstractPacketInjector(reveivedFilters) {
|
||||
@Override
|
||||
public PacketEvent packetRecieved(PacketContainer packet, Player client, byte[] buffered) {
|
||||
NetworkMarker marker = buffered != null ? new NettyNetworkMarker(ConnectionSide.CLIENT_SIDE, buffered) : null;
|
||||
injectionFactory.fromPlayer(client, NettyProtocolInjector.this).
|
||||
saveMarker(packet.getHandle(), marker);
|
||||
return packetReceived(packet, client, marker);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void inputBuffersChanged(Set<PacketType> set) {
|
||||
bufferedPackets = new PacketTypeSet(set);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
-175
@@ -1,175 +0,0 @@
|
||||
package com.comphenix.protocol.injector.netty;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.PacketType.Protocol;
|
||||
import com.comphenix.protocol.PacketType.Sender;
|
||||
import com.comphenix.protocol.injector.packet.MapContainer;
|
||||
import com.comphenix.protocol.reflect.StructureModifier;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
import com.google.common.collect.BiMap;
|
||||
import com.google.common.collect.HashBiMap;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Sets;
|
||||
|
||||
/**
|
||||
* Represents a way of accessing the new netty Protocol enum.
|
||||
* @author Kristian
|
||||
*/
|
||||
// TODO: Handle modifications to the BiMap
|
||||
public class NettyProtocolRegistry {
|
||||
/**
|
||||
* Represents a register we are currently building.
|
||||
* @author Kristian
|
||||
*/
|
||||
private static class Register {
|
||||
// The main lookup table
|
||||
public BiMap<PacketType, Class<?>> typeToClass = HashBiMap.create();
|
||||
public volatile Set<PacketType> serverPackets = Sets.newHashSet();
|
||||
public volatile Set<PacketType> clientPackets = Sets.newHashSet();
|
||||
public List<MapContainer> containers = Lists.newArrayList();
|
||||
|
||||
/**
|
||||
* Determine if the current register is outdated.
|
||||
* @return TRUE if it is, FALSE otherwise.
|
||||
*/
|
||||
public boolean isOutdated() {
|
||||
for (MapContainer container : containers) {
|
||||
if (container.hasChanged()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private Class<?> enumProtocol;
|
||||
|
||||
// Current register
|
||||
private volatile Register register;
|
||||
|
||||
public NettyProtocolRegistry() {
|
||||
enumProtocol = MinecraftReflection.getEnumProtocolClass();
|
||||
initialize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve an immutable view of the packet type lookup.
|
||||
* @return The packet type lookup.
|
||||
*/
|
||||
public Map<PacketType, Class<?>> getPacketTypeLookup() {
|
||||
return Collections.unmodifiableMap(register.typeToClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve an immutable view of the class to packet type lookup.
|
||||
* @return The packet type lookup.
|
||||
*/
|
||||
public Map<Class<?>, PacketType> getPacketClassLookup() {
|
||||
return Collections.unmodifiableMap(register.typeToClass.inverse());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve every known client packet, from every protocol.
|
||||
* @return Every client packet.
|
||||
*/
|
||||
public Set<PacketType> getClientPackets() {
|
||||
return Collections.unmodifiableSet(register.clientPackets);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve every known server packet, from every protocol.
|
||||
* @return Every server packet.
|
||||
*/
|
||||
public Set<PacketType> getServerPackets() {
|
||||
return Collections.unmodifiableSet(register.serverPackets);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that our local register is up-to-date with Minecraft.
|
||||
* <p>
|
||||
* This operation may block the calling thread.
|
||||
*/
|
||||
public synchronized void synchronize() {
|
||||
// See if the register is outdated
|
||||
if (register.isOutdated()) {
|
||||
initialize();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the packet lookup tables in each protocol.
|
||||
*/
|
||||
private synchronized 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;
|
||||
}
|
||||
|
||||
private 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(), PacketType.UNKNOWN_PACKET);
|
||||
register.typeToClass.put(type, entry.getValue());
|
||||
|
||||
if (sender == Sender.SERVER)
|
||||
register.serverPackets.add(type);
|
||||
if (sender == Sender.CLIENT)
|
||||
register.clientPackets.add(type);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the number of mapping in all the maps.
|
||||
* @param maps - iterable of maps.
|
||||
* @return The sum of all the entries.
|
||||
*/
|
||||
private int sum(Iterable<? extends Map<Integer, Class<?>>> maps) {
|
||||
int count = 0;
|
||||
|
||||
for (Map<Integer, Class<?>> map : maps)
|
||||
count += map.size();
|
||||
return count;
|
||||
}
|
||||
}
|
||||
-64
@@ -1,64 +0,0 @@
|
||||
package com.comphenix.protocol.injector.packet;
|
||||
|
||||
import java.io.FilterInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
/**
|
||||
* Represents an input stream that stores every read block of bytes in another output stream.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
class CaptureInputStream extends FilterInputStream {
|
||||
protected OutputStream out;
|
||||
|
||||
public CaptureInputStream(InputStream in, OutputStream out) {
|
||||
super(in);
|
||||
this.out = out;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
int value = super.read();
|
||||
|
||||
// Write the byte
|
||||
if (value >= 0)
|
||||
out.write(value);
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
super.close();
|
||||
out.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] b) throws IOException {
|
||||
int count = super.read(b);
|
||||
|
||||
if (count > 0 ) {
|
||||
out.write(b, 0, count);
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] b, int off, int len) throws IOException {
|
||||
int count = super.read(b, off, len);
|
||||
|
||||
if (count > 0 ) {
|
||||
out.write(b, off, count);
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the output stream that receives all the read information.
|
||||
* @return The output stream everything is copied to.
|
||||
*/
|
||||
public OutputStream getOutputStream() {
|
||||
return out;
|
||||
}
|
||||
}
|
||||
-150
@@ -1,150 +0,0 @@
|
||||
package com.comphenix.protocol.injector.packet;
|
||||
|
||||
import java.io.DataOutput;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
import net.sf.cglib.proxy.Callback;
|
||||
import net.sf.cglib.proxy.CallbackFilter;
|
||||
import net.sf.cglib.proxy.Enhancer;
|
||||
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.error.Report;
|
||||
import com.comphenix.protocol.error.ReportType;
|
||||
import com.comphenix.protocol.events.NetworkMarker;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.reflect.MethodInfo;
|
||||
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
|
||||
import com.comphenix.protocol.utility.EnhancerFactory;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
/**
|
||||
* Retrieve a packet instance that has its write method intercepted.
|
||||
* @author Kristian
|
||||
*/
|
||||
public class InterceptWritePacket {
|
||||
public static final ReportType REPORT_CANNOT_FIND_WRITE_PACKET_METHOD = new ReportType("Cannot find write packet method in %s.");
|
||||
public static final ReportType REPORT_CANNOT_CONSTRUCT_WRITE_PROXY = new ReportType("Cannot construct write proxy packet %s.");
|
||||
|
||||
/**
|
||||
* Matches the readPacketData(DataInputStream) method in Packet.
|
||||
*/
|
||||
private static FuzzyMethodContract WRITE_PACKET = FuzzyMethodContract.newBuilder().
|
||||
returnTypeVoid().
|
||||
parameterDerivedOf(DataOutput.class).
|
||||
parameterCount(1).
|
||||
build();
|
||||
|
||||
private CallbackFilter filter;
|
||||
private boolean writePacketIntercepted;
|
||||
|
||||
private ConcurrentMap<Integer, Class<?>> proxyClasses = Maps.newConcurrentMap();
|
||||
private ErrorReporter reporter;
|
||||
|
||||
private WritePacketModifier modifierWrite;
|
||||
private WritePacketModifier modifierRest;
|
||||
|
||||
public InterceptWritePacket(ErrorReporter reporter) {
|
||||
this.reporter = reporter;
|
||||
|
||||
// Initialize modifiers
|
||||
this.modifierWrite = new WritePacketModifier(reporter, true);
|
||||
this.modifierRest = new WritePacketModifier(reporter, false);
|
||||
}
|
||||
|
||||
private Class<?> createProxyClass(int packetId) {
|
||||
// Construct the proxy object
|
||||
Enhancer ex = EnhancerFactory.getInstance().createEnhancer();
|
||||
|
||||
// Attempt to share callback filter
|
||||
if (filter == null) {
|
||||
filter = new CallbackFilter() {
|
||||
@Override
|
||||
public int accept(Method method) {
|
||||
// Skip methods defined in Object
|
||||
if (WRITE_PACKET.isMatch(MethodInfo.fromMethod(method), null)) {
|
||||
writePacketIntercepted = true;
|
||||
return 0;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Subclass the generic packet class
|
||||
ex.setSuperclass(MinecraftReflection.getPacketClass());
|
||||
ex.setCallbackFilter(filter);
|
||||
ex.setUseCache(false);
|
||||
|
||||
ex.setCallbackTypes( new Class[] { WritePacketModifier.class, WritePacketModifier.class });
|
||||
Class<?> proxyClass = ex.createClass();
|
||||
|
||||
// Register write modifiers too
|
||||
Enhancer.registerStaticCallbacks(proxyClass, new Callback[] { modifierWrite, modifierRest });
|
||||
|
||||
if (proxyClass != null) {
|
||||
// Check that we found the read method
|
||||
if (!writePacketIntercepted) {
|
||||
reporter.reportWarning(this,
|
||||
Report.newBuilder(REPORT_CANNOT_FIND_WRITE_PACKET_METHOD).
|
||||
messageParam(MinecraftReflection.getPacketClass()));
|
||||
}
|
||||
}
|
||||
return proxyClass;
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private Class<?> getProxyClass(int packetId) {
|
||||
Class<?> stored = proxyClasses.get(packetId);
|
||||
|
||||
// Concurrent pattern
|
||||
if (stored == null) {
|
||||
final Class<?> created = createProxyClass(packetId);
|
||||
stored = proxyClasses.putIfAbsent(packetId, created);
|
||||
|
||||
// We won!
|
||||
if (stored == null) {
|
||||
stored = created;
|
||||
PacketRegistry.getPacketToID().put(stored, packetId);
|
||||
}
|
||||
}
|
||||
return stored;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new instance of the proxy object.
|
||||
* @return New instance of proxy, or NULL if we failed.
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public Object constructProxy(Object proxyObject, PacketEvent event, NetworkMarker marker) {
|
||||
Class<?> proxyClass = null;
|
||||
|
||||
try {
|
||||
proxyClass = getProxyClass(event.getPacketID());
|
||||
Object generated = proxyClass.newInstance();
|
||||
|
||||
modifierWrite.register(generated, proxyObject, event, marker);
|
||||
modifierRest.register(generated, proxyObject, event, marker);
|
||||
return generated;
|
||||
|
||||
} catch (Exception e) {
|
||||
reporter.reportWarning(this,
|
||||
Report.newBuilder(REPORT_CANNOT_CONSTRUCT_WRITE_PROXY).
|
||||
messageParam(proxyClass));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when the write packet proxy class should be removed.
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public void cleanup() {
|
||||
// Remove all proxy classes from the registry
|
||||
for (Class<?> stored : proxyClasses.values()) {
|
||||
PacketRegistry.getPacketToID().remove(stored);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
package com.comphenix.protocol.injector.packet;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.ForwardingMap;
|
||||
import com.google.common.collect.ForwardingMultimap;
|
||||
import com.google.common.collect.HashMultimap;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Multimap;
|
||||
|
||||
public class InverseMaps {
|
||||
private InverseMaps() {
|
||||
// Not constructable
|
||||
}
|
||||
|
||||
public static <K, V> Multimap<K, V> inverseMultimap(final Map<V, K> map, final Predicate<Map.Entry<V, K>> filter) {
|
||||
final MapContainer container = new MapContainer(map);
|
||||
|
||||
return new ForwardingMultimap<K, V>() {
|
||||
// The cached multimap
|
||||
private Multimap<K, V> inverseMultimap;
|
||||
|
||||
@Override
|
||||
protected Multimap<K, V> delegate() {
|
||||
if (container.hasChanged()) {
|
||||
inverseMultimap = HashMultimap.create();
|
||||
|
||||
// Construct the inverse map
|
||||
for (Map.Entry<V, K> entry : map.entrySet()) {
|
||||
if (filter.apply(entry)) {
|
||||
inverseMultimap.put(entry.getValue(), entry.getKey());
|
||||
}
|
||||
}
|
||||
container.setChanged(false);
|
||||
}
|
||||
return inverseMultimap;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static <K, V> Map<K, V> inverseMap(final Map<V, K> map, final Predicate<Map.Entry<V, K>> filter) {
|
||||
final MapContainer container = new MapContainer(map);
|
||||
|
||||
return new ForwardingMap<K, V>() {
|
||||
// The cached map
|
||||
private Map<K, V> inverseMap;
|
||||
|
||||
@Override
|
||||
protected Map<K, V> delegate() {
|
||||
if (container.hasChanged()) {
|
||||
inverseMap = Maps.newHashMap();
|
||||
|
||||
// Construct the inverse map
|
||||
for (Map.Entry<V, K> entry : map.entrySet()) {
|
||||
if (filter.apply(entry)) {
|
||||
inverseMap.put(entry.getValue(), entry.getKey());
|
||||
}
|
||||
}
|
||||
container.setChanged(false);
|
||||
}
|
||||
return inverseMap;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
-67
@@ -1,67 +0,0 @@
|
||||
package com.comphenix.protocol.injector.packet;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.events.ConnectionSide;
|
||||
import com.comphenix.protocol.events.NetworkMarker;
|
||||
import com.google.common.io.ByteStreams;
|
||||
import com.google.common.io.InputSupplier;
|
||||
import com.google.common.primitives.Bytes;
|
||||
|
||||
/**
|
||||
* Represents a network marker for 1.6.4 and earlier.
|
||||
* @author Kristian
|
||||
*/
|
||||
public class LegacyNetworkMarker extends NetworkMarker {
|
||||
public LegacyNetworkMarker(@Nonnull ConnectionSide side, byte[] inputBuffer, PacketType type) {
|
||||
super(side, inputBuffer, type);
|
||||
}
|
||||
|
||||
public LegacyNetworkMarker(@Nonnull ConnectionSide side, ByteBuffer inputBuffer, PacketType type) {
|
||||
super(side, inputBuffer, type);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DataInputStream skipHeader(DataInputStream input) throws IOException {
|
||||
// This has already been done
|
||||
return input;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ByteBuffer addHeader(ByteBuffer buffer, PacketType type) {
|
||||
return ByteBuffer.wrap(Bytes.concat(new byte[] { (byte) type.getLegacyId() }, buffer.array()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
protected DataInputStream addHeader(final DataInputStream input, final PacketType type) {
|
||||
InputSupplier<InputStream> header = new InputSupplier<InputStream>() {
|
||||
@Override
|
||||
public InputStream getInput() throws IOException {
|
||||
byte[] data = new byte[] { (byte) type.getLegacyId() };
|
||||
return new ByteArrayInputStream(data);
|
||||
}
|
||||
};
|
||||
|
||||
InputSupplier<InputStream> data = new InputSupplier<InputStream>() {
|
||||
@Override
|
||||
public InputStream getInput() throws IOException {
|
||||
return input;
|
||||
}
|
||||
};
|
||||
|
||||
// Combine them into a single stream
|
||||
try {
|
||||
return new DataInputStream(ByteStreams.join(header, data).getInput());
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Cannot add header.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
-334
@@ -1,334 +0,0 @@
|
||||
package com.comphenix.protocol.injector.packet;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import net.sf.cglib.proxy.Factory;
|
||||
|
||||
import com.comphenix.protocol.reflect.FieldAccessException;
|
||||
import com.comphenix.protocol.reflect.FieldUtils;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.comphenix.protocol.reflect.fuzzy.FuzzyClassContract;
|
||||
import com.comphenix.protocol.reflect.fuzzy.FuzzyFieldContract;
|
||||
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
import com.comphenix.protocol.wrappers.TroveWrapper;
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Multimap;
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
class LegacyPacketRegistry {
|
||||
private static final int MIN_SERVER_PACKETS = 5;
|
||||
private static final int MIN_CLIENT_PACKETS = 5;
|
||||
|
||||
// Fuzzy reflection
|
||||
private FuzzyReflection packetRegistry;
|
||||
|
||||
// The packet class to packet ID translator
|
||||
private Map<Class, Integer> packetToID;
|
||||
|
||||
// Packet IDs to classes, grouped by whether or not they're vanilla or custom defined
|
||||
private Multimap<Integer, Class> customIdToPacket;
|
||||
private Map<Integer, Class> vanillaIdToPacket;
|
||||
|
||||
// Whether or not certain packets are sent by the client or the server
|
||||
private ImmutableSet<Integer> serverPackets;
|
||||
private ImmutableSet<Integer> clientPackets;
|
||||
|
||||
// The underlying sets
|
||||
private Set<Integer> serverPacketsRef;
|
||||
private Set<Integer> clientPacketsRef;
|
||||
|
||||
// New proxy values
|
||||
private Map<Integer, Class> overwrittenPackets = new HashMap<Integer, Class>();
|
||||
|
||||
// Vanilla packets
|
||||
private Map<Integer, Class> previousValues = new HashMap<Integer, Class>();
|
||||
|
||||
/**
|
||||
* Initialize the registry.
|
||||
*/
|
||||
@SuppressWarnings({ "unchecked" })
|
||||
public void initialize() {
|
||||
if (packetToID == null) {
|
||||
try {
|
||||
Field packetsField = getPacketRegistry().getFieldByType("packetsField", Map.class);
|
||||
packetToID = (Map<Class, Integer>) FieldUtils.readStaticField(packetsField, true);
|
||||
} catch (IllegalArgumentException e) {
|
||||
// Spigot 1.2.5 MCPC workaround
|
||||
try {
|
||||
packetToID = getSpigotWrapper();
|
||||
} catch (Exception e2) {
|
||||
// Very bad indeed
|
||||
throw new IllegalArgumentException(e.getMessage() + "; Spigot workaround failed.", e2);
|
||||
}
|
||||
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException("Unable to retrieve the packetClassToIdMap", e);
|
||||
}
|
||||
|
||||
// Create the inverse maps
|
||||
customIdToPacket = InverseMaps.inverseMultimap(packetToID, new Predicate<Map.Entry<Class, Integer>>() {
|
||||
@Override
|
||||
public boolean apply(@Nullable Entry<Class, Integer> entry) {
|
||||
return !MinecraftReflection.isMinecraftClass(entry.getKey());
|
||||
}
|
||||
});
|
||||
|
||||
// And the vanilla pack - here we assume a unique ID to class mapping
|
||||
vanillaIdToPacket = InverseMaps.inverseMap(packetToID, new Predicate<Map.Entry<Class, Integer>>() {
|
||||
@Override
|
||||
public boolean apply(@Nullable Entry<Class, Integer> entry) {
|
||||
return MinecraftReflection.isMinecraftClass(entry.getKey());
|
||||
}
|
||||
});
|
||||
}
|
||||
initializeSets();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void initializeSets() throws FieldAccessException {
|
||||
if (serverPacketsRef == null || clientPacketsRef == null) {
|
||||
List<Field> sets = getPacketRegistry().getFieldListByType(Set.class);
|
||||
|
||||
try {
|
||||
if (sets.size() > 1) {
|
||||
serverPacketsRef = (Set<Integer>) FieldUtils.readStaticField(sets.get(0), true);
|
||||
clientPacketsRef = (Set<Integer>) FieldUtils.readStaticField(sets.get(1), true);
|
||||
|
||||
// Impossible
|
||||
if (serverPacketsRef == null || clientPacketsRef == null)
|
||||
throw new FieldAccessException("Packet sets are in an illegal state.");
|
||||
|
||||
// NEVER allow callers to modify the underlying sets
|
||||
serverPackets = ImmutableSet.copyOf(serverPacketsRef);
|
||||
clientPackets = ImmutableSet.copyOf(clientPacketsRef);
|
||||
|
||||
// Check sizes
|
||||
if (serverPackets.size() < MIN_SERVER_PACKETS)
|
||||
throw new InsufficientPacketsException("Insufficient server packets.", false, serverPackets.size());
|
||||
if (clientPackets.size() < MIN_CLIENT_PACKETS)
|
||||
throw new InsufficientPacketsException("Insufficient client packets.", true, clientPackets.size());
|
||||
|
||||
} else {
|
||||
throw new FieldAccessException("Cannot retrieve packet client/server sets.");
|
||||
}
|
||||
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new FieldAccessException("Cannot access field.", e);
|
||||
}
|
||||
|
||||
} else {
|
||||
// Copy over again if it has changed
|
||||
if (serverPacketsRef != null && serverPacketsRef.size() != serverPackets.size())
|
||||
serverPackets = ImmutableSet.copyOf(serverPacketsRef);
|
||||
if (clientPacketsRef != null && clientPacketsRef.size() != clientPackets.size())
|
||||
clientPackets = ImmutableSet.copyOf(clientPacketsRef);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the packet mapping.
|
||||
* @return The packet map.
|
||||
*/
|
||||
public Map<Class, Integer> getPacketToID() {
|
||||
// Initialize it, if we haven't already
|
||||
if (packetToID == null) {
|
||||
initialize();
|
||||
}
|
||||
return packetToID;
|
||||
}
|
||||
|
||||
private Map<Class, Integer> getSpigotWrapper() throws IllegalAccessException {
|
||||
// If it talks like a duck, etc.
|
||||
// Perhaps it would be nice to have a proper duck typing library as well
|
||||
FuzzyClassContract mapLike = FuzzyClassContract.newBuilder().
|
||||
method(FuzzyMethodContract.newBuilder().
|
||||
nameExact("size").returnTypeExact(int.class)).
|
||||
method(FuzzyMethodContract.newBuilder().
|
||||
nameExact("put").parameterCount(2)).
|
||||
method(FuzzyMethodContract.newBuilder().
|
||||
nameExact("get").parameterCount(1)).
|
||||
build();
|
||||
|
||||
Field packetsField = getPacketRegistry().getField(
|
||||
FuzzyFieldContract.newBuilder().typeMatches(mapLike).build());
|
||||
Object troveMap = FieldUtils.readStaticField(packetsField, true);
|
||||
|
||||
// Fix incorrect no entry values
|
||||
TroveWrapper.transformNoEntryValue(troveMap, new Function<Integer, Integer>() {
|
||||
public Integer apply(Integer value) {
|
||||
if (value >= 0 && value < 256) {
|
||||
// Someone forgot to set the no entry value. Let's help them.
|
||||
return -1;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
});
|
||||
|
||||
// We'll assume this a Trove map
|
||||
return TroveWrapper.getDecoratedMap(troveMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the cached fuzzy reflection instance allowing access to the packet registry.
|
||||
* @return Reflected packet registry.
|
||||
*/
|
||||
private FuzzyReflection getPacketRegistry() {
|
||||
if (packetRegistry == null)
|
||||
packetRegistry = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass(), true);
|
||||
return packetRegistry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the injected proxy classes handlig each packet ID.
|
||||
* @return Injected classes.
|
||||
*/
|
||||
public Map<Integer, Class> getOverwrittenPackets() {
|
||||
return overwrittenPackets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the vanilla classes handling each packet ID.
|
||||
* @return Vanilla classes.
|
||||
*/
|
||||
public Map<Integer, Class> getPreviousPackets() {
|
||||
return previousValues;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve every known and supported server packet.
|
||||
* @return An immutable set of every known server packet.
|
||||
* @throws FieldAccessException If we're unable to retrieve the server packet data from Minecraft.
|
||||
*/
|
||||
public Set<Integer> getServerPackets() throws FieldAccessException {
|
||||
initializeSets();
|
||||
|
||||
// Sanity check. This is impossible!
|
||||
if (serverPackets != null && serverPackets.size() < MIN_SERVER_PACKETS)
|
||||
throw new FieldAccessException("Server packet list is empty. Seems to be unsupported");
|
||||
return serverPackets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve every known and supported client packet.
|
||||
* @return An immutable set of every known client packet.
|
||||
* @throws FieldAccessException If we're unable to retrieve the client packet data from Minecraft.
|
||||
*/
|
||||
public Set<Integer> getClientPackets() throws FieldAccessException {
|
||||
initializeSets();
|
||||
|
||||
// As above
|
||||
if (clientPackets != null && clientPackets.size() < MIN_CLIENT_PACKETS)
|
||||
throw new FieldAccessException("Client packet list is empty. Seems to be unsupported");
|
||||
return clientPackets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the correct packet class from a given packet ID.
|
||||
* @param packetID - the packet ID.
|
||||
* @return The associated class.
|
||||
*/
|
||||
public Class getPacketClassFromID(int packetID) {
|
||||
return getPacketClassFromID(packetID, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the correct packet class from a given packet ID.
|
||||
* @param packetID - the packet ID.
|
||||
* @param forceVanilla - whether or not to look for vanilla classes, not injected classes.
|
||||
* @return The associated class.
|
||||
*/
|
||||
public Class getPacketClassFromID(int packetID, boolean forceVanilla) {
|
||||
Map<Integer, Class> lookup = forceVanilla ? previousValues : overwrittenPackets;
|
||||
Class<?> result = null;
|
||||
|
||||
// Optimized lookup
|
||||
if (lookup.containsKey(packetID)) {
|
||||
return removeEnhancer(lookup.get(packetID), forceVanilla);
|
||||
}
|
||||
|
||||
// Refresh lookup tables
|
||||
getPacketToID();
|
||||
|
||||
// See if we can look for non-vanilla classes
|
||||
if (!forceVanilla) {
|
||||
result = Iterables.getFirst(customIdToPacket.get(packetID), null);
|
||||
}
|
||||
if (result == null) {
|
||||
result = vanillaIdToPacket.get(packetID);
|
||||
}
|
||||
|
||||
// See if we got it
|
||||
if (result != null)
|
||||
return result;
|
||||
else
|
||||
throw new IllegalArgumentException("The packet ID " + packetID + " is not registered.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the packet ID of a given packet.
|
||||
* @param packet - the type of packet to check.
|
||||
* @return The ID of the given packet.
|
||||
* @throws IllegalArgumentException If this is not a valid packet.
|
||||
*/
|
||||
public int getPacketID(Class<?> packet) {
|
||||
if (packet == null)
|
||||
throw new IllegalArgumentException("Packet type class cannot be NULL.");
|
||||
if (!MinecraftReflection.getPacketClass().isAssignableFrom(packet))
|
||||
throw new IllegalArgumentException("Type must be a packet.");
|
||||
|
||||
// The registry contains both the overridden and original packets
|
||||
return getPacketToID().get(packet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the first superclass that is not a CBLib proxy object.
|
||||
* @param clazz - the class whose hierachy we're going to search through.
|
||||
* @param remove - whether or not to skip enhanced (proxy) classes.
|
||||
* @return If remove is TRUE, the first superclass that is not a proxy.
|
||||
*/
|
||||
private static Class removeEnhancer(Class clazz, boolean remove) {
|
||||
if (remove) {
|
||||
// Get the underlying vanilla class
|
||||
while (Factory.class.isAssignableFrom(clazz) && !clazz.equals(Object.class)) {
|
||||
clazz = clazz.getSuperclass();
|
||||
}
|
||||
}
|
||||
return clazz;
|
||||
}
|
||||
|
||||
/**
|
||||
* Occurs when we were unable to retrieve all the packets in the registry.
|
||||
* @author Kristian
|
||||
*/
|
||||
public static class InsufficientPacketsException extends RuntimeException {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final boolean client;
|
||||
private final int packetCount;
|
||||
|
||||
private InsufficientPacketsException(String message, boolean client, int packetCount) {
|
||||
super(message);
|
||||
this.client = client;
|
||||
this.packetCount = packetCount;
|
||||
}
|
||||
|
||||
public boolean isClient() {
|
||||
return client;
|
||||
}
|
||||
|
||||
public int getPacketCount() {
|
||||
return packetCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
-96
@@ -1,96 +0,0 @@
|
||||
package com.comphenix.protocol.injector.packet;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
import com.comphenix.protocol.ProtocolLibrary;
|
||||
import com.comphenix.protocol.ProtocolManager;
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.injector.ListenerInvoker;
|
||||
import com.comphenix.protocol.injector.PacketFilterManager;
|
||||
import com.comphenix.protocol.injector.player.PlayerInjectionHandler;
|
||||
import com.comphenix.protocol.reflect.FieldAccessException;
|
||||
import com.google.common.base.Preconditions;
|
||||
|
||||
/**
|
||||
* A builder responsible for creating incoming packet injectors.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public class PacketInjectorBuilder {
|
||||
protected PacketInjectorBuilder() {
|
||||
// No need to construct this
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a new packet injector builder.
|
||||
* @return Injector builder.
|
||||
*/
|
||||
public static PacketInjectorBuilder newBuilder() {
|
||||
return new PacketInjectorBuilder();
|
||||
}
|
||||
|
||||
protected ListenerInvoker invoker;
|
||||
protected ErrorReporter reporter;
|
||||
protected PlayerInjectionHandler playerInjection;
|
||||
|
||||
/**
|
||||
* The error reporter used by the created injector.
|
||||
* @param reporter - new error reporter.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public PacketInjectorBuilder reporter(@Nonnull ErrorReporter reporter) {
|
||||
Preconditions.checkNotNull(reporter, "reporter cannot be NULL");
|
||||
this.reporter = reporter;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The packet stream invoker.
|
||||
* @param invoker - the invoker.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public PacketInjectorBuilder invoker(@Nonnull ListenerInvoker invoker) {
|
||||
Preconditions.checkNotNull(invoker, "invoker cannot be NULL");
|
||||
this.invoker = invoker;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the player injection.
|
||||
* @param playerInjection - the injection.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
@Nonnull
|
||||
public PacketInjectorBuilder playerInjection(@Nonnull PlayerInjectionHandler playerInjection) {
|
||||
Preconditions.checkNotNull(playerInjection, "playerInjection cannot be NULL");
|
||||
this.playerInjection = playerInjection;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called before an object is created with this builder.
|
||||
*/
|
||||
private void initializeDefaults() {
|
||||
ProtocolManager manager = ProtocolLibrary.getProtocolManager();
|
||||
|
||||
// Initialize with default values if we can
|
||||
if (reporter == null)
|
||||
reporter = ProtocolLibrary.getErrorReporter();
|
||||
if (invoker == null)
|
||||
invoker = (PacketFilterManager) manager;
|
||||
if (playerInjection == null)
|
||||
throw new IllegalStateException("Player injection parameter must be initialized.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a packet injector using the provided fields or the default values.
|
||||
* <p>
|
||||
* Note that any non-null builder parameters must be set.
|
||||
* @return The created injector.
|
||||
* @throws FieldAccessException If anything goes wrong in terms of reflection.
|
||||
*/
|
||||
public PacketInjector buildInjector() throws FieldAccessException {
|
||||
initializeDefaults();
|
||||
return new ProxyPacketInjector(invoker, playerInjection, reporter);
|
||||
}
|
||||
}
|
||||
@@ -1,424 +0,0 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol.injector.packet;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.PacketType.Sender;
|
||||
import com.comphenix.protocol.ProtocolLibrary;
|
||||
import com.comphenix.protocol.error.Report;
|
||||
import com.comphenix.protocol.error.ReportType;
|
||||
import com.comphenix.protocol.injector.netty.NettyProtocolRegistry;
|
||||
import com.comphenix.protocol.injector.packet.LegacyPacketRegistry.InsufficientPacketsException;
|
||||
import com.comphenix.protocol.reflect.FieldAccessException;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
import com.comphenix.protocol.wrappers.TroveWrapper.CannotFindTroveNoEntryValue;
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Sets;
|
||||
|
||||
/**
|
||||
* Static packet registry in Minecraft.
|
||||
* @author Kristian
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
public class PacketRegistry {
|
||||
public static final ReportType REPORT_CANNOT_CORRECT_TROVE_MAP = new ReportType("Unable to correct no entry value.");
|
||||
|
||||
public static final ReportType REPORT_INSUFFICIENT_SERVER_PACKETS = new ReportType("Too few server packets detected: %s");
|
||||
public static final ReportType REPORT_INSUFFICIENT_CLIENT_PACKETS = new ReportType("Too few client packets detected: %s");
|
||||
|
||||
// Two different packet registry
|
||||
private static volatile LegacyPacketRegistry LEGACY;
|
||||
private static volatile NettyProtocolRegistry NETTY;
|
||||
|
||||
// Cached for legacy
|
||||
private static volatile Set<PacketType> NETTY_SERVER_PACKETS;
|
||||
private static volatile Set<PacketType> NETTY_CLIENT_PACKETS;
|
||||
|
||||
// Cached for Netty
|
||||
private static volatile Set<Integer> LEGACY_SERVER_PACKETS;
|
||||
private static volatile Set<Integer> LEGACY_CLIENT_PACKETS;
|
||||
private static volatile Map<Integer, Class> LEGACY_PREVIOUS_PACKETS;
|
||||
|
||||
// Whether or not the registry has
|
||||
private static boolean INITIALIZED;
|
||||
|
||||
/**
|
||||
* Initialize the packet registry.
|
||||
*/
|
||||
private static void initialize() {
|
||||
if (INITIALIZED) {
|
||||
// Make sure they were initialized
|
||||
if (NETTY == null && LEGACY == null)
|
||||
throw new IllegalStateException("No initialized registry.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for netty
|
||||
if (MinecraftReflection.isUsingNetty()) {
|
||||
if (NETTY == null) {
|
||||
NETTY = new NettyProtocolRegistry();
|
||||
}
|
||||
} else {
|
||||
initializeLegacy();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the given packet type is supported on the current server.
|
||||
* @param type - the type to check.
|
||||
* @return TRUE if it is, FALSE otherwise.
|
||||
*/
|
||||
public static boolean isSupported(PacketType type) {
|
||||
initialize();
|
||||
|
||||
if (NETTY != null)
|
||||
return NETTY.getPacketTypeLookup().containsKey(type);
|
||||
|
||||
// Look up the correct type
|
||||
return type.isClient() ?
|
||||
LEGACY.getClientPackets().contains(type.getLegacyId()) :
|
||||
LEGACY.getServerPackets().contains(type.getLegacyId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the legacy packet registry.
|
||||
*/
|
||||
private static void initializeLegacy() {
|
||||
if (LEGACY == null) {
|
||||
try {
|
||||
LEGACY = new LegacyPacketRegistry();
|
||||
LEGACY.initialize();
|
||||
} catch (InsufficientPacketsException e) {
|
||||
if (e.isClient()) {
|
||||
ProtocolLibrary.getErrorReporter().reportWarning(
|
||||
PacketRegistry.class, Report.newBuilder(REPORT_INSUFFICIENT_CLIENT_PACKETS).messageParam(e.getPacketCount())
|
||||
);
|
||||
} else {
|
||||
ProtocolLibrary.getErrorReporter().reportWarning(
|
||||
PacketRegistry.class, Report.newBuilder(REPORT_INSUFFICIENT_SERVER_PACKETS).messageParam(e.getPacketCount())
|
||||
);
|
||||
}
|
||||
} catch (CannotFindTroveNoEntryValue e) {
|
||||
ProtocolLibrary.getErrorReporter().reportWarning(PacketRegistry.class,
|
||||
Report.newBuilder(REPORT_CANNOT_CORRECT_TROVE_MAP).error(e.getCause()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a map of every packet class to every ID.
|
||||
* <p>
|
||||
* Deprecated: Use {@link #getPacketToType()} instead.
|
||||
* @return A map of packet classes and their corresponding ID.
|
||||
*/
|
||||
@Deprecated
|
||||
public static Map<Class, Integer> getPacketToID() {
|
||||
initialize();
|
||||
|
||||
if (NETTY != null) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<Class, Integer> result = (Map)Maps.transformValues(NETTY.getPacketClassLookup(), new Function<PacketType, Integer>() {
|
||||
public Integer apply(PacketType type) {
|
||||
return type.getLegacyId();
|
||||
};
|
||||
});
|
||||
return result;
|
||||
}
|
||||
return LEGACY.getPacketToID();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a map of every packet class to the respective packet type.
|
||||
* @return A map of packet classes and their corresponding packet type.
|
||||
*/
|
||||
public static Map<Class, PacketType> getPacketToType() {
|
||||
initialize();
|
||||
|
||||
if (NETTY != null) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<Class, PacketType> result = (Map)NETTY.getPacketClassLookup();
|
||||
return result;
|
||||
}
|
||||
return Maps.transformValues(LEGACY.getPacketToID(), new Function<Integer, PacketType>() {
|
||||
public PacketType apply(Integer packetId) {
|
||||
return PacketType.findLegacy(packetId);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the injected proxy classes handlig each packet ID.
|
||||
* <p>
|
||||
* This is not supported in 1.7.2 and later.
|
||||
* @return Injected classes.
|
||||
*/
|
||||
@Deprecated
|
||||
public static Map<Integer, Class> getOverwrittenPackets() {
|
||||
initialize();
|
||||
|
||||
if (LEGACY != null)
|
||||
return LEGACY.getOverwrittenPackets();
|
||||
throw new IllegalStateException("Not supported on Netty.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the vanilla classes handling each packet ID.
|
||||
* @return Vanilla classes.
|
||||
*/
|
||||
@Deprecated
|
||||
public static Map<Integer, Class> getPreviousPackets() {
|
||||
initialize();
|
||||
|
||||
if (NETTY != null) {
|
||||
// Construct it first
|
||||
if (LEGACY_PREVIOUS_PACKETS == null) {
|
||||
Map<Integer, Class> map = Maps.newHashMap();
|
||||
|
||||
for (Entry<PacketType, Class<?>> entry : NETTY.getPacketTypeLookup().entrySet()) {
|
||||
map.put(entry.getKey().getLegacyId(), entry.getValue());
|
||||
}
|
||||
LEGACY_PREVIOUS_PACKETS = Collections.unmodifiableMap(map);
|
||||
}
|
||||
return LEGACY_PREVIOUS_PACKETS;
|
||||
}
|
||||
return LEGACY.getPreviousPackets();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve every known and supported server packet.
|
||||
* <p>
|
||||
* Deprecated: Use {@link #getServerPacketTypes()} instead.
|
||||
* @return An immutable set of every known server packet.
|
||||
* @throws FieldAccessException If we're unable to retrieve the server packet data from Minecraft.
|
||||
*/
|
||||
@Deprecated
|
||||
public static Set<Integer> getServerPackets() throws FieldAccessException {
|
||||
initialize();
|
||||
|
||||
if (NETTY != null) {
|
||||
if (LEGACY_SERVER_PACKETS == null) {
|
||||
LEGACY_SERVER_PACKETS = toLegacy(NETTY.getServerPackets());
|
||||
}
|
||||
return LEGACY_SERVER_PACKETS;
|
||||
}
|
||||
return LEGACY.getServerPackets();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve every known and supported server packet type.
|
||||
* @return Every server packet type.
|
||||
*/
|
||||
public static Set<PacketType> getServerPacketTypes() {
|
||||
initialize();
|
||||
|
||||
if (NETTY != null) {
|
||||
NETTY.synchronize();
|
||||
return NETTY.getServerPackets();
|
||||
}
|
||||
|
||||
// Handle legacy
|
||||
if (NETTY_SERVER_PACKETS == null) {
|
||||
NETTY_SERVER_PACKETS = toPacketTypes(LEGACY.getServerPackets(), Sender.SERVER);
|
||||
}
|
||||
return NETTY_SERVER_PACKETS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve every known and supported client packet.
|
||||
* <p>
|
||||
* Deprecated: Use {@link #getClientPacketTypes()} instead.
|
||||
* @return An immutable set of every known client packet.
|
||||
* @throws FieldAccessException If we're unable to retrieve the client packet data from Minecraft.
|
||||
*/
|
||||
@Deprecated
|
||||
public static Set<Integer> getClientPackets() throws FieldAccessException {
|
||||
initialize();
|
||||
|
||||
if (NETTY != null) {
|
||||
if (LEGACY_CLIENT_PACKETS == null) {
|
||||
LEGACY_CLIENT_PACKETS = toLegacy(NETTY.getClientPackets());
|
||||
}
|
||||
return LEGACY_CLIENT_PACKETS;
|
||||
}
|
||||
return LEGACY.getClientPackets();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve every known and supported server packet type.
|
||||
* @return Every server packet type.
|
||||
*/
|
||||
public static Set<PacketType> getClientPacketTypes() {
|
||||
initialize();
|
||||
|
||||
if (NETTY != null) {
|
||||
NETTY.synchronize();
|
||||
return NETTY.getClientPackets();
|
||||
}
|
||||
|
||||
// Handle legacy
|
||||
if (NETTY_CLIENT_PACKETS == null) {
|
||||
NETTY_CLIENT_PACKETS = toPacketTypes(LEGACY.getClientPackets(), Sender.CLIENT);
|
||||
}
|
||||
return NETTY_CLIENT_PACKETS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a set of packet types to a set of integers based on the legacy packet ID.
|
||||
* @param types - packet type.
|
||||
* @return Set of integers.
|
||||
*/
|
||||
public static Set<Integer> toLegacy(Set<PacketType> types) {
|
||||
Set<Integer> result = Sets.newHashSet();
|
||||
|
||||
for (PacketType type : types)
|
||||
result.add(type.getLegacyId());
|
||||
return Collections.unmodifiableSet(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a set of legacy packet IDs to packet types.
|
||||
* @param types - legacy packet IDs.
|
||||
* @return Set of packet types.
|
||||
*/
|
||||
public static Set<PacketType> toPacketTypes(Set<Integer> ids) {
|
||||
return toPacketTypes(ids, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a set of legacy packet IDs to packet types.
|
||||
* @param types - legacy packet IDs.
|
||||
* @param preference - the sender preference, if any.
|
||||
* @return Set of packet types.
|
||||
*/
|
||||
public static Set<PacketType> toPacketTypes(Set<Integer> ids, Sender preference) {
|
||||
Set<PacketType> result = Sets.newHashSet();
|
||||
|
||||
for (int id : ids)
|
||||
result.add(PacketType.fromLegacy(id, preference));
|
||||
return Collections.unmodifiableSet(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the correct packet class from a given packet ID.
|
||||
* <p>
|
||||
* Deprecated: Use {@link #getPacketClassFromType(PacketType)} instead.
|
||||
* @param packetID - the packet ID.
|
||||
* @return The associated class.
|
||||
*/
|
||||
@Deprecated
|
||||
public static Class getPacketClassFromID(int packetID) {
|
||||
initialize();
|
||||
|
||||
if (NETTY != null)
|
||||
return NETTY.getPacketTypeLookup().get(PacketType.findLegacy(packetID));
|
||||
return LEGACY.getPacketClassFromID(packetID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the correct packet class from a given type.
|
||||
* @param type - the packet type.
|
||||
* @return The associated class.
|
||||
*/
|
||||
public static Class getPacketClassFromType(PacketType type) {
|
||||
return getPacketClassFromType(type, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the correct packet class from a given type.
|
||||
* <p>
|
||||
* Note that forceVanillla will be ignored on MC 1.7.2 and later.
|
||||
* @param type - the packet type.
|
||||
* @param forceVanilla - whether or not to look for vanilla classes, not injected classes.
|
||||
* @return The associated class.
|
||||
*/
|
||||
public static Class getPacketClassFromType(PacketType type, boolean forceVanilla) {
|
||||
initialize();
|
||||
|
||||
if (NETTY != null)
|
||||
return NETTY.getPacketTypeLookup().get(type);
|
||||
return LEGACY.getPacketClassFromID(type.getLegacyId(), forceVanilla);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the correct packet class from a given packet ID.
|
||||
* <p>
|
||||
* This method has been deprecated.
|
||||
* @param packetID - the packet ID.
|
||||
* @param forceVanilla - whether or not to look for vanilla classes, not injected classes.
|
||||
* @return The associated class.
|
||||
*/
|
||||
@Deprecated
|
||||
public static Class getPacketClassFromID(int packetID, boolean forceVanilla) {
|
||||
initialize();
|
||||
|
||||
if (LEGACY != null)
|
||||
return LEGACY.getPacketClassFromID(packetID, forceVanilla);
|
||||
return getPacketClassFromID(packetID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the packet ID of a given packet.
|
||||
* <p>
|
||||
* Deprecated: Use {@link #getPacketType(Class)}.
|
||||
* @param packet - the type of packet to check.
|
||||
* @return The legacy ID of the given packet.
|
||||
* @throws IllegalArgumentException If this is not a valid packet.
|
||||
*/
|
||||
@Deprecated
|
||||
public static int getPacketID(Class<?> packet) {
|
||||
initialize();
|
||||
|
||||
if (NETTY != null)
|
||||
return NETTY.getPacketClassLookup().get(packet).getLegacyId();
|
||||
return LEGACY.getPacketID(packet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the packet type of a given packet.
|
||||
* @param packet - the class of the packet.
|
||||
* @return The packet type, or NULL if not found.
|
||||
*/
|
||||
public static PacketType getPacketType(Class<?> packet) {
|
||||
return getPacketType(packet, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the packet type of a given packet.
|
||||
* @param packet - the class of the packet.
|
||||
* @param sender - the sender of the packet, or NULL.
|
||||
* @return The packet type, or NULL if not found.
|
||||
*/
|
||||
public static PacketType getPacketType(Class<?> packet, Sender sender) {
|
||||
initialize();
|
||||
|
||||
if (NETTY != null) {
|
||||
return NETTY.getPacketClassLookup().get(packet);
|
||||
} else {
|
||||
final int id = LEGACY.getPacketID(packet);
|
||||
|
||||
return PacketType.hasLegacy(id) ?
|
||||
PacketType.fromLegacy(id, sender) : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
-372
@@ -1,372 +0,0 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol.injector.packet;
|
||||
|
||||
import java.io.DataInput;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import net.sf.cglib.proxy.Callback;
|
||||
import net.sf.cglib.proxy.CallbackFilter;
|
||||
import net.sf.cglib.proxy.Enhancer;
|
||||
import net.sf.cglib.proxy.Factory;
|
||||
import net.sf.cglib.proxy.NoOp;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.PacketType.Sender;
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.error.Report;
|
||||
import com.comphenix.protocol.error.ReportType;
|
||||
import com.comphenix.protocol.events.ConnectionSide;
|
||||
import com.comphenix.protocol.events.ListenerOptions;
|
||||
import com.comphenix.protocol.events.NetworkMarker;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.injector.ListenerInvoker;
|
||||
import com.comphenix.protocol.injector.player.PlayerInjectionHandler;
|
||||
import com.comphenix.protocol.reflect.FieldAccessException;
|
||||
import com.comphenix.protocol.reflect.FieldUtils;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.comphenix.protocol.reflect.MethodInfo;
|
||||
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
|
||||
import com.comphenix.protocol.utility.EnhancerFactory;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
import com.comphenix.protocol.wrappers.WrappedIntHashMap;
|
||||
|
||||
/**
|
||||
* This class is responsible for adding or removing proxy objects that intercepts received packets.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
class ProxyPacketInjector implements PacketInjector {
|
||||
public static final ReportType REPORT_CANNOT_FIND_READ_PACKET_METHOD = new ReportType("Cannot find read packet method for ID %s.");
|
||||
public static final ReportType REPORT_UNKNOWN_ORIGIN_FOR_PACKET = new ReportType("Timeout: Unknown origin %s for packet %s. Are you using GamePhase.LOGIN?");
|
||||
|
||||
/**
|
||||
* Represents a way to update the packet ID to class lookup table.
|
||||
* @author Kristian
|
||||
*/
|
||||
private static interface PacketClassLookup {
|
||||
public void setLookup(int packetID, Class<?> clazz);
|
||||
}
|
||||
|
||||
private static class IntHashMapLookup implements PacketClassLookup {
|
||||
private WrappedIntHashMap intHashMap;
|
||||
|
||||
public IntHashMapLookup() throws IllegalAccessException {
|
||||
initialize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLookup(int packetID, Class<?> clazz) {
|
||||
intHashMap.put(packetID, clazz);
|
||||
}
|
||||
|
||||
private void initialize() throws IllegalAccessException {
|
||||
if (intHashMap == null) {
|
||||
// We're looking for the first static field with a Minecraft-object. This should be a IntHashMap.
|
||||
Field intHashMapField = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass(), true).
|
||||
getFieldByType("packetIdMap", MinecraftReflection.getIntHashMapClass());
|
||||
|
||||
try {
|
||||
intHashMap = WrappedIntHashMap.fromHandle(
|
||||
FieldUtils.readField(intHashMapField, (Object) null, true));
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new RuntimeException("Minecraft is incompatible.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class ArrayLookup implements PacketClassLookup {
|
||||
private Class<?>[] array;
|
||||
|
||||
public ArrayLookup() throws IllegalAccessException {
|
||||
initialize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLookup(int packetID, Class<?> clazz) {
|
||||
array[packetID] = clazz;
|
||||
}
|
||||
|
||||
private void initialize() throws IllegalAccessException {
|
||||
FuzzyReflection reflection = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass());
|
||||
|
||||
// Is there a Class array with 256 elements instead?
|
||||
for (Field field : reflection.getFieldListByType(Class[].class)) {
|
||||
Class<?>[] test = (Class<?>[]) FieldUtils.readField(field, (Object)null);
|
||||
|
||||
if (test.length == 256) {
|
||||
array = test;
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException(
|
||||
"Unable to find an array with the type " + Class[].class +
|
||||
" in " + MinecraftReflection.getPacketClass());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Matches the readPacketData(DataInputStream) method in Packet.
|
||||
*/
|
||||
private static FuzzyMethodContract READ_PACKET = FuzzyMethodContract.newBuilder().
|
||||
returnTypeVoid().
|
||||
parameterDerivedOf(DataInput.class).
|
||||
parameterCount(1).
|
||||
build();
|
||||
|
||||
private static PacketClassLookup lookup;
|
||||
|
||||
// The packet filter manager
|
||||
private ListenerInvoker manager;
|
||||
|
||||
// Error reporter
|
||||
private ErrorReporter reporter;
|
||||
|
||||
// Allows us to determine the sender
|
||||
private PlayerInjectionHandler playerInjection;
|
||||
|
||||
// Share callback filter
|
||||
private CallbackFilter filter;
|
||||
|
||||
// Determine if the read packet method was found
|
||||
private boolean readPacketIntercepted = false;
|
||||
|
||||
public ProxyPacketInjector(ListenerInvoker manager, PlayerInjectionHandler playerInjection,
|
||||
ErrorReporter reporter) throws FieldAccessException {
|
||||
|
||||
this.manager = manager;
|
||||
this.playerInjection = playerInjection;
|
||||
this.reporter = reporter;
|
||||
initialize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancelled(Object packet) {
|
||||
return ReadPacketModifier.isCancelled(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCancelled(Object packet, boolean cancelled) {
|
||||
if (cancelled) {
|
||||
ReadPacketModifier.setOverride(packet, null);
|
||||
} else {
|
||||
ReadPacketModifier.removeOverride(packet);
|
||||
}
|
||||
}
|
||||
|
||||
private void initialize() throws FieldAccessException {
|
||||
if (lookup == null) {
|
||||
try {
|
||||
lookup = new IntHashMapLookup();
|
||||
} catch (Exception e1) {
|
||||
|
||||
try {
|
||||
lookup = new ArrayLookup();
|
||||
} catch (Exception e2) {
|
||||
// Wow
|
||||
throw new FieldAccessException(e1.getMessage() + ". Workaround failed too.", e2);
|
||||
}
|
||||
}
|
||||
|
||||
// Should work fine now
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void inputBuffersChanged(Set<PacketType> set) {
|
||||
// No need to do anything
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings({"rawtypes", "deprecation"})
|
||||
public boolean addPacketHandler(PacketType type, Set<ListenerOptions> options) {
|
||||
final int packetID = type.getLegacyId();
|
||||
|
||||
if (hasPacketHandler(type))
|
||||
return false;
|
||||
|
||||
Enhancer ex = EnhancerFactory.getInstance().createEnhancer();
|
||||
|
||||
// Unfortunately, we can't easily distinguish between these two functions:
|
||||
// * Object lookup(int par1)
|
||||
// * Object removeObject(int par1)
|
||||
|
||||
// So, we'll use the classMapToInt registry instead.
|
||||
Map<Integer, Class> overwritten = PacketRegistry.getOverwrittenPackets();
|
||||
Map<Integer, Class> previous = PacketRegistry.getPreviousPackets();
|
||||
Map<Class, Integer> registry = PacketRegistry.getPacketToID();
|
||||
Class old = PacketRegistry.getPacketClassFromType(type);
|
||||
|
||||
// If this packet is not known
|
||||
if (old == null) {
|
||||
throw new IllegalStateException("Packet ID " + type + " is not a valid packet type in this version.");
|
||||
}
|
||||
// Check for previous injections
|
||||
if (Factory.class.isAssignableFrom(old)) {
|
||||
throw new IllegalStateException("Packet " + type + " has already been injected.");
|
||||
}
|
||||
|
||||
if (filter == null) {
|
||||
readPacketIntercepted = false;
|
||||
|
||||
filter = new CallbackFilter() {
|
||||
@Override
|
||||
public int accept(Method method) {
|
||||
// Skip methods defined in Object
|
||||
if (method.getDeclaringClass().equals(Object.class)) {
|
||||
return 0;
|
||||
} else if (READ_PACKET.isMatch(MethodInfo.fromMethod(method), null)) {
|
||||
readPacketIntercepted = true;
|
||||
return 1;
|
||||
} else {
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Subclass the specific packet class
|
||||
ex.setSuperclass(old);
|
||||
ex.setCallbackFilter(filter);
|
||||
ex.setCallbackTypes(new Class<?>[] { NoOp.class, ReadPacketModifier.class, ReadPacketModifier.class });
|
||||
Class proxy = ex.createClass();
|
||||
|
||||
// Create the proxy handlers
|
||||
ReadPacketModifier modifierReadPacket = new ReadPacketModifier(packetID, this, reporter, true);
|
||||
ReadPacketModifier modifierRest = new ReadPacketModifier(packetID, this, reporter, false);
|
||||
|
||||
// Add a static reference
|
||||
Enhancer.registerStaticCallbacks(proxy, new Callback[] { NoOp.INSTANCE, modifierReadPacket, modifierRest });
|
||||
|
||||
// Check that we found the read method
|
||||
if (!readPacketIntercepted) {
|
||||
reporter.reportWarning(this,
|
||||
Report.newBuilder(REPORT_CANNOT_FIND_READ_PACKET_METHOD).messageParam(packetID));
|
||||
}
|
||||
|
||||
// Override values
|
||||
previous.put(packetID, old);
|
||||
registry.put(proxy, packetID);
|
||||
overwritten.put(packetID, proxy);
|
||||
lookup.setLookup(packetID, proxy);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings({"rawtypes", "deprecation"})
|
||||
public boolean removePacketHandler(PacketType type) {
|
||||
final int packetID = type.getLegacyId();
|
||||
|
||||
if (!hasPacketHandler(type))
|
||||
return false;
|
||||
|
||||
Map<Class, Integer> registry = PacketRegistry.getPacketToID();
|
||||
Map<Integer, Class> previous = PacketRegistry.getPreviousPackets();
|
||||
Map<Integer, Class> overwritten = PacketRegistry.getOverwrittenPackets();
|
||||
|
||||
Class old = previous.get(packetID);
|
||||
Class proxy = PacketRegistry.getPacketClassFromType(type);
|
||||
|
||||
lookup.setLookup(packetID, old);
|
||||
previous.remove(packetID);
|
||||
registry.remove(proxy);
|
||||
overwritten.remove(packetID);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the data a packet read must be buffered.
|
||||
* @param packetId - the packet to check.
|
||||
* @return TRUE if it does, FALSE otherwise.
|
||||
*/
|
||||
@Deprecated
|
||||
public boolean requireInputBuffers(int packetId) {
|
||||
return manager.requireInputBuffer(packetId);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public boolean hasPacketHandler(PacketType type) {
|
||||
return PacketRegistry.getPreviousPackets().containsKey(type.getLegacyId());
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public Set<PacketType> getPacketHandlers() {
|
||||
return PacketRegistry.toPacketTypes(PacketRegistry.getPreviousPackets().keySet(), Sender.CLIENT);
|
||||
}
|
||||
|
||||
// Called from the ReadPacketModified monitor
|
||||
public PacketEvent packetRecieved(PacketContainer packet, InputStream input, byte[] buffered) {
|
||||
if (playerInjection.canRecievePackets()) {
|
||||
return playerInjection.handlePacketRecieved(packet, input, buffered);
|
||||
}
|
||||
|
||||
try {
|
||||
Player client = playerInjection.getPlayerByConnection((DataInputStream) input);
|
||||
|
||||
// Never invoke a event if we don't know where it's from
|
||||
if (client != null) {
|
||||
return packetRecieved(packet, client, buffered);
|
||||
} else {
|
||||
// The timeout elapsed!
|
||||
reporter.reportWarning(this, Report.newBuilder(REPORT_UNKNOWN_ORIGIN_FOR_PACKET).messageParam(input, packet.getType()));
|
||||
return null;
|
||||
}
|
||||
|
||||
} catch (InterruptedException e) {
|
||||
// We will ignore this - it occurs when a player disconnects
|
||||
//reporter.reportDetailed(this, "Thread was interrupted.", e, packet, input);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketEvent packetRecieved(PacketContainer packet, Player client, byte[] buffered) {
|
||||
NetworkMarker marker = buffered != null ? new LegacyNetworkMarker(ConnectionSide.CLIENT_SIDE, buffered, packet.getType()) : null;
|
||||
PacketEvent event = PacketEvent.fromClient(manager, packet, marker, client);
|
||||
|
||||
manager.invokePacketRecieving(event);
|
||||
return event;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings({"rawtypes", "deprecation"})
|
||||
public synchronized void cleanupAll() {
|
||||
Map<Integer, Class> overwritten = PacketRegistry.getOverwrittenPackets();
|
||||
Map<Integer, Class> previous = PacketRegistry.getPreviousPackets();
|
||||
|
||||
// Remove every packet handler
|
||||
for (Integer id : previous.keySet().toArray(new Integer[0])) {
|
||||
removePacketHandler(PacketType.findLegacy(id, Sender.CLIENT));
|
||||
removePacketHandler(PacketType.findLegacy(id, Sender.SERVER));
|
||||
}
|
||||
|
||||
overwritten.clear();
|
||||
previous.clear();
|
||||
}
|
||||
}
|
||||
-186
@@ -1,186 +0,0 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol.injector.packet;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Map;
|
||||
|
||||
import net.sf.cglib.proxy.MethodInterceptor;
|
||||
import net.sf.cglib.proxy.MethodProxy;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.PacketType.Sender;
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.error.Report;
|
||||
import com.comphenix.protocol.error.ReportType;
|
||||
import com.comphenix.protocol.events.NetworkMarker;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.injector.NetworkProcessor;
|
||||
import com.google.common.collect.MapMaker;
|
||||
|
||||
class ReadPacketModifier implements MethodInterceptor {
|
||||
public static final ReportType REPORT_CANNOT_HANDLE_CLIENT_PACKET = new ReportType("Cannot handle client packet.");
|
||||
|
||||
// A cancel marker
|
||||
private static final Object CANCEL_MARKER = new Object();
|
||||
|
||||
// Common for all packets of the same type
|
||||
private ProxyPacketInjector packetInjector;
|
||||
private int packetID;
|
||||
|
||||
// Report errors
|
||||
private ErrorReporter reporter;
|
||||
private NetworkProcessor processor;
|
||||
|
||||
// If this is a read packet data method
|
||||
private boolean isReadPacketDataMethod;
|
||||
|
||||
// Whether or not a packet has been cancelled
|
||||
private static Map<Object, Object> override = new MapMaker().weakKeys().makeMap();
|
||||
|
||||
public ReadPacketModifier(int packetID, ProxyPacketInjector packetInjector, ErrorReporter reporter, boolean isReadPacketDataMethod) {
|
||||
this.packetID = packetID;
|
||||
this.packetInjector = packetInjector;
|
||||
this.reporter = reporter;
|
||||
this.processor = new NetworkProcessor(reporter);
|
||||
this.isReadPacketDataMethod = isReadPacketDataMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove any packet overrides.
|
||||
* @param packet - the packet to rever
|
||||
*/
|
||||
public static void removeOverride(Object packet) {
|
||||
override.remove(packet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the packet that overrides the methods of the given packet.
|
||||
* @param packet - the given packet.
|
||||
* @return Overriden object.
|
||||
*/
|
||||
public static Object getOverride(Object packet) {
|
||||
return override.get(packet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the packet instance to delegate to instead, or mark the packet as cancelled.
|
||||
* <p>
|
||||
* To undo a override, use {@link #removeOverride(Object)}.
|
||||
* @param packet - the packet.
|
||||
* @param override - the override method. NULL to cancel this packet.
|
||||
*/
|
||||
public static void setOverride(Object packet, Object overridePacket) {
|
||||
override.put(packet, overridePacket != null ? overridePacket : CANCEL_MARKER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the given packet has been cancelled before.
|
||||
* @param packet - the packet to check.
|
||||
* @return TRUE if it has been cancelled, FALSE otherwise.
|
||||
*/
|
||||
public static boolean isCancelled(Object packet) {
|
||||
return getOverride(packet) == CANCEL_MARKER;
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public Object intercept(Object thisObj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
|
||||
// Atomic retrieval
|
||||
Object overridenObject = override.get(thisObj);
|
||||
Object returnValue = null;
|
||||
|
||||
// We need this in order to get the correct player
|
||||
InputStream input = isReadPacketDataMethod ? (InputStream) args[0] : null;
|
||||
ByteArrayOutputStream bufferStream = null;
|
||||
|
||||
// See if we need to buffer the read data
|
||||
if (isReadPacketDataMethod && packetInjector.requireInputBuffers(packetID)) {
|
||||
CaptureInputStream captured = new CaptureInputStream(
|
||||
input, bufferStream = new ByteArrayOutputStream());
|
||||
|
||||
// Swap it with our custom stream
|
||||
args[0] = new DataInputStream(captured);
|
||||
}
|
||||
|
||||
if (overridenObject != null) {
|
||||
// This packet has been cancelled
|
||||
if (overridenObject == CANCEL_MARKER) {
|
||||
// So, cancel all void methods
|
||||
if (method.getReturnType().equals(Void.TYPE))
|
||||
return null;
|
||||
else // Revert to normal for everything else
|
||||
overridenObject = thisObj;
|
||||
}
|
||||
|
||||
returnValue = proxy.invokeSuper(overridenObject, args);
|
||||
} else {
|
||||
returnValue = proxy.invokeSuper(thisObj, args);
|
||||
}
|
||||
|
||||
// Is this a readPacketData method?
|
||||
if (isReadPacketDataMethod) {
|
||||
// Swap back custom stream
|
||||
args[0] = input;
|
||||
|
||||
try {
|
||||
byte[] buffer = bufferStream != null ? bufferStream.toByteArray() : null;
|
||||
|
||||
// Let the people know
|
||||
PacketType type = PacketType.findLegacy(packetID, Sender.CLIENT);
|
||||
PacketContainer container = new PacketContainer(type, thisObj);
|
||||
PacketEvent event = packetInjector.packetRecieved(container, input, buffer);
|
||||
|
||||
// Handle override
|
||||
if (event != null) {
|
||||
Object result = event.getPacket().getHandle();
|
||||
|
||||
if (event.isCancelled()) {
|
||||
override.put(thisObj, CANCEL_MARKER);
|
||||
return returnValue;
|
||||
} else if (!objectEquals(thisObj, result)) {
|
||||
override.put(thisObj, result);
|
||||
}
|
||||
|
||||
// This is fine - received packets are enqueued in any case
|
||||
NetworkMarker marker = NetworkMarker.getNetworkMarker(event);
|
||||
processor.invokePostEvent(event, marker);
|
||||
}
|
||||
|
||||
} catch (OutOfMemoryError e) {
|
||||
throw e;
|
||||
} catch (ThreadDeath e) {
|
||||
throw e;
|
||||
} catch (Throwable e) {
|
||||
// Minecraft cannot handle this error
|
||||
reporter.reportDetailed(this,
|
||||
Report.newBuilder(REPORT_CANNOT_HANDLE_CLIENT_PACKET).callerParam(args[0]).error(e)
|
||||
);
|
||||
}
|
||||
}
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
private boolean objectEquals(Object a, Object b) {
|
||||
return System.identityHashCode(a) != System.identityHashCode(b);
|
||||
}
|
||||
}
|
||||
-130
@@ -1,130 +0,0 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol.injector.packet;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataOutput;
|
||||
import java.io.DataOutputStream;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Map;
|
||||
|
||||
import net.sf.cglib.proxy.MethodInterceptor;
|
||||
import net.sf.cglib.proxy.MethodProxy;
|
||||
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.error.Report;
|
||||
import com.comphenix.protocol.error.ReportType;
|
||||
import com.comphenix.protocol.events.NetworkMarker;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.injector.NetworkProcessor;
|
||||
import com.google.common.collect.MapMaker;
|
||||
|
||||
public class WritePacketModifier implements MethodInterceptor {
|
||||
public static final ReportType REPORT_CANNOT_WRITE_SERVER_PACKET = new ReportType("Cannot write server packet.");
|
||||
|
||||
private static class ProxyInformation {
|
||||
// Marker that contains custom writers
|
||||
public final Object proxyObject;
|
||||
public final PacketEvent event;
|
||||
public final NetworkMarker marker;
|
||||
|
||||
public ProxyInformation(Object proxyObject, PacketEvent event, NetworkMarker marker) {
|
||||
this.proxyObject = proxyObject;
|
||||
this.event = event;
|
||||
this.marker = marker;
|
||||
}
|
||||
}
|
||||
|
||||
private Map<Object, ProxyInformation> proxyLookup = new MapMaker().weakKeys().makeMap();
|
||||
|
||||
// Report errors
|
||||
private final ErrorReporter reporter;
|
||||
private final NetworkProcessor processor;
|
||||
|
||||
// Whether or not this represents the write method
|
||||
private boolean isWriteMethod;
|
||||
|
||||
public WritePacketModifier(ErrorReporter reporter, boolean isWriteMethod) {
|
||||
this.reporter = reporter;
|
||||
this.processor = new NetworkProcessor(reporter);
|
||||
this.isWriteMethod = isWriteMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
* Associate the given generated instance of a class and the given parameteters.
|
||||
* @param generatedClass - the generated class.
|
||||
* @param proxyObject - the object to call from the generated class.
|
||||
* @param event - the packet event.
|
||||
* @param marker - the network marker.
|
||||
*/
|
||||
public void register(Object generatedClass, Object proxyObject, PacketEvent event, NetworkMarker marker) {
|
||||
proxyLookup.put(generatedClass, new ProxyInformation(proxyObject, event, marker));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object intercept(Object thisObj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
|
||||
ProxyInformation information = proxyLookup.get(thisObj);
|
||||
|
||||
if (information == null) {
|
||||
// This is really bad - someone forgot to register the proxy
|
||||
throw new RuntimeException("Cannot find proxy information for " + thisObj);
|
||||
}
|
||||
|
||||
if (isWriteMethod) {
|
||||
// If every output handler has been removed - ignore everything
|
||||
if (!information.marker.getOutputHandlers().isEmpty()) {
|
||||
try {
|
||||
DataOutput output = (DataOutput) args[0];
|
||||
|
||||
// First - we need the initial buffer
|
||||
ByteArrayOutputStream outputBufferStream = new ByteArrayOutputStream();
|
||||
proxy.invoke(information.proxyObject, new Object[] { new DataOutputStream(outputBufferStream) });
|
||||
|
||||
// Let each handler prepare the actual output
|
||||
byte[] outputBuffer = processor.processOutput(information.event, information.marker,
|
||||
outputBufferStream.toByteArray());
|
||||
|
||||
// Write that output to the network stream
|
||||
output.write(outputBuffer);
|
||||
|
||||
// We're done
|
||||
processor.invokePostEvent(information.event, information.marker);
|
||||
return null;
|
||||
|
||||
} catch (OutOfMemoryError e) {
|
||||
throw e;
|
||||
} catch (ThreadDeath e) {
|
||||
throw e;
|
||||
} catch (Throwable e) {
|
||||
// Minecraft cannot handle this error
|
||||
reporter.reportDetailed(this,
|
||||
Report.newBuilder(REPORT_CANNOT_WRITE_SERVER_PACKET).callerParam(args[0]).error(e)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Invoke this write method first
|
||||
proxy.invoke(information.proxyObject, args);
|
||||
processor.invokePostEvent(information.event, information.marker);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Default to the super method
|
||||
return proxy.invoke(information.proxyObject, args);
|
||||
}
|
||||
}
|
||||
-193
@@ -1,193 +0,0 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol.injector.player;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
import net.sf.cglib.proxy.Callback;
|
||||
import net.sf.cglib.proxy.Enhancer;
|
||||
import net.sf.cglib.proxy.MethodInterceptor;
|
||||
import net.sf.cglib.proxy.MethodProxy;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.ProtocolLibrary;
|
||||
import com.comphenix.protocol.error.Report;
|
||||
import com.comphenix.protocol.error.ReportType;
|
||||
import com.comphenix.protocol.injector.ListenerInvoker;
|
||||
import com.comphenix.protocol.injector.player.NetworkFieldInjector.FakePacket;
|
||||
import com.comphenix.protocol.utility.EnhancerFactory;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
import com.google.common.collect.MapMaker;
|
||||
|
||||
/**
|
||||
* The array list that notifies when packets are sent by the server.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
class InjectedArrayList extends ArrayList<Object> {
|
||||
public static final ReportType REPORT_CANNOT_REVERT_CANCELLED_PACKET = new ReportType("Reverting cancelled packet failed.");
|
||||
|
||||
/**
|
||||
* Silly Eclipse.
|
||||
*/
|
||||
private static final long serialVersionUID = -1173865905404280990L;
|
||||
|
||||
// Fake inverted proxy objects
|
||||
private static ConcurrentMap<Object, Object> delegateLookup = new MapMaker().weakKeys().makeMap();
|
||||
|
||||
private transient PlayerInjector injector;
|
||||
private transient Set<Object> ignoredPackets;
|
||||
|
||||
private transient InvertedIntegerCallback callback;
|
||||
|
||||
public InjectedArrayList(PlayerInjector injector, Set<Object> ignoredPackets) {
|
||||
this.injector = injector;
|
||||
this.ignoredPackets = ignoredPackets;
|
||||
this.callback = new InvertedIntegerCallback();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean add(Object packet) {
|
||||
|
||||
Object result = null;
|
||||
|
||||
// Check for fake packets and ignored packets
|
||||
if (packet instanceof FakePacket) {
|
||||
return true;
|
||||
} else if (ignoredPackets.contains(packet)) {
|
||||
// Don't send it to the filters
|
||||
result = ignoredPackets.remove(packet);
|
||||
} else {
|
||||
result = injector.handlePacketSending(packet);
|
||||
}
|
||||
|
||||
// A NULL packet indicate cancelling
|
||||
try {
|
||||
if (result != null) {
|
||||
super.add(result);
|
||||
} else {
|
||||
// We'll use the FakePacket marker instead of preventing the filters
|
||||
injector.sendServerPacket(createNegativePacket(packet), null, true);
|
||||
}
|
||||
|
||||
// Collection.add contract
|
||||
return true;
|
||||
|
||||
} catch (InvocationTargetException e) {
|
||||
// Prefer to report this to the user, instead of risking sending it to Minecraft
|
||||
ProtocolLibrary.getErrorReporter().reportDetailed(this,
|
||||
Report.newBuilder(REPORT_CANNOT_REVERT_CANCELLED_PACKET).error(e).callerParam(packet)
|
||||
);
|
||||
|
||||
// Failure
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by a hack that reverses the effect of a cancelled packet. Returns a packet
|
||||
* whereby every int method's return value is inverted (a => -a).
|
||||
*
|
||||
* @param source - packet to invert.
|
||||
* @return The inverted packet.
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
Object createNegativePacket(Object source) {
|
||||
ListenerInvoker invoker = injector.getInvoker();
|
||||
|
||||
PacketType type = invoker.getPacketType(source);
|
||||
|
||||
// We want to subtract the byte amount that were added to the running
|
||||
// total of outstanding packets. Otherwise, cancelling too many packets
|
||||
// might cause a "disconnect.overflow" error.
|
||||
//
|
||||
// We do that by constructing a special packet of the same type that returns
|
||||
// a negative integer for all zero-parameter integer methods. This includes the
|
||||
// size() method, which is used by the queue method to count the number of
|
||||
// bytes to add.
|
||||
//
|
||||
// Essentially, we have:
|
||||
//
|
||||
// public class NegativePacket extends [a packet] {
|
||||
// @Override
|
||||
// public int size() {
|
||||
// return -super.size();
|
||||
// }
|
||||
// ect.
|
||||
// }
|
||||
Enhancer ex = EnhancerFactory.getInstance().createEnhancer();
|
||||
ex.setSuperclass(MinecraftReflection.getPacketClass());
|
||||
ex.setInterfaces(new Class[] { FakePacket.class } );
|
||||
ex.setUseCache(true);
|
||||
ex.setCallbackType(InvertedIntegerCallback.class);
|
||||
|
||||
Class<?> proxyClass = ex.createClass();
|
||||
Enhancer.registerCallbacks(proxyClass, new Callback[] { callback });
|
||||
|
||||
try {
|
||||
// Temporarily associate the fake packet class
|
||||
invoker.registerPacketClass(proxyClass, type.getLegacyId());
|
||||
Object proxy = proxyClass.newInstance();
|
||||
|
||||
InjectedArrayList.registerDelegate(proxy, source);
|
||||
return proxy;
|
||||
|
||||
} catch (Exception e) {
|
||||
// Don't pollute the throws tree
|
||||
throw new RuntimeException("Cannot create fake class.", e);
|
||||
} finally {
|
||||
// Remove this association
|
||||
invoker.unregisterPacketClass(proxyClass);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that the inverted integer proxy uses the given object as source.
|
||||
* @param proxy - inverted integer proxy.
|
||||
* @param source - source object.
|
||||
*/
|
||||
private static void registerDelegate(Object proxy, Object source) {
|
||||
delegateLookup.put(proxy, source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inverts the integer result of every integer method.
|
||||
* @author Kristian
|
||||
*/
|
||||
private class InvertedIntegerCallback implements MethodInterceptor {
|
||||
@Override
|
||||
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
|
||||
final Object delegate = delegateLookup.get(obj);
|
||||
|
||||
if (delegate == null) {
|
||||
throw new IllegalStateException("Unable to find delegate source for " + obj);
|
||||
}
|
||||
|
||||
if (method.getReturnType().equals(int.class) && args.length == 0) {
|
||||
Integer result = (Integer) proxy.invoke(delegate, args);
|
||||
return -result;
|
||||
} else {
|
||||
return proxy.invoke(delegate, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-442
@@ -1,442 +0,0 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol.injector.player;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import net.sf.cglib.proxy.Factory;
|
||||
|
||||
import org.bukkit.Server;
|
||||
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.error.Report;
|
||||
import com.comphenix.protocol.error.ReportType;
|
||||
import com.comphenix.protocol.injector.server.AbstractInputStreamLookup;
|
||||
import com.comphenix.protocol.reflect.FieldAccessException;
|
||||
import com.comphenix.protocol.reflect.FieldUtils;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.comphenix.protocol.reflect.ObjectWriter;
|
||||
import com.comphenix.protocol.reflect.VolatileField;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
|
||||
/**
|
||||
* Used to ensure that the 1.3 server is referencing the correct server handler.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public class InjectedServerConnection {
|
||||
// A number of things can go wrong ...
|
||||
public static final ReportType REPORT_CANNOT_FIND_MINECRAFT_SERVER = new ReportType("Cannot extract minecraft server from Bukkit.");
|
||||
public static final ReportType REPORT_CANNOT_INJECT_SERVER_CONNECTION = new ReportType("Cannot inject into server connection. Bad things will happen.");
|
||||
|
||||
public static final ReportType REPORT_CANNOT_FIND_LISTENER_THREAD = new ReportType("Cannot find listener thread in MinecraftServer.");
|
||||
public static final ReportType REPORT_CANNOT_READ_LISTENER_THREAD = new ReportType("Unable to read the listener thread.");
|
||||
|
||||
public static final ReportType REPORT_CANNOT_FIND_SERVER_CONNECTION = new ReportType("Unable to retrieve server connection");
|
||||
public static final ReportType REPORT_UNEXPECTED_THREAD_COUNT = new ReportType("Unexpected number of threads in %s: %s");
|
||||
public static final ReportType REPORT_CANNOT_FIND_NET_HANDLER_THREAD = new ReportType("Unable to retrieve net handler thread.");
|
||||
public static final ReportType REPORT_INSUFFICENT_THREAD_COUNT = new ReportType("Unable to inject %s lists in %s.");
|
||||
|
||||
public static final ReportType REPORT_CANNOT_COPY_OLD_TO_NEW = new ReportType("Cannot copy old %s to new.");
|
||||
|
||||
private static Field listenerThreadField;
|
||||
private static Field minecraftServerField;
|
||||
private static Field listField;
|
||||
private static Field dedicatedThreadField;
|
||||
|
||||
private static Method serverConnectionMethod;
|
||||
|
||||
private List<VolatileField> listFields;
|
||||
private List<ReplacedArrayList<Object>> replacedLists;
|
||||
|
||||
// The current detected server socket
|
||||
public enum ServerSocketType {
|
||||
SERVER_CONNECTION,
|
||||
LISTENER_THREAD,
|
||||
}
|
||||
|
||||
// Used to inject net handlers
|
||||
private NetLoginInjector netLoginInjector;
|
||||
|
||||
// Inject server connections
|
||||
private AbstractInputStreamLookup socketInjector;
|
||||
|
||||
// Detected by the initializer
|
||||
private ServerSocketType socketType;
|
||||
|
||||
private Server server;
|
||||
private ErrorReporter reporter;
|
||||
private boolean hasAttempted;
|
||||
private boolean hasSuccess;
|
||||
|
||||
private Object minecraftServer = null;
|
||||
|
||||
public InjectedServerConnection(ErrorReporter reporter, AbstractInputStreamLookup socketInjector, Server server, NetLoginInjector netLoginInjector) {
|
||||
this.listFields = new ArrayList<VolatileField>();
|
||||
this.replacedLists = new ArrayList<ReplacedArrayList<Object>>();
|
||||
this.reporter = reporter;
|
||||
this.server = server;
|
||||
this.socketInjector = socketInjector;
|
||||
this.netLoginInjector = netLoginInjector;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the current server connection.
|
||||
* @param reporter - error reproter.
|
||||
* @param server - the current server.
|
||||
* @return The current server connection, or NULL if it hasn't been initialized yet.
|
||||
* @throws FieldAccessException Reflection error.
|
||||
*/
|
||||
public static Object getServerConnection(ErrorReporter reporter, Server server) {
|
||||
try {
|
||||
// Now we are probably able to check for Netty
|
||||
InjectedServerConnection inspector = new InjectedServerConnection(reporter, null, server, null);
|
||||
return inspector.getServerConnection();
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new FieldAccessException("Reflection error.", e);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new FieldAccessException("Corrupt data.", e);
|
||||
} catch (InvocationTargetException e) {
|
||||
throw new FieldAccessException("Minecraft error.", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initial reflective detective work. Will be automatically called by most methods in this class.
|
||||
*/
|
||||
public void initialize() {
|
||||
// Only execute this method once
|
||||
if (!hasAttempted)
|
||||
hasAttempted = true;
|
||||
else
|
||||
return;
|
||||
|
||||
if (minecraftServerField == null)
|
||||
minecraftServerField = FuzzyReflection.fromObject(server, true).
|
||||
getFieldByType("MinecraftServer", MinecraftReflection.getMinecraftServerClass());
|
||||
|
||||
try {
|
||||
minecraftServer = FieldUtils.readField(minecraftServerField, server, true);
|
||||
} catch (IllegalAccessException e1) {
|
||||
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_FIND_MINECRAFT_SERVER));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (serverConnectionMethod == null)
|
||||
serverConnectionMethod = FuzzyReflection.fromClass(minecraftServerField.getType()).
|
||||
getMethodByParameters("getServerConnection",
|
||||
MinecraftReflection.getServerConnectionClass(), new Class[] {});
|
||||
// We're using Minecraft 1.3.1
|
||||
socketType = ServerSocketType.SERVER_CONNECTION;
|
||||
|
||||
} catch (IllegalArgumentException e) {
|
||||
// Minecraft 1.2.5 or lower
|
||||
socketType = ServerSocketType.LISTENER_THREAD;
|
||||
|
||||
} catch (Exception e) {
|
||||
// Oh damn - inform the player
|
||||
reporter.reportDetailed(this, Report.newBuilder(REPORT_CANNOT_INJECT_SERVER_CONNECTION).error(e));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the known server socket type.
|
||||
* <p>
|
||||
* This depends on the version of CraftBukkit we are using.
|
||||
* @return The server socket type.
|
||||
*/
|
||||
public ServerSocketType getServerSocketType() {
|
||||
return socketType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inject the connection interceptor into the correct server socket implementation.
|
||||
*/
|
||||
public void injectList() {
|
||||
initialize();
|
||||
|
||||
if (socketType == ServerSocketType.SERVER_CONNECTION) {
|
||||
injectServerConnection();
|
||||
} else if (socketType == ServerSocketType.LISTENER_THREAD) {
|
||||
injectListenerThread();
|
||||
} else {
|
||||
// Damn it
|
||||
throw new IllegalStateException("Unable to detected server connection.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the listener thread field.
|
||||
*/
|
||||
private void initializeListenerField() {
|
||||
if (listenerThreadField == null)
|
||||
listenerThreadField = FuzzyReflection.fromObject(minecraftServer).
|
||||
getFieldByType("networkListenThread", MinecraftReflection.getNetworkListenThreadClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the listener thread object, or NULL the server isn't using this socket implementation.
|
||||
* @return The listener thread, or NULL.
|
||||
* @throws IllegalAccessException Cannot access field.
|
||||
* @throws RuntimeException Unexpected class structure - the field doesn't exist.
|
||||
*/
|
||||
public Object getListenerThread() throws RuntimeException, IllegalAccessException {
|
||||
initialize();
|
||||
|
||||
if (socketType == ServerSocketType.LISTENER_THREAD) {
|
||||
initializeListenerField();
|
||||
return listenerThreadField.get(minecraftServer);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the server connection object, or NULL if the server isn't using it as the socket implementation.
|
||||
* @return The socket connection, or NULL.
|
||||
* @throws IllegalAccessException If the reflective operation failed.
|
||||
* @throws IllegalArgumentException If the reflective operation failed.
|
||||
* @throws InvocationTargetException If the reflective operation failed.
|
||||
*/
|
||||
public Object getServerConnection() throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
|
||||
initialize();
|
||||
|
||||
if (socketType == ServerSocketType.SERVER_CONNECTION)
|
||||
return serverConnectionMethod.invoke(minecraftServer);
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
private void injectListenerThread() {
|
||||
try {
|
||||
initializeListenerField();
|
||||
} catch (RuntimeException e) {
|
||||
reporter.reportDetailed(this,
|
||||
Report.newBuilder(REPORT_CANNOT_FIND_LISTENER_THREAD).callerParam(minecraftServer).error(e)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
Object listenerThread = null;
|
||||
|
||||
// Attempt to get the thread
|
||||
try {
|
||||
listenerThread = getListenerThread();
|
||||
} catch (Exception e) {
|
||||
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_READ_LISTENER_THREAD).error(e));
|
||||
return;
|
||||
}
|
||||
|
||||
// Inject the server socket too
|
||||
injectServerSocket(listenerThread);
|
||||
|
||||
// Just inject every list field we can get
|
||||
injectEveryListField(listenerThread, 1);
|
||||
hasSuccess = true;
|
||||
}
|
||||
|
||||
private void injectServerConnection() {
|
||||
Object serverConnection = null;
|
||||
|
||||
// Careful - we might fail
|
||||
try {
|
||||
serverConnection = getServerConnection();
|
||||
} catch (Exception e) {
|
||||
reporter.reportDetailed(this,
|
||||
Report.newBuilder(REPORT_CANNOT_FIND_SERVER_CONNECTION).callerParam(minecraftServer).error(e)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (listField == null)
|
||||
listField = FuzzyReflection.fromClass(serverConnectionMethod.getReturnType(), true).
|
||||
getFieldByType("netServerHandlerList", List.class);
|
||||
if (dedicatedThreadField == null) {
|
||||
List<Field> matches = FuzzyReflection.fromObject(serverConnection, true).
|
||||
getFieldListByType(Thread.class);
|
||||
|
||||
// Verify the field count
|
||||
if (matches.size() != 1)
|
||||
reporter.reportWarning(this,
|
||||
Report.newBuilder(REPORT_UNEXPECTED_THREAD_COUNT).messageParam(serverConnection.getClass(), matches.size())
|
||||
);
|
||||
else
|
||||
dedicatedThreadField = matches.get(0);
|
||||
}
|
||||
|
||||
// Next, try to get the dedicated thread
|
||||
try {
|
||||
if (dedicatedThreadField != null) {
|
||||
Object dedicatedThread = FieldUtils.readField(dedicatedThreadField, serverConnection, true);
|
||||
|
||||
// Inject server socket and NetServerHandlers.
|
||||
injectServerSocket(dedicatedThread);
|
||||
injectEveryListField(dedicatedThread, 1);
|
||||
}
|
||||
} catch (IllegalAccessException e) {
|
||||
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_FIND_NET_HANDLER_THREAD).error(e));
|
||||
}
|
||||
|
||||
injectIntoList(serverConnection, listField);
|
||||
hasSuccess = true;
|
||||
}
|
||||
|
||||
private void injectServerSocket(Object container) {
|
||||
socketInjector.inject(container);
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatically inject into every List-compatible public or private field of the given object.
|
||||
* @param container - container object with the fields to inject.
|
||||
* @param minimum - the minimum number of fields we expect exists.
|
||||
*/
|
||||
private void injectEveryListField(Object container, int minimum) {
|
||||
// Ok, great. Get every list field
|
||||
List<Field> lists = FuzzyReflection.fromObject(container, true).getFieldListByType(List.class);
|
||||
|
||||
for (Field list : lists) {
|
||||
injectIntoList(container, list);
|
||||
}
|
||||
|
||||
// Warn about unexpected errors
|
||||
if (lists.size() < minimum) {
|
||||
reporter.reportWarning(this, Report.newBuilder(REPORT_INSUFFICENT_THREAD_COUNT).messageParam(minimum, container.getClass()));
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void injectIntoList(Object instance, Field field) {
|
||||
VolatileField listFieldRef = new VolatileField(field, instance, true);
|
||||
List<Object> list = (List<Object>) listFieldRef.getValue();
|
||||
|
||||
// Careful not to inject twice
|
||||
if (list instanceof ReplacedArrayList) {
|
||||
replacedLists.add((ReplacedArrayList<Object>) list);
|
||||
} else {
|
||||
ReplacedArrayList<Object> injectedList = createReplacement(list);
|
||||
|
||||
replacedLists.add(injectedList);
|
||||
listFieldRef.setValue(injectedList);
|
||||
listFields.add(listFieldRef);
|
||||
}
|
||||
}
|
||||
|
||||
// Hack to avoid the "moved to quickly" error
|
||||
private ReplacedArrayList<Object> createReplacement(List<Object> list) {
|
||||
return new ReplacedArrayList<Object>(list) {
|
||||
/**
|
||||
* Shut up Eclipse!
|
||||
*/
|
||||
private static final long serialVersionUID = 2070481080950500367L;
|
||||
|
||||
// Object writer we'll use
|
||||
private final ObjectWriter writer = new ObjectWriter();
|
||||
|
||||
@Override
|
||||
protected void onReplacing(Object inserting, Object replacement) {
|
||||
// Is this a normal Minecraft object?
|
||||
if (!(inserting instanceof Factory)) {
|
||||
// If so, copy the content of the old element to the new
|
||||
try {
|
||||
writer.copyTo(inserting, replacement, inserting.getClass());
|
||||
} catch (OutOfMemoryError e) {
|
||||
throw e;
|
||||
} catch (ThreadDeath e) {
|
||||
throw e;
|
||||
} catch (Throwable e) {
|
||||
reporter.reportDetailed(InjectedServerConnection.this,
|
||||
Report.newBuilder(REPORT_CANNOT_COPY_OLD_TO_NEW).messageParam(inserting).callerParam(inserting, replacement).error(e)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onInserting(Object inserting) {
|
||||
// Ready for some login handler injection?
|
||||
if (MinecraftReflection.isLoginHandler(inserting)) {
|
||||
Object replaced = netLoginInjector.onNetLoginCreated(inserting);
|
||||
|
||||
// Only replace if it has changed
|
||||
if (inserting != replaced)
|
||||
addMapping(inserting, replaced, true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRemoved(Object removing) {
|
||||
// Clean up?
|
||||
if (MinecraftReflection.isLoginHandler(removing)) {
|
||||
netLoginInjector.cleanup(removing);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace the server handler instance kept by the "keep alive" object.
|
||||
* @param oldHandler - old server handler.
|
||||
* @param newHandler - new, proxied server handler.
|
||||
*/
|
||||
public void replaceServerHandler(Object oldHandler, Object newHandler) {
|
||||
if (!hasAttempted) {
|
||||
injectList();
|
||||
}
|
||||
|
||||
if (hasSuccess) {
|
||||
for (ReplacedArrayList<Object> replacedList : replacedLists) {
|
||||
replacedList.addMapping(oldHandler, newHandler);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Revert to the old vanilla server handler, if it has been replaced.
|
||||
* @param oldHandler - old vanilla server handler.
|
||||
*/
|
||||
public void revertServerHandler(Object oldHandler) {
|
||||
if (hasSuccess) {
|
||||
for (ReplacedArrayList<Object> replacedList : replacedLists) {
|
||||
replacedList.removeMapping(oldHandler);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Undoes everything.
|
||||
*/
|
||||
public void cleanupAll() {
|
||||
if (replacedLists.size() > 0) {
|
||||
// Repair the underlying lists
|
||||
for (ReplacedArrayList<Object> replacedList : replacedLists) {
|
||||
replacedList.revertAll();
|
||||
}
|
||||
for (VolatileField field : listFields) {
|
||||
field.revertValue();
|
||||
}
|
||||
|
||||
listFields.clear();
|
||||
replacedLists.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
-162
@@ -1,162 +0,0 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol.injector.player;
|
||||
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.error.Report;
|
||||
import com.comphenix.protocol.error.ReportType;
|
||||
import com.comphenix.protocol.injector.GamePhase;
|
||||
import com.comphenix.protocol.injector.player.PlayerInjectionHandler.ConflictStrategy;
|
||||
import com.comphenix.protocol.injector.server.TemporaryPlayerFactory;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
/**
|
||||
* Injects every NetLoginHandler created by the server.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
class NetLoginInjector {
|
||||
public static final ReportType REPORT_CANNOT_HOOK_LOGIN_HANDLER = new ReportType("Unable to hook %s.");
|
||||
public static final ReportType REPORT_CANNOT_CLEANUP_LOGIN_HANDLER = new ReportType("Cannot cleanup %s.");
|
||||
|
||||
private ConcurrentMap<Object, PlayerInjector> injectedLogins = Maps.newConcurrentMap();
|
||||
|
||||
// Handles every hook
|
||||
private ProxyPlayerInjectionHandler injectionHandler;
|
||||
|
||||
// Create temporary players
|
||||
private TemporaryPlayerFactory playerFactory = new TemporaryPlayerFactory();
|
||||
|
||||
// The current error reporter
|
||||
private ErrorReporter reporter;
|
||||
private Server server;
|
||||
|
||||
public NetLoginInjector(ErrorReporter reporter, Server server, ProxyPlayerInjectionHandler injectionHandler) {
|
||||
this.reporter = reporter;
|
||||
this.server = server;
|
||||
this.injectionHandler = injectionHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when a NetLoginHandler has been created.
|
||||
* @param inserting - the new NetLoginHandler.
|
||||
* @return An injected NetLoginHandler, or the original object.
|
||||
*/
|
||||
public Object onNetLoginCreated(Object inserting) {
|
||||
try {
|
||||
// Make sure we actually need to inject during this phase
|
||||
if (!injectionHandler.isInjectionNecessary(GamePhase.LOGIN))
|
||||
return inserting;
|
||||
|
||||
Player temporary = playerFactory.createTemporaryPlayer(server);
|
||||
// Note that we bail out if there's an existing player injector
|
||||
PlayerInjector injector = injectionHandler.injectPlayer(
|
||||
temporary, inserting, ConflictStrategy.BAIL_OUT, GamePhase.LOGIN);
|
||||
|
||||
if (injector != null) {
|
||||
// Update injector as well
|
||||
TemporaryPlayerFactory.setInjectorInPlayer(temporary, injector);
|
||||
injector.updateOnLogin = true;
|
||||
|
||||
// Save the login
|
||||
injectedLogins.putIfAbsent(inserting, injector);
|
||||
}
|
||||
|
||||
// NetServerInjector can never work (currently), so we don't need to replace the NetLoginHandler
|
||||
return inserting;
|
||||
|
||||
} catch (OutOfMemoryError e) {
|
||||
throw e;
|
||||
} catch (ThreadDeath e) {
|
||||
throw e;
|
||||
} catch (Throwable e) {
|
||||
// Minecraft can't handle this, so we'll deal with it here
|
||||
reporter.reportDetailed(this,
|
||||
Report.newBuilder(REPORT_CANNOT_HOOK_LOGIN_HANDLER).
|
||||
messageParam(MinecraftReflection.getNetLoginHandlerName()).
|
||||
callerParam(inserting, injectionHandler).
|
||||
error(e)
|
||||
);
|
||||
return inserting;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when a NetLoginHandler should be reverted.
|
||||
* @param inserting - the original NetLoginHandler.
|
||||
* @return An injected NetLoginHandler, or the original object.
|
||||
*/
|
||||
public synchronized void cleanup(Object removing) {
|
||||
PlayerInjector injected = injectedLogins.get(removing);
|
||||
|
||||
if (injected != null) {
|
||||
try {
|
||||
PlayerInjector newInjector = null;
|
||||
Player player = injected.getPlayer();
|
||||
|
||||
// Clean up list
|
||||
injectedLogins.remove(removing);
|
||||
|
||||
// No need to clean up twice
|
||||
if (injected.isClean())
|
||||
return;
|
||||
|
||||
// Hack to clean up other references
|
||||
newInjector = injectionHandler.getInjectorByNetworkHandler(injected.getNetworkManager());
|
||||
injectionHandler.uninjectPlayer(player);
|
||||
|
||||
// Update NetworkManager
|
||||
if (newInjector != null) {
|
||||
if (injected instanceof NetworkObjectInjector) {
|
||||
newInjector.setNetworkManager(injected.getNetworkManager(), true);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (OutOfMemoryError e) {
|
||||
throw e;
|
||||
} catch (ThreadDeath e) {
|
||||
throw e;
|
||||
} catch (Throwable e) {
|
||||
// Don't leak this to Minecraft
|
||||
reporter.reportDetailed(this,
|
||||
Report.newBuilder(REPORT_CANNOT_CLEANUP_LOGIN_HANDLER).
|
||||
messageParam(MinecraftReflection.getNetLoginHandlerName()).
|
||||
callerParam(removing).
|
||||
error(e)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all injected hooks.
|
||||
*/
|
||||
public void cleanupAll() {
|
||||
for (PlayerInjector injector : injectedLogins.values()) {
|
||||
injector.cleanupAll();
|
||||
}
|
||||
|
||||
injectedLogins.clear();
|
||||
}
|
||||
}
|
||||
-222
@@ -1,222 +0,0 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol.injector.player;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import com.comphenix.protocol.Packets;
|
||||
import com.comphenix.protocol.concurrency.IntegerSet;
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.events.ListeningWhitelist;
|
||||
import com.comphenix.protocol.events.NetworkMarker;
|
||||
import com.comphenix.protocol.events.PacketListener;
|
||||
import com.comphenix.protocol.injector.GamePhase;
|
||||
import com.comphenix.protocol.injector.ListenerInvoker;
|
||||
import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks;
|
||||
import com.comphenix.protocol.reflect.FieldUtils;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.comphenix.protocol.reflect.StructureModifier;
|
||||
import com.comphenix.protocol.reflect.VolatileField;
|
||||
import com.comphenix.protocol.utility.MinecraftVersion;
|
||||
import com.google.common.collect.Sets;
|
||||
|
||||
/**
|
||||
* Injection hook that overrides the packet queue lists in NetworkHandler.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
class NetworkFieldInjector extends PlayerInjector {
|
||||
|
||||
/**
|
||||
* Marker interface that indicates a packet is fake and should not be processed.
|
||||
* @author Kristian
|
||||
*/
|
||||
public interface FakePacket {
|
||||
// Nothing
|
||||
}
|
||||
|
||||
// After commit 336a4e00668fd2518c41242755ed6b3bdc3b0e6c (Update CraftBukkit to Minecraft 1.4.4.),
|
||||
// CraftBukkit stopped redirecting map chunk and map chunk bulk packets to a separate queue.
|
||||
// Thus, NetworkFieldInjector can safely handle every packet (though not perfectly - some packets
|
||||
// will be slightly processed).
|
||||
private MinecraftVersion safeVersion = new MinecraftVersion("1.4.4");
|
||||
|
||||
// Packets to ignore
|
||||
private Set<Object> ignoredPackets = Sets.newSetFromMap(new ConcurrentHashMap<Object, Boolean>());
|
||||
|
||||
// Overridden fields
|
||||
private List<VolatileField> overridenLists = new ArrayList<VolatileField>();
|
||||
|
||||
// Sync field
|
||||
private static Field syncField;
|
||||
private Object syncObject;
|
||||
|
||||
// Determine if we're listening
|
||||
private IntegerSet sendingFilters;
|
||||
|
||||
public NetworkFieldInjector(ErrorReporter reporter, Player player,
|
||||
ListenerInvoker manager, IntegerSet sendingFilters) throws IllegalAccessException {
|
||||
|
||||
super(reporter, player, manager);
|
||||
this.sendingFilters = sendingFilters;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean hasListener(int packetID) {
|
||||
return sendingFilters.contains(packetID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void initialize(Object injectionSource) throws IllegalAccessException {
|
||||
super.initialize(injectionSource);
|
||||
|
||||
// Get the sync field as well
|
||||
if (hasInitialized) {
|
||||
if (syncField == null)
|
||||
syncField = FuzzyReflection.fromObject(networkManager, true).getFieldByType("java\\.lang\\.Object");
|
||||
syncObject = FieldUtils.readField(syncField, networkManager, true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendServerPacket(Object packet, NetworkMarker marker, boolean filtered) throws InvocationTargetException {
|
||||
if (networkManager != null) {
|
||||
try {
|
||||
if (!filtered) {
|
||||
ignoredPackets.add(packet);
|
||||
}
|
||||
if (marker != null) {
|
||||
queuedMarkers.put(packet, marker);
|
||||
}
|
||||
|
||||
// Note that invocation target exception is a wrapper for a checked exception
|
||||
queueMethod.invoke(networkManager, packet);
|
||||
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw e;
|
||||
} catch (InvocationTargetException e) {
|
||||
throw e;
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new IllegalStateException("Unable to access queue method.", e);
|
||||
}
|
||||
} else {
|
||||
throw new IllegalStateException("Unable to load network mananager. Cannot send packet.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public UnsupportedListener checkListener(MinecraftVersion version, PacketListener listener) {
|
||||
if (version != null && version.compareTo(safeVersion) > 0) {
|
||||
return null;
|
||||
|
||||
} else {
|
||||
@SuppressWarnings("deprecation")
|
||||
int[] unsupported = { Packets.Server.MAP_CHUNK, Packets.Server.MAP_CHUNK_BULK };
|
||||
|
||||
// Unfortunately, we don't support chunk packets
|
||||
if (ListeningWhitelist.containsAny(listener.getSendingWhitelist(), unsupported)) {
|
||||
return new UnsupportedListener("The NETWORK_FIELD_INJECTOR hook doesn't support map chunk listeners.", unsupported);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectManager() {
|
||||
if (networkManager != null) {
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
StructureModifier<List> list = networkModifier.withType(List.class);
|
||||
|
||||
// Subclass both send queues
|
||||
for (Field field : list.getFields()) {
|
||||
VolatileField overwriter = new VolatileField(field, networkManager, true);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Object> minecraftList = (List<Object>) overwriter.getOldValue();
|
||||
|
||||
synchronized(syncObject) {
|
||||
// The list we'll be inserting
|
||||
List<Object> hackedList = new InjectedArrayList(this, ignoredPackets);
|
||||
|
||||
// Add every previously stored packet
|
||||
for (Object packet : minecraftList) {
|
||||
hackedList.add(packet);
|
||||
}
|
||||
|
||||
// Don' keep stale packets around
|
||||
minecraftList.clear();
|
||||
overwriter.setValue(Collections.synchronizedList(hackedList));
|
||||
}
|
||||
|
||||
overridenLists.add(overwriter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected void cleanHook() {
|
||||
// Clean up
|
||||
for (VolatileField overriden : overridenLists) {
|
||||
List<Object> minecraftList = (List<Object>) overriden.getOldValue();
|
||||
List<Object> hacketList = (List<Object>) overriden.getValue();
|
||||
|
||||
if (minecraftList == hacketList) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get a lock before we modify the list
|
||||
synchronized(syncObject) {
|
||||
try {
|
||||
// Copy over current packets
|
||||
for (Object packet : (List<Object>) overriden.getValue()) {
|
||||
minecraftList.add(packet);
|
||||
}
|
||||
} finally {
|
||||
overriden.revertValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
overridenLists.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleDisconnect() {
|
||||
// No need to do anything
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canInject(GamePhase phase) {
|
||||
// All phases should work
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlayerInjectHooks getHookType() {
|
||||
return PlayerInjectHooks.NETWORK_HANDLER_FIELDS;
|
||||
}
|
||||
}
|
||||
-230
@@ -1,230 +0,0 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol.injector.player;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import net.sf.cglib.proxy.Callback;
|
||||
import net.sf.cglib.proxy.CallbackFilter;
|
||||
import net.sf.cglib.proxy.Enhancer;
|
||||
import net.sf.cglib.proxy.LazyLoader;
|
||||
import net.sf.cglib.proxy.MethodInterceptor;
|
||||
import net.sf.cglib.proxy.MethodProxy;
|
||||
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import com.comphenix.protocol.Packets;
|
||||
import com.comphenix.protocol.concurrency.IntegerSet;
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.events.ListeningWhitelist;
|
||||
import com.comphenix.protocol.events.NetworkMarker;
|
||||
import com.comphenix.protocol.events.PacketListener;
|
||||
import com.comphenix.protocol.injector.GamePhase;
|
||||
import com.comphenix.protocol.injector.ListenerInvoker;
|
||||
import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks;
|
||||
import com.comphenix.protocol.injector.server.TemporaryPlayerFactory;
|
||||
import com.comphenix.protocol.utility.EnhancerFactory;
|
||||
import com.comphenix.protocol.utility.MinecraftVersion;
|
||||
|
||||
/**
|
||||
* Injection method that overrides the NetworkHandler itself, and its queue-method.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public class NetworkObjectInjector extends PlayerInjector {
|
||||
// Determine if we're listening
|
||||
private IntegerSet sendingFilters;
|
||||
|
||||
// After commit 336a4e00668fd2518c41242755ed6b3bdc3b0e6c (Update CraftBukkit to Minecraft 1.4.4.),
|
||||
// CraftBukkit stopped redirecting map chunk and map chunk bulk packets to a separate queue.
|
||||
// Thus, NetworkFieldInjector can safely handle every packet (though not perfectly - some packets
|
||||
// will be slightly processed).
|
||||
private MinecraftVersion safeVersion = new MinecraftVersion("1.4.4");
|
||||
|
||||
// Shared callback filter - avoid creating a new class every time
|
||||
private volatile static CallbackFilter callbackFilter;
|
||||
|
||||
// Temporary player factory
|
||||
private static volatile TemporaryPlayerFactory tempPlayerFactory;
|
||||
|
||||
/**
|
||||
* Create a new network object injector.
|
||||
* <p>
|
||||
* Note: This class is intended to be internal. Do not use.
|
||||
* @param reporter - the error reporter.
|
||||
* @param player - the player Bukkit entity.
|
||||
* @param invoker - the packet invoker.
|
||||
* @param sendingFilters - list of permitted packet IDs.
|
||||
* @throws IllegalAccessException If reflection failed.
|
||||
*/
|
||||
public NetworkObjectInjector(ErrorReporter reporter, Player player,
|
||||
ListenerInvoker invoker, IntegerSet sendingFilters) throws IllegalAccessException {
|
||||
|
||||
super(reporter, player, invoker);
|
||||
this.sendingFilters = sendingFilters;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean hasListener(int packetID) {
|
||||
return sendingFilters.contains(packetID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a temporary player for use during login.
|
||||
* @param server - Bukkit server.
|
||||
* @return The temporary player.
|
||||
*/
|
||||
public Player createTemporaryPlayer(Server server) {
|
||||
if (tempPlayerFactory == null)
|
||||
tempPlayerFactory = new TemporaryPlayerFactory();
|
||||
|
||||
// Create and associate the fake player with this network injector
|
||||
return tempPlayerFactory.createTemporaryPlayer(server, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendServerPacket(Object packet, NetworkMarker marker, boolean filtered) throws InvocationTargetException {
|
||||
Object networkDelegate = filtered ? networkManagerRef.getValue() : networkManagerRef.getOldValue();
|
||||
|
||||
if (networkDelegate != null) {
|
||||
try {
|
||||
if (marker != null) {
|
||||
queuedMarkers.put(packet, marker);
|
||||
}
|
||||
|
||||
// Note that invocation target exception is a wrapper for a checked exception
|
||||
queueMethod.invoke(networkDelegate, packet);
|
||||
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw e;
|
||||
} catch (InvocationTargetException e) {
|
||||
throw e;
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new IllegalStateException("Unable to access queue method.", e);
|
||||
}
|
||||
} else {
|
||||
throw new IllegalStateException("Unable to load network mananager. Cannot send packet.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public UnsupportedListener checkListener(MinecraftVersion version, PacketListener listener) {
|
||||
if (version != null && version.compareTo(safeVersion) > 0) {
|
||||
return null;
|
||||
|
||||
} else {
|
||||
@SuppressWarnings("deprecation")
|
||||
int[] unsupported = { Packets.Server.MAP_CHUNK, Packets.Server.MAP_CHUNK_BULK };
|
||||
|
||||
// Unfortunately, we don't support chunk packets
|
||||
if (ListeningWhitelist.containsAny(listener.getSendingWhitelist(), unsupported)) {
|
||||
return new UnsupportedListener("The NETWORK_OBJECT_INJECTOR hook doesn't support map chunk listeners.", unsupported);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectManager() {
|
||||
|
||||
if (networkManager != null) {
|
||||
final Class<?> networkInterface = networkManagerRef.getField().getType();
|
||||
final Object networkDelegate = networkManagerRef.getOldValue();
|
||||
|
||||
if (!networkInterface.isInterface()) {
|
||||
throw new UnsupportedOperationException(
|
||||
"Must use CraftBukkit 1.3.0 or later to inject into into NetworkMananger.");
|
||||
}
|
||||
|
||||
Callback queueFilter = new MethodInterceptor() {
|
||||
@Override
|
||||
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
|
||||
Object packet = args[0];
|
||||
|
||||
if (packet != null) {
|
||||
packet = handlePacketSending(packet);
|
||||
|
||||
// A NULL packet indicate cancelling
|
||||
if (packet != null)
|
||||
args[0] = packet;
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
// Delegate to our underlying class
|
||||
return proxy.invokeSuper(networkDelegate, args);
|
||||
}
|
||||
};
|
||||
Callback dispatch = new LazyLoader() {
|
||||
@Override
|
||||
public Object loadObject() throws Exception {
|
||||
return networkDelegate;
|
||||
}
|
||||
};
|
||||
|
||||
// Share callback filter - that way, we avoid generating a new class every time.
|
||||
if (callbackFilter == null) {
|
||||
callbackFilter = new CallbackFilter() {
|
||||
@Override
|
||||
public int accept(Method method) {
|
||||
if (method.equals(queueMethod))
|
||||
return 0;
|
||||
else
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Create our proxy object
|
||||
Enhancer ex = EnhancerFactory.getInstance().createEnhancer();
|
||||
ex.setSuperclass(networkInterface);
|
||||
ex.setCallbacks(new Callback[] { queueFilter, dispatch });
|
||||
ex.setCallbackFilter(callbackFilter);
|
||||
|
||||
// Inject it, if we can.
|
||||
networkManagerRef.setValue(ex.create());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void cleanHook() {
|
||||
// Clean up
|
||||
if (networkManagerRef != null && networkManagerRef.isCurrentSet()) {
|
||||
networkManagerRef.revertValue();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleDisconnect() {
|
||||
// No need to do anything
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canInject(GamePhase phase) {
|
||||
// Works for all phases
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlayerInjectHooks getHookType() {
|
||||
return PlayerInjectHooks.NETWORK_MANAGER_OBJECT;
|
||||
}
|
||||
}
|
||||
-368
@@ -1,368 +0,0 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol.injector.player;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
|
||||
import net.sf.cglib.proxy.Callback;
|
||||
import net.sf.cglib.proxy.CallbackFilter;
|
||||
import net.sf.cglib.proxy.Enhancer;
|
||||
import net.sf.cglib.proxy.Factory;
|
||||
import net.sf.cglib.proxy.MethodInterceptor;
|
||||
import net.sf.cglib.proxy.MethodProxy;
|
||||
import net.sf.cglib.proxy.NoOp;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import com.comphenix.protocol.concurrency.IntegerSet;
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.error.Report;
|
||||
import com.comphenix.protocol.error.ReportType;
|
||||
import com.comphenix.protocol.events.NetworkMarker;
|
||||
import com.comphenix.protocol.events.PacketListener;
|
||||
import com.comphenix.protocol.injector.GamePhase;
|
||||
import com.comphenix.protocol.injector.ListenerInvoker;
|
||||
import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks;
|
||||
import com.comphenix.protocol.reflect.FieldUtils;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.comphenix.protocol.reflect.ObjectWriter;
|
||||
import com.comphenix.protocol.reflect.VolatileField;
|
||||
import com.comphenix.protocol.reflect.instances.DefaultInstances;
|
||||
import com.comphenix.protocol.reflect.instances.ExistingGenerator;
|
||||
import com.comphenix.protocol.utility.EnhancerFactory;
|
||||
import com.comphenix.protocol.utility.MinecraftMethods;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
import com.comphenix.protocol.utility.MinecraftVersion;
|
||||
|
||||
/**
|
||||
* Represents a player hook into the NetServerHandler class.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
class NetworkServerInjector extends PlayerInjector {
|
||||
// Disconnected field
|
||||
public static final ReportType REPORT_ASSUMING_DISCONNECT_FIELD = new ReportType("Unable to find 'disconnected' field. Assuming %s.");
|
||||
public static final ReportType REPORT_DISCONNECT_FIELD_MISSING = new ReportType("Cannot find disconnected field. Is ProtocolLib up to date?");
|
||||
public static final ReportType REPORT_DISCONNECT_FIELD_FAILURE = new ReportType("Unable to update disconnected field. Player quit event may be sent twice.");
|
||||
|
||||
private volatile static CallbackFilter callbackFilter;
|
||||
private volatile static boolean foundSendPacket;
|
||||
|
||||
private volatile static Field disconnectField;
|
||||
private InjectedServerConnection serverInjection;
|
||||
|
||||
// Determine if we're listening
|
||||
private IntegerSet sendingFilters;
|
||||
|
||||
// Whether or not the player has disconnected
|
||||
private boolean hasDisconnected;
|
||||
|
||||
// Used to copy fields
|
||||
private final ObjectWriter writer = new ObjectWriter();
|
||||
|
||||
public NetworkServerInjector(
|
||||
ErrorReporter reporter, Player player,
|
||||
ListenerInvoker invoker, IntegerSet sendingFilters,
|
||||
InjectedServerConnection serverInjection) throws IllegalAccessException {
|
||||
|
||||
super(reporter, player, invoker);
|
||||
this.sendingFilters = sendingFilters;
|
||||
this.serverInjection = serverInjection;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean hasListener(int packetID) {
|
||||
return sendingFilters.contains(packetID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendServerPacket(Object packet, NetworkMarker marker, boolean filtered) throws InvocationTargetException {
|
||||
Object serverDelegate = filtered ? serverHandlerRef.getValue() : serverHandlerRef.getOldValue();
|
||||
|
||||
if (serverDelegate != null) {
|
||||
try {
|
||||
if (marker != null) {
|
||||
queuedMarkers.put(packet, marker);
|
||||
}
|
||||
|
||||
// Note that invocation target exception is a wrapper for a checked exception
|
||||
MinecraftMethods.getSendPacketMethod().invoke(serverDelegate, packet);
|
||||
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw e;
|
||||
} catch (InvocationTargetException e) {
|
||||
throw e;
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new IllegalStateException("Unable to access send packet method.", e);
|
||||
}
|
||||
} else {
|
||||
throw new IllegalStateException("Unable to load server handler. Cannot send packet.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectManager() {
|
||||
if (serverHandlerRef == null)
|
||||
throw new IllegalStateException("Cannot find server handler.");
|
||||
// Don't inject twice
|
||||
if (serverHandlerRef.getValue() instanceof Factory)
|
||||
return;
|
||||
|
||||
if (!tryInjectManager()) {
|
||||
Class<?> serverHandlerClass = MinecraftReflection.getNetServerHandlerClass();
|
||||
|
||||
// Try to override the proxied object
|
||||
if (proxyServerField != null) {
|
||||
serverHandlerRef = new VolatileField(proxyServerField, serverHandler, true);
|
||||
serverHandler = serverHandlerRef.getValue();
|
||||
|
||||
if (serverHandler == null)
|
||||
throw new RuntimeException("Cannot hook player: Inner proxy object is NULL.");
|
||||
else
|
||||
serverHandlerClass = serverHandler.getClass();
|
||||
|
||||
// Try again
|
||||
if (tryInjectManager()) {
|
||||
// It worked - probably
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
throw new RuntimeException(
|
||||
"Cannot hook player: Unable to find a valid constructor for the "
|
||||
+ serverHandlerClass.getName() + " object.");
|
||||
}
|
||||
}
|
||||
|
||||
private boolean tryInjectManager() {
|
||||
Class<?> serverClass = serverHandler.getClass();
|
||||
|
||||
Enhancer ex = EnhancerFactory.getInstance().createEnhancer();
|
||||
Callback sendPacketCallback = new MethodInterceptor() {
|
||||
@Override
|
||||
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
|
||||
Object packet = args[0];
|
||||
|
||||
if (packet != null) {
|
||||
packet = handlePacketSending(packet);
|
||||
|
||||
// A NULL packet indicate cancelling
|
||||
if (packet != null)
|
||||
args[0] = packet;
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
// Call the method directly
|
||||
return proxy.invokeSuper(obj, args);
|
||||
};
|
||||
};
|
||||
Callback noOpCallback = NoOp.INSTANCE;
|
||||
|
||||
// Share callback filter - that way, we avoid generating a new class for
|
||||
// every logged in player.
|
||||
if (callbackFilter == null) {
|
||||
callbackFilter = new SendMethodFilter();
|
||||
}
|
||||
|
||||
ex.setSuperclass(serverClass);
|
||||
ex.setCallbacks(new Callback[] { sendPacketCallback, noOpCallback });
|
||||
ex.setCallbackFilter(callbackFilter);
|
||||
|
||||
// Find the Minecraft NetServerHandler superclass
|
||||
Class<?> minecraftSuperClass = getFirstMinecraftSuperClass(serverHandler.getClass());
|
||||
ExistingGenerator generator = ExistingGenerator.fromObjectFields(serverHandler, minecraftSuperClass);
|
||||
DefaultInstances serverInstances = null;
|
||||
|
||||
// Maybe the proxy instance can help?
|
||||
Object proxyInstance = getProxyServerHandler();
|
||||
|
||||
// Use the existing server proxy when we create one
|
||||
if (proxyInstance != null && proxyInstance != serverHandler) {
|
||||
serverInstances = DefaultInstances.fromArray(generator,
|
||||
ExistingGenerator.fromObjectArray(new Object[] { proxyInstance }));
|
||||
} else {
|
||||
serverInstances = DefaultInstances.fromArray(generator);
|
||||
}
|
||||
|
||||
serverInstances.setNonNull(true);
|
||||
serverInstances.setMaximumRecursion(1);
|
||||
|
||||
Object proxyObject = serverInstances.forEnhancer(ex).getDefault(serverClass);
|
||||
|
||||
// Inject it now
|
||||
if (proxyObject != null) {
|
||||
// Did we override a sendPacket method?
|
||||
if (!foundSendPacket) {
|
||||
throw new IllegalArgumentException("Unable to find a sendPacket method in " + serverClass);
|
||||
}
|
||||
|
||||
serverInjection.replaceServerHandler(serverHandler, proxyObject);
|
||||
serverHandlerRef.setValue(proxyObject);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private Object getProxyServerHandler() {
|
||||
if (proxyServerField != null && !proxyServerField.equals(serverHandlerRef.getField())) {
|
||||
try {
|
||||
return FieldUtils.readField(proxyServerField, serverHandler, true);
|
||||
} catch (OutOfMemoryError e) {
|
||||
throw e;
|
||||
} catch (ThreadDeath e) {
|
||||
throw e;
|
||||
} catch (Throwable e) {
|
||||
// Oh well
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private Class<?> getFirstMinecraftSuperClass(Class<?> clazz) {
|
||||
if (MinecraftReflection.isMinecraftClass(clazz))
|
||||
return clazz;
|
||||
else if (clazz.equals(Object.class))
|
||||
return clazz;
|
||||
else
|
||||
return getFirstMinecraftSuperClass(clazz.getSuperclass());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void cleanHook() {
|
||||
if (serverHandlerRef != null && serverHandlerRef.isCurrentSet()) {
|
||||
writer.copyTo(serverHandlerRef.getValue(), serverHandlerRef.getOldValue(), serverHandler.getClass());
|
||||
serverHandlerRef.revertValue();
|
||||
|
||||
try {
|
||||
if (getNetHandler() != null) {
|
||||
// Restore packet listener
|
||||
try {
|
||||
FieldUtils.writeField(netHandlerField, networkManager, serverHandlerRef.getOldValue(), true);
|
||||
} catch (IllegalAccessException e) {
|
||||
// Oh well
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
} catch (IllegalAccessException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
// Prevent the PlayerQuitEvent from being sent twice
|
||||
if (hasDisconnected) {
|
||||
setDisconnect(serverHandlerRef.getValue(), true);
|
||||
}
|
||||
}
|
||||
|
||||
serverInjection.revertServerHandler(serverHandler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleDisconnect() {
|
||||
hasDisconnected = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the disconnected field in a NetServerHandler.
|
||||
* @param handler - the NetServerHandler.
|
||||
* @param value - the new value.
|
||||
*/
|
||||
private void setDisconnect(Object handler, boolean value) {
|
||||
// Set it
|
||||
try {
|
||||
// Load the field
|
||||
if (disconnectField == null) {
|
||||
disconnectField = FuzzyReflection.fromObject(handler).getFieldByName("disconnected.*");
|
||||
}
|
||||
FieldUtils.writeField(disconnectField, handler, value);
|
||||
|
||||
} catch (IllegalArgumentException e) {
|
||||
// Assume it's the first ...
|
||||
if (disconnectField == null) {
|
||||
disconnectField = FuzzyReflection.fromObject(handler).getFieldByType("disconnected", boolean.class);
|
||||
reporter.reportWarning(this, Report.newBuilder(REPORT_ASSUMING_DISCONNECT_FIELD).messageParam(disconnectField));
|
||||
|
||||
// Try again
|
||||
if (disconnectField != null) {
|
||||
setDisconnect(handler, value);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// This is really bad
|
||||
reporter.reportDetailed(this, Report.newBuilder(REPORT_DISCONNECT_FIELD_MISSING).error(e));
|
||||
|
||||
} catch (IllegalAccessException e) {
|
||||
reporter.reportWarning(this, Report.newBuilder(REPORT_DISCONNECT_FIELD_FAILURE).error(e));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public UnsupportedListener checkListener(MinecraftVersion version, PacketListener listener) {
|
||||
// We support everything
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canInject(GamePhase phase) {
|
||||
// Doesn't work when logging in
|
||||
return phase == GamePhase.PLAYING;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlayerInjectHooks getHookType() {
|
||||
return PlayerInjectHooks.NETWORK_SERVER_OBJECT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a CallbackFilter that only matches the SendPacket method.
|
||||
* @author Kristian
|
||||
*/
|
||||
private static class SendMethodFilter implements CallbackFilter {
|
||||
private Method sendPacket = MinecraftMethods.getSendPacketMethod();
|
||||
|
||||
@Override
|
||||
public int accept(Method method) {
|
||||
if (isCallableEqual(sendPacket, method)) {
|
||||
NetworkServerInjector.foundSendPacket = true;
|
||||
return 0;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the two methods are equal in terms of call semantics.
|
||||
* <p>
|
||||
* Two methods are equal if they have the same name, parameter types and return type.
|
||||
* @param first - first method.
|
||||
* @param second - second method.
|
||||
* @return TRUE if they are, FALSE otherwise.
|
||||
*/
|
||||
private boolean isCallableEqual(Method first, Method second) {
|
||||
return first.getName().equals(second.getName()) &&
|
||||
first.getReturnType().equals(second.getReturnType()) &&
|
||||
Arrays.equals(first.getParameterTypes(), second.getParameterTypes());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,756 +0,0 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol.injector.player;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketAddress;
|
||||
import java.util.Map;
|
||||
|
||||
import net.sf.cglib.proxy.Factory;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.PacketType.Sender;
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.error.Report;
|
||||
import com.comphenix.protocol.error.ReportType;
|
||||
import com.comphenix.protocol.events.NetworkMarker;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.events.PacketListener;
|
||||
import com.comphenix.protocol.injector.BukkitUnwrapper;
|
||||
import com.comphenix.protocol.injector.GamePhase;
|
||||
import com.comphenix.protocol.injector.ListenerInvoker;
|
||||
import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks;
|
||||
import com.comphenix.protocol.injector.packet.InterceptWritePacket;
|
||||
import com.comphenix.protocol.injector.server.SocketInjector;
|
||||
import com.comphenix.protocol.reflect.FieldUtils;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.comphenix.protocol.reflect.StructureModifier;
|
||||
import com.comphenix.protocol.reflect.VolatileField;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
import com.comphenix.protocol.utility.MinecraftVersion;
|
||||
import com.google.common.collect.MapMaker;
|
||||
|
||||
public abstract class PlayerInjector implements SocketInjector {
|
||||
// Disconnect method related reports
|
||||
public static final ReportType REPORT_ASSUME_DISCONNECT_METHOD = new ReportType("Cannot find disconnect method by name. Assuming %s.");
|
||||
public static final ReportType REPORT_INVALID_ARGUMENT_DISCONNECT = new ReportType("Invalid argument passed to disconnect method: %s");
|
||||
public static final ReportType REPORT_CANNOT_ACCESS_DISCONNECT = new ReportType("Unable to access disconnect method.");
|
||||
|
||||
public static final ReportType REPORT_CANNOT_CLOSE_SOCKET = new ReportType("Unable to close socket.");
|
||||
public static final ReportType REPORT_ACCESS_DENIED_CLOSE_SOCKET = new ReportType("Insufficient permissions. Cannot close socket.");
|
||||
|
||||
public static final ReportType REPORT_DETECTED_CUSTOM_SERVER_HANDLER =
|
||||
new ReportType("Detected server handler proxy type by another plugin. Conflict may occur!");
|
||||
public static final ReportType REPORT_CANNOT_PROXY_SERVER_HANDLER = new ReportType("Unable to load server handler from proxy type.");
|
||||
|
||||
public static final ReportType REPORT_CANNOT_UPDATE_PLAYER = new ReportType("Cannot update player in PlayerEvent.");
|
||||
public static final ReportType REPORT_CANNOT_HANDLE_PACKET = new ReportType("Cannot handle server packet.");
|
||||
|
||||
public static final ReportType REPORT_INVALID_NETWORK_MANAGER = new ReportType("NetworkManager doesn't appear to be valid.");
|
||||
|
||||
// Net login handler stuff
|
||||
private static Field netLoginNetworkField;
|
||||
|
||||
// Different disconnect methods
|
||||
private static Method loginDisconnect;
|
||||
private static Method serverDisconnect;
|
||||
|
||||
// Cache previously retrieved fields
|
||||
protected static Field serverHandlerField;
|
||||
protected static Field proxyServerField;
|
||||
|
||||
protected static Field networkManagerField;
|
||||
protected static Field netHandlerField;
|
||||
protected static Field socketField;
|
||||
protected static Field socketAddressField;
|
||||
|
||||
private static Field inputField;
|
||||
private static Field entityPlayerField;
|
||||
|
||||
// Whether or not we're using a proxy type
|
||||
private static boolean hasProxyType;
|
||||
|
||||
// To add our injected array lists
|
||||
protected static StructureModifier<Object> networkModifier;
|
||||
|
||||
// And methods
|
||||
protected static Method queueMethod;
|
||||
protected static Method processMethod;
|
||||
|
||||
protected volatile Player player;
|
||||
protected boolean hasInitialized;
|
||||
|
||||
// Reference to the player's network manager
|
||||
protected VolatileField networkManagerRef;
|
||||
protected VolatileField serverHandlerRef;
|
||||
protected Object networkManager;
|
||||
|
||||
// Current net handler
|
||||
protected Object loginHandler;
|
||||
protected Object serverHandler;
|
||||
protected Object netHandler;
|
||||
|
||||
// Current socket and address
|
||||
protected Socket socket;
|
||||
protected SocketAddress socketAddress;
|
||||
|
||||
// The packet manager and filters
|
||||
protected ListenerInvoker invoker;
|
||||
|
||||
// Previous data input
|
||||
protected DataInputStream cachedInput;
|
||||
|
||||
// Handle errors
|
||||
protected ErrorReporter reporter;
|
||||
|
||||
// Previous markers
|
||||
protected Map<Object, NetworkMarker> queuedMarkers = new MapMaker().weakKeys().makeMap();
|
||||
protected InterceptWritePacket writePacketInterceptor;
|
||||
|
||||
// Whether or not the injector has been cleaned
|
||||
private boolean clean;
|
||||
|
||||
// Whether or not to update the current player on the first Packet1Login
|
||||
boolean updateOnLogin;
|
||||
volatile Player updatedPlayer;
|
||||
|
||||
public PlayerInjector(ErrorReporter reporter, Player player, ListenerInvoker invoker) throws IllegalAccessException {
|
||||
this.reporter = reporter;
|
||||
this.player = player;
|
||||
this.invoker = invoker;
|
||||
this.writePacketInterceptor = invoker.getInterceptWritePacket();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the notch (NMS) entity player object.
|
||||
* @param player - the player to retrieve.
|
||||
* @return Notch player object.
|
||||
*/
|
||||
protected Object getEntityPlayer(Player player) {
|
||||
BukkitUnwrapper unwrapper = new BukkitUnwrapper();
|
||||
return unwrapper.unwrapItem(player);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize all fields for this player injector, if it hasn't already.
|
||||
* @throws IllegalAccessException An error has occured.
|
||||
*/
|
||||
public void initialize(Object injectionSource) throws IllegalAccessException {
|
||||
if (injectionSource == null)
|
||||
throw new IllegalArgumentException("injectionSource cannot be NULL");
|
||||
|
||||
//Dispatch to the correct injection method
|
||||
if (injectionSource instanceof Player)
|
||||
initializePlayer((Player) injectionSource);
|
||||
else if (MinecraftReflection.isLoginHandler(injectionSource))
|
||||
initializeLogin(injectionSource);
|
||||
else
|
||||
throw new IllegalArgumentException("Cannot initialize a player hook using a " + injectionSource.getClass().getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the player injector using an actual player instance.
|
||||
* @param player - the player to hook.
|
||||
*/
|
||||
public void initializePlayer(Player player) {
|
||||
Object notchEntity = getEntityPlayer(player);
|
||||
|
||||
// Save the player too
|
||||
this.player = player;
|
||||
|
||||
if (!hasInitialized) {
|
||||
// Do this first, in case we encounter an exception
|
||||
hasInitialized = true;
|
||||
|
||||
// Retrieve the server handler
|
||||
if (serverHandlerField == null) {
|
||||
serverHandlerField = FuzzyReflection.fromObject(notchEntity).getFieldByType(
|
||||
"NetServerHandler", MinecraftReflection.getNetServerHandlerClass());
|
||||
proxyServerField = getProxyField(notchEntity, serverHandlerField);
|
||||
}
|
||||
|
||||
// Yo dawg
|
||||
serverHandlerRef = new VolatileField(serverHandlerField, notchEntity);
|
||||
serverHandler = serverHandlerRef.getValue();
|
||||
|
||||
// Next, get the network manager
|
||||
if (networkManagerField == null)
|
||||
networkManagerField = FuzzyReflection.fromObject(serverHandler).getFieldByType(
|
||||
"networkManager", MinecraftReflection.getNetworkManagerClass());
|
||||
initializeNetworkManager(networkManagerField, serverHandler);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the player injector from a NetLoginHandler.
|
||||
* @param netLoginHandler - the net login handler to inject.
|
||||
*/
|
||||
public void initializeLogin(Object netLoginHandler) {
|
||||
if (!hasInitialized) {
|
||||
// Just in case
|
||||
if (!MinecraftReflection.isLoginHandler(netLoginHandler))
|
||||
throw new IllegalArgumentException("netLoginHandler (" + netLoginHandler + ") is not a " +
|
||||
MinecraftReflection.getNetLoginHandlerName());
|
||||
|
||||
hasInitialized = true;
|
||||
loginHandler = netLoginHandler;
|
||||
|
||||
if (netLoginNetworkField == null)
|
||||
netLoginNetworkField = FuzzyReflection.fromObject(netLoginHandler).
|
||||
getFieldByType("networkManager", MinecraftReflection.getNetworkManagerClass());
|
||||
initializeNetworkManager(netLoginNetworkField, netLoginHandler);
|
||||
}
|
||||
}
|
||||
|
||||
private void initializeNetworkManager(Field reference, Object container) {
|
||||
networkManagerRef = new VolatileField(reference, container);
|
||||
networkManager = networkManagerRef.getValue();
|
||||
|
||||
// No, don't do it
|
||||
if (networkManager instanceof Factory) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create the network manager modifier from the actual object type
|
||||
if (networkManager != null && networkModifier == null)
|
||||
networkModifier = new StructureModifier<Object>(networkManager.getClass(), null, false);
|
||||
|
||||
// And the queue method
|
||||
if (queueMethod == null)
|
||||
queueMethod = FuzzyReflection.fromClass(reference.getType()).
|
||||
getMethodByParameters("queue", MinecraftReflection.getPacketClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve whether or not the server handler is a proxy object.
|
||||
* @return TRUE if it is, FALSE otherwise.
|
||||
*/
|
||||
protected boolean hasProxyServerHandler() {
|
||||
return hasProxyType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the current network manager.
|
||||
* @return Current network manager.
|
||||
*/
|
||||
public Object getNetworkManager() {
|
||||
return networkManagerRef.getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the current server handler (PlayerConnection).
|
||||
* @return Current server handler.
|
||||
*/
|
||||
public Object getServerHandler() {
|
||||
return serverHandlerRef.getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current network manager.
|
||||
* @param value - new network manager.
|
||||
* @param force - whether or not to save this value.
|
||||
*/
|
||||
public void setNetworkManager(Object value, boolean force) {
|
||||
networkManagerRef.setValue(value);
|
||||
|
||||
if (force)
|
||||
networkManagerRef.saveValue();
|
||||
initializeNetworkManager(networkManagerField, serverHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the associated socket of this player.
|
||||
* @return The associated socket.
|
||||
* @throws IllegalAccessException If we're unable to read the socket field.
|
||||
*/
|
||||
@Override
|
||||
public Socket getSocket() throws IllegalAccessException {
|
||||
try {
|
||||
if (socketField == null)
|
||||
socketField = FuzzyReflection.fromObject(networkManager, true).
|
||||
getFieldListByType(Socket.class).get(0);
|
||||
if (socket == null)
|
||||
socket = (Socket) FieldUtils.readField(socketField, networkManager, true);
|
||||
return socket;
|
||||
|
||||
} catch (IndexOutOfBoundsException e) {
|
||||
throw new IllegalAccessException("Unable to read the socket field.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the associated remote address of a player.
|
||||
* @return The associated remote address.
|
||||
* @throws IllegalAccessException If we're unable to read the socket address field.
|
||||
*/
|
||||
@Override
|
||||
public SocketAddress getAddress() throws IllegalAccessException {
|
||||
try {
|
||||
if (socketAddressField == null)
|
||||
socketAddressField = FuzzyReflection.fromObject(networkManager, true).
|
||||
getFieldListByType(SocketAddress.class).get(0);
|
||||
if (socketAddress == null)
|
||||
socketAddress = (SocketAddress) FieldUtils.readField(socketAddressField, networkManager, true);
|
||||
return socketAddress;
|
||||
|
||||
} catch (IndexOutOfBoundsException e) {
|
||||
// Inform about the state of the network manager too
|
||||
reporter.reportWarning(
|
||||
this, Report.newBuilder(REPORT_INVALID_NETWORK_MANAGER).callerParam(networkManager).build());
|
||||
throw new IllegalAccessException("Unable to read the socket address field.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to disconnect the current client.
|
||||
* @param message - the message to display.
|
||||
* @throws InvocationTargetException If disconnection failed.
|
||||
*/
|
||||
@Override
|
||||
public void disconnect(String message) throws InvocationTargetException {
|
||||
// Get a non-null handler
|
||||
boolean usingNetServer = serverHandler != null;
|
||||
|
||||
Object handler = usingNetServer ? serverHandler : loginHandler;
|
||||
Method disconnect = usingNetServer ? serverDisconnect : loginDisconnect;
|
||||
|
||||
// Execute disconnect on it
|
||||
if (handler != null) {
|
||||
if (disconnect == null) {
|
||||
try {
|
||||
disconnect = FuzzyReflection.fromObject(handler).getMethodByName("disconnect.*");
|
||||
} catch (IllegalArgumentException e) {
|
||||
// Just assume it's the first String method
|
||||
disconnect = FuzzyReflection.fromObject(handler).getMethodByParameters("disconnect", String.class);
|
||||
reporter.reportWarning(this, Report.newBuilder(REPORT_ASSUME_DISCONNECT_METHOD).messageParam(disconnect));
|
||||
}
|
||||
|
||||
// Save the method for later
|
||||
if (usingNetServer)
|
||||
serverDisconnect = disconnect;
|
||||
else
|
||||
loginDisconnect = disconnect;
|
||||
}
|
||||
|
||||
try {
|
||||
disconnect.invoke(handler, message);
|
||||
return;
|
||||
} catch (IllegalArgumentException e) {
|
||||
reporter.reportDetailed(this, Report.newBuilder(REPORT_INVALID_ARGUMENT_DISCONNECT).error(e).messageParam(message).callerParam(handler));
|
||||
} catch (IllegalAccessException e) {
|
||||
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_ACCESS_DISCONNECT).error(e));
|
||||
}
|
||||
}
|
||||
|
||||
// Fuck it
|
||||
try {
|
||||
Socket socket = getSocket();
|
||||
|
||||
try {
|
||||
socket.close();
|
||||
} catch (IOException e) {
|
||||
reporter.reportDetailed(this, Report.newBuilder(REPORT_CANNOT_CLOSE_SOCKET).error(e).callerParam(socket));
|
||||
}
|
||||
|
||||
} catch (IllegalAccessException e) {
|
||||
reporter.reportWarning(this, Report.newBuilder(REPORT_ACCESS_DENIED_CLOSE_SOCKET).error(e));
|
||||
}
|
||||
}
|
||||
|
||||
private Field getProxyField(Object notchEntity, Field serverField) {
|
||||
try {
|
||||
Object currentHandler = FieldUtils.readField(serverHandlerField, notchEntity, true);
|
||||
|
||||
// This is bad
|
||||
if (currentHandler == null)
|
||||
throw new ServerHandlerNull();
|
||||
|
||||
// See if this isn't a standard net handler class
|
||||
if (!isStandardMinecraftNetHandler(currentHandler)) {
|
||||
// This is our proxy object
|
||||
if (currentHandler instanceof Factory)
|
||||
return null;
|
||||
|
||||
hasProxyType = true;
|
||||
reporter.reportWarning(this, Report.newBuilder(REPORT_DETECTED_CUSTOM_SERVER_HANDLER).callerParam(serverField));
|
||||
|
||||
// No? Is it a Proxy type?
|
||||
try {
|
||||
FuzzyReflection reflection = FuzzyReflection.fromObject(currentHandler, true);
|
||||
|
||||
// It might be
|
||||
return reflection.getFieldByType("NetServerHandler", MinecraftReflection.getNetServerHandlerClass());
|
||||
|
||||
} catch (RuntimeException e) {
|
||||
// Damn
|
||||
}
|
||||
}
|
||||
|
||||
} catch (IllegalAccessException e) {
|
||||
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_PROXY_SERVER_HANDLER).error(e).callerParam(notchEntity, serverField));
|
||||
}
|
||||
|
||||
// Nope, just go with it
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a given object is a standard Minecraft net handler.
|
||||
* @param obj the object to test.
|
||||
* @return TRUE if it is, FALSE otherwise.
|
||||
*/
|
||||
private boolean isStandardMinecraftNetHandler(Object obj) {
|
||||
if (obj == null)
|
||||
return false;
|
||||
Class<?> clazz = obj.getClass();
|
||||
|
||||
return MinecraftReflection.getNetLoginHandlerClass().equals(clazz) ||
|
||||
MinecraftReflection.getNetServerHandlerClass().equals(clazz);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the current net handler for this player.
|
||||
* @return Current net handler.
|
||||
* @throws IllegalAccessException Unable to find or retrieve net handler.
|
||||
*/
|
||||
protected Object getNetHandler() throws IllegalAccessException {
|
||||
return getNetHandler(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the current net handler for this player.
|
||||
* @return Current net handler.
|
||||
* @throws IllegalAccessException Unable to find or retrieve net handler.
|
||||
*/
|
||||
protected Object getNetHandler(boolean refresh) throws IllegalAccessException {
|
||||
// What a mess
|
||||
try {
|
||||
if (netHandlerField == null)
|
||||
netHandlerField = FuzzyReflection.fromClass(networkManager.getClass(), true).
|
||||
getFieldByType("NetHandler", MinecraftReflection.getNetHandlerClass());
|
||||
} catch (RuntimeException e1) {
|
||||
// Swallow it
|
||||
}
|
||||
|
||||
// Second attempt
|
||||
if (netHandlerField == null) {
|
||||
try {
|
||||
// Well, that sucks. Try just Minecraft objects then.
|
||||
netHandlerField = FuzzyReflection.fromClass(networkManager.getClass(), true).
|
||||
getFieldByType(MinecraftReflection.getMinecraftObjectRegex());
|
||||
|
||||
} catch (RuntimeException e2) {
|
||||
throw new IllegalAccessException("Cannot locate net handler. " + e2.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// Get the handler
|
||||
if (netHandler == null || refresh)
|
||||
netHandler = FieldUtils.readField(netHandlerField, networkManager, true);
|
||||
return netHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the stored entity player from a given NetHandler.
|
||||
* @param netHandler - the nethandler to retrieve it from.
|
||||
* @return The stored entity player.
|
||||
* @throws IllegalAccessException If the reflection failed.
|
||||
*/
|
||||
private Object getEntityPlayer(Object netHandler) throws IllegalAccessException {
|
||||
if (entityPlayerField == null)
|
||||
entityPlayerField = FuzzyReflection.fromObject(netHandler).getFieldByType(
|
||||
"EntityPlayer", MinecraftReflection.getEntityPlayerClass());
|
||||
return FieldUtils.readField(entityPlayerField, netHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the given packet as if it was transmitted by the current player.
|
||||
* @param packet - packet to process.
|
||||
* @throws IllegalAccessException If the reflection machinery failed.
|
||||
* @throws InvocationTargetException If the underlying method caused an error.
|
||||
*/
|
||||
public void processPacket(Object packet) throws IllegalAccessException, InvocationTargetException {
|
||||
|
||||
Object netHandler = getNetHandler();
|
||||
|
||||
// Get the process method
|
||||
if (processMethod == null) {
|
||||
try {
|
||||
processMethod = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass()).
|
||||
getMethodByParameters("processPacket", netHandlerField.getType());
|
||||
} catch (RuntimeException e) {
|
||||
throw new IllegalArgumentException("Cannot locate process packet method: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// We're ready
|
||||
try {
|
||||
processMethod.invoke(packet, netHandler);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new IllegalArgumentException("Method " + processMethod.getName() + " is not compatible.");
|
||||
} catch (InvocationTargetException e) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a packet to the client.
|
||||
* @param packet - server packet to send.
|
||||
* @param marker - the network marker.
|
||||
* @param filtered - whether or not the packet will be filtered by our listeners.
|
||||
* @throws InvocationTargetException If an error occured when sending the packet.
|
||||
*/
|
||||
@Override
|
||||
public abstract void sendServerPacket(Object packet, NetworkMarker marker, boolean filtered) throws InvocationTargetException;
|
||||
|
||||
/**
|
||||
* Inject a hook to catch packets sent to the current player.
|
||||
*/
|
||||
public abstract void injectManager();
|
||||
|
||||
/**
|
||||
* Remove all hooks and modifications.
|
||||
*/
|
||||
public final void cleanupAll() {
|
||||
if (!clean) {
|
||||
cleanHook();
|
||||
writePacketInterceptor.cleanup();
|
||||
}
|
||||
clean = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up after the player has disconnected.
|
||||
*/
|
||||
public abstract void handleDisconnect();
|
||||
|
||||
/**
|
||||
* Override to add custom cleanup behavior.
|
||||
*/
|
||||
protected abstract void cleanHook();
|
||||
|
||||
/**
|
||||
* Determine whether or not this hook has already been cleaned.
|
||||
* @return TRUE if it has, FALSE otherwise.
|
||||
*/
|
||||
public boolean isClean() {
|
||||
return clean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if this inject method can even be attempted.
|
||||
* @return TRUE if can be attempted, though possibly with failure, FALSE otherwise.
|
||||
*/
|
||||
public abstract boolean canInject(GamePhase state);
|
||||
|
||||
/**
|
||||
* Retrieve the hook type this class represents.
|
||||
* @return Hook type this class represents.
|
||||
*/
|
||||
public abstract PlayerInjectHooks getHookType();
|
||||
|
||||
/**
|
||||
* Invoked before a new listener is registered.
|
||||
* <p>
|
||||
* The player injector should only return a non-null value if some or all of the packet IDs are unsupported.
|
||||
* @param version - the current Minecraft version, or NULL if unknown.
|
||||
* @param listener - the listener that is about to be registered.
|
||||
* @return A error message with the unsupported packet IDs, or NULL if this listener is valid.
|
||||
*/
|
||||
public abstract UnsupportedListener checkListener(MinecraftVersion version, PacketListener listener);
|
||||
|
||||
/**
|
||||
* Allows a packet to be sent by the listeners.
|
||||
* @param packet - packet to sent.
|
||||
* @return The given packet, or the packet replaced by the listeners.
|
||||
*/
|
||||
@SuppressWarnings({ "deprecation", "null" })
|
||||
public Object handlePacketSending(Object packet) {
|
||||
try {
|
||||
// Get the packet ID too
|
||||
Integer id = invoker.getPacketID(packet);
|
||||
Player currentPlayer = player;
|
||||
|
||||
// Hack #1
|
||||
if (updateOnLogin) {
|
||||
if (updatedPlayer == null) {
|
||||
try {
|
||||
final Object handler = getNetHandler(true);
|
||||
|
||||
// Is this a net server class?
|
||||
if (MinecraftReflection.getNetServerHandlerClass().isAssignableFrom(handler.getClass())) {
|
||||
setUpdatedPlayer(
|
||||
(Player) MinecraftReflection.getBukkitEntity(getEntityPlayer(handler))
|
||||
);
|
||||
}
|
||||
} catch (IllegalAccessException e) {
|
||||
reporter.reportDetailed(this, Report.newBuilder(REPORT_CANNOT_UPDATE_PLAYER).error(e).callerParam(packet));
|
||||
}
|
||||
}
|
||||
|
||||
// This will only occur in the NetLoginHandler injection
|
||||
if (updatedPlayer != null) {
|
||||
currentPlayer = updatedPlayer;
|
||||
updateOnLogin = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure we're listening
|
||||
if (id != null && hasListener(id)) {
|
||||
NetworkMarker marker = queuedMarkers.remove(packet);
|
||||
|
||||
// A packet has been sent guys!
|
||||
PacketType type = PacketType.findLegacy(id, Sender.SERVER);
|
||||
PacketContainer container = new PacketContainer(type, packet);
|
||||
PacketEvent event = PacketEvent.fromServer(invoker, container, marker, currentPlayer);
|
||||
invoker.invokePacketSending(event);
|
||||
|
||||
// Cancelling is pretty simple. Just ignore the packet.
|
||||
if (event.isCancelled())
|
||||
return null;
|
||||
|
||||
// Right, remember to replace the packet again
|
||||
Object result = event.getPacket().getHandle();
|
||||
marker = NetworkMarker.getNetworkMarker(event);
|
||||
|
||||
// See if we need to proxy the write method
|
||||
if (result != null && (NetworkMarker.hasOutputHandlers(marker) || NetworkMarker.hasPostListeners(marker))) {
|
||||
result = writePacketInterceptor.constructProxy(result, event, marker);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} catch (OutOfMemoryError e) {
|
||||
throw e;
|
||||
} catch (ThreadDeath e) {
|
||||
throw e;
|
||||
} catch (Throwable e) {
|
||||
reporter.reportDetailed(this, Report.newBuilder(REPORT_CANNOT_HANDLE_PACKET).error(e).callerParam(packet));
|
||||
}
|
||||
|
||||
return packet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the given injector is listening for this packet ID.
|
||||
* @param packetID - packet ID to check.
|
||||
* @return TRUE if it is, FALSE oterhwise.
|
||||
*/
|
||||
protected abstract boolean hasListener(int packetID);
|
||||
|
||||
/**
|
||||
* Retrieve the current player's input stream.
|
||||
* @param cache - whether or not to cache the result of this method.
|
||||
* @return The player's input stream.
|
||||
*/
|
||||
public DataInputStream getInputStream(boolean cache) {
|
||||
// And the data input stream that we'll use to identify a player
|
||||
if (networkManager == null)
|
||||
throw new IllegalStateException("Network manager is NULL.");
|
||||
if (inputField == null)
|
||||
inputField = FuzzyReflection.fromObject(networkManager, true).
|
||||
getFieldByType("java\\.io\\.DataInputStream");
|
||||
|
||||
// Get the associated input stream
|
||||
try {
|
||||
if (cache && cachedInput != null)
|
||||
return cachedInput;
|
||||
|
||||
// Save to cache
|
||||
cachedInput = (DataInputStream) FieldUtils.readField(inputField, networkManager, true);
|
||||
return cachedInput;
|
||||
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException("Unable to read input stream.", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the hooked player.
|
||||
*/
|
||||
@Override
|
||||
public Player getPlayer() {
|
||||
return player;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the hooked player.
|
||||
* <p>
|
||||
* Should only be called during the creation of the injector.
|
||||
* @param player - the new hooked player.
|
||||
*/
|
||||
public void setPlayer(Player player) {
|
||||
this.player = player;
|
||||
}
|
||||
|
||||
/**
|
||||
* Object that can invoke the packet events.
|
||||
* @return Packet event invoker.
|
||||
*/
|
||||
public ListenerInvoker getInvoker() {
|
||||
return invoker;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the hooked player object OR the more up-to-date player instance.
|
||||
* @return The hooked player, or a more up-to-date instance.
|
||||
*/
|
||||
@Override
|
||||
public Player getUpdatedPlayer() {
|
||||
if (updatedPlayer != null)
|
||||
return updatedPlayer;
|
||||
else
|
||||
return player;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void transferState(SocketInjector delegate) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUpdatedPlayer(Player updatedPlayer) {
|
||||
this.updatedPlayer = updatedPlayer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates that a player's NetServerHandler or PlayerConnection was NULL.
|
||||
* <p>
|
||||
* This is usually because the player has just logged out, or due to it being a "fake" player in MCPC++.
|
||||
* @author Kristian
|
||||
*/
|
||||
public static class ServerHandlerNull extends IllegalAccessError {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public ServerHandlerNull() {
|
||||
super("Unable to fetch server handler: was NUll.");
|
||||
}
|
||||
|
||||
public ServerHandlerNull(String s) {
|
||||
super(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
-143
@@ -1,143 +0,0 @@
|
||||
package com.comphenix.protocol.injector.player;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Server;
|
||||
|
||||
import com.comphenix.protocol.ProtocolLibrary;
|
||||
import com.comphenix.protocol.ProtocolManager;
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.events.PacketListener;
|
||||
import com.comphenix.protocol.injector.GamePhase;
|
||||
import com.comphenix.protocol.injector.ListenerInvoker;
|
||||
import com.comphenix.protocol.injector.PacketFilterManager;
|
||||
import com.comphenix.protocol.utility.MinecraftVersion;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.base.Predicate;
|
||||
|
||||
/**
|
||||
* Constructor for different player injectors.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public class PlayerInjectorBuilder {
|
||||
public static PlayerInjectorBuilder newBuilder() {
|
||||
return new PlayerInjectorBuilder();
|
||||
}
|
||||
|
||||
protected PlayerInjectorBuilder() {
|
||||
// Use the static method.
|
||||
}
|
||||
|
||||
protected ErrorReporter reporter;
|
||||
protected Predicate<GamePhase> injectionFilter;
|
||||
protected ListenerInvoker invoker;
|
||||
protected Set<PacketListener> packetListeners;
|
||||
protected Server server;
|
||||
protected MinecraftVersion version;
|
||||
|
||||
/**
|
||||
* The error reporter used by the created injector.
|
||||
* @param reporter - new error reporter.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public PlayerInjectorBuilder reporter(@Nonnull ErrorReporter reporter) {
|
||||
Preconditions.checkNotNull(reporter, "reporter cannot be NULL");
|
||||
this.reporter = reporter;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The injection filter that is used to determine if it is necessary to perform
|
||||
* injection during a certain phase.
|
||||
* @param injectionFilter - filter predicate.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
@Nonnull
|
||||
public PlayerInjectorBuilder injectionFilter(@Nonnull Predicate<GamePhase> injectionFilter) {
|
||||
Preconditions.checkNotNull(injectionFilter, "injectionFilter cannot be NULL");
|
||||
this.injectionFilter = injectionFilter;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The packet stream invoker.
|
||||
* @param invoker - the invoker.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public PlayerInjectorBuilder invoker(@Nonnull ListenerInvoker invoker) {
|
||||
Preconditions.checkNotNull(invoker, "invoker cannot be NULL");
|
||||
this.invoker = invoker;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the set of packet listeners.
|
||||
* @param packetListeners - packet listeners.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
@Nonnull
|
||||
public PlayerInjectorBuilder packetListeners(@Nonnull Set<PacketListener> packetListeners) {
|
||||
Preconditions.checkNotNull(packetListeners, "packetListeners cannot be NULL");
|
||||
this.packetListeners = packetListeners;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the Bukkit server used for scheduling.
|
||||
* @param server - the Bukkit server.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public PlayerInjectorBuilder server(@Nonnull Server server) {
|
||||
Preconditions.checkNotNull(server, "server cannot be NULL");
|
||||
this.server = server;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current Minecraft version.
|
||||
* @param version - the current Minecraft version, or NULL if unknown.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public PlayerInjectorBuilder version(MinecraftVersion version) {
|
||||
this.version = version;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called before an object is created with this builder.
|
||||
*/
|
||||
private void initializeDefaults() {
|
||||
ProtocolManager manager = ProtocolLibrary.getProtocolManager();
|
||||
|
||||
// Initialize with default values if we can
|
||||
if (reporter == null)
|
||||
reporter = ProtocolLibrary.getErrorReporter();
|
||||
if (invoker == null)
|
||||
invoker = (PacketFilterManager) manager;
|
||||
if (server == null)
|
||||
server = Bukkit.getServer();
|
||||
if (injectionFilter == null)
|
||||
throw new IllegalStateException("injectionFilter must be initialized.");
|
||||
if (packetListeners == null)
|
||||
throw new IllegalStateException("packetListeners must be initialized.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct the injection handler.
|
||||
* <p>
|
||||
* Any builder parameter marked as NON-NULL is essential and must be initialized.
|
||||
* @return The constructed injection handler using the current parameters.
|
||||
*/
|
||||
public PlayerInjectionHandler buildHandler() {
|
||||
// Fill any default fields
|
||||
initializeDefaults();
|
||||
|
||||
return new ProxyPlayerInjectionHandler(
|
||||
reporter, injectionFilter,
|
||||
invoker, packetListeners, server, version);
|
||||
}
|
||||
}
|
||||
-756
@@ -1,756 +0,0 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol.injector.player;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketAddress;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import net.sf.cglib.proxy.Factory;
|
||||
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.PacketType.Sender;
|
||||
import com.comphenix.protocol.Packets;
|
||||
import com.comphenix.protocol.concurrency.BlockingHashMap;
|
||||
import com.comphenix.protocol.concurrency.IntegerSet;
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.error.Report;
|
||||
import com.comphenix.protocol.error.ReportType;
|
||||
import com.comphenix.protocol.events.ListenerOptions;
|
||||
import com.comphenix.protocol.events.NetworkMarker;
|
||||
import com.comphenix.protocol.events.PacketAdapter;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.events.PacketListener;
|
||||
import com.comphenix.protocol.injector.GamePhase;
|
||||
import com.comphenix.protocol.injector.ListenerInvoker;
|
||||
import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks;
|
||||
import com.comphenix.protocol.injector.PlayerLoggedOutException;
|
||||
import com.comphenix.protocol.injector.packet.PacketRegistry;
|
||||
import com.comphenix.protocol.injector.server.AbstractInputStreamLookup;
|
||||
import com.comphenix.protocol.injector.server.BukkitSocketInjector;
|
||||
import com.comphenix.protocol.injector.server.InputStreamLookupBuilder;
|
||||
import com.comphenix.protocol.injector.server.SocketInjector;
|
||||
import com.comphenix.protocol.utility.MinecraftProtocolVersion;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
import com.comphenix.protocol.utility.MinecraftVersion;
|
||||
import com.comphenix.protocol.utility.SafeCacheBuilder;
|
||||
import com.google.common.base.Objects;
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
/**
|
||||
* Responsible for injecting into a player's sendPacket method.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
|
||||
// Warnings and errors
|
||||
public static final ReportType REPORT_UNSUPPPORTED_LISTENER = new ReportType("Cannot fully register listener for %s: %s");
|
||||
|
||||
// Fallback to older player hook types
|
||||
public static final ReportType REPORT_PLAYER_HOOK_FAILED = new ReportType("Player hook %s failed.");
|
||||
public static final ReportType REPORT_SWITCHED_PLAYER_HOOK = new ReportType("Switching to %s instead.");
|
||||
|
||||
public static final ReportType REPORT_HOOK_CLEANUP_FAILED = new ReportType("Cleaing up after player hook failed.");
|
||||
public static final ReportType REPORT_CANNOT_REVERT_HOOK = new ReportType("Unable to fully revert old injector. May cause conflicts.");
|
||||
|
||||
// Server connection injection
|
||||
private InjectedServerConnection serverInjection;
|
||||
|
||||
// Server socket injection
|
||||
private AbstractInputStreamLookup inputStreamLookup;
|
||||
|
||||
// NetLogin injector
|
||||
private NetLoginInjector netLoginInjector;
|
||||
|
||||
// The last successful player hook
|
||||
private WeakReference<PlayerInjector> lastSuccessfulHook;
|
||||
|
||||
// Dummy injection
|
||||
private ConcurrentMap<Player, PlayerInjector> dummyInjectors =
|
||||
SafeCacheBuilder.newBuilder().
|
||||
expireAfterWrite(30, TimeUnit.SECONDS).
|
||||
build(BlockingHashMap.<Player, PlayerInjector>newInvalidCacheLoader());
|
||||
|
||||
// Player injection
|
||||
private Map<Player, PlayerInjector> playerInjection = Maps.newConcurrentMap();
|
||||
|
||||
// Player injection types
|
||||
private volatile PlayerInjectHooks loginPlayerHook = PlayerInjectHooks.NETWORK_SERVER_OBJECT;
|
||||
private volatile PlayerInjectHooks playingPlayerHook = PlayerInjectHooks.NETWORK_SERVER_OBJECT;
|
||||
|
||||
// Error reporter
|
||||
private ErrorReporter reporter;
|
||||
|
||||
// Whether or not we're closing
|
||||
private boolean hasClosed;
|
||||
|
||||
// Used to invoke events
|
||||
private ListenerInvoker invoker;
|
||||
|
||||
// Current Minecraft version
|
||||
private MinecraftVersion version;
|
||||
|
||||
// Enabled packet filters
|
||||
private IntegerSet sendingFilters = new IntegerSet(Packets.MAXIMUM_PACKET_ID + 1);
|
||||
|
||||
// List of packet listeners
|
||||
private Set<PacketListener> packetListeners;
|
||||
|
||||
// Used to filter injection attempts
|
||||
private Predicate<GamePhase> injectionFilter;
|
||||
|
||||
public ProxyPlayerInjectionHandler(
|
||||
ErrorReporter reporter, Predicate<GamePhase> injectionFilter,
|
||||
ListenerInvoker invoker, Set<PacketListener> packetListeners, Server server, MinecraftVersion version) {
|
||||
|
||||
this.reporter = reporter;
|
||||
this.invoker = invoker;
|
||||
this.injectionFilter = injectionFilter;
|
||||
this.packetListeners = packetListeners;
|
||||
this.version = version;
|
||||
|
||||
this.inputStreamLookup = InputStreamLookupBuilder.newBuilder().
|
||||
server(server).
|
||||
reporter(reporter).
|
||||
build();
|
||||
|
||||
// Create net login injectors and the server connection injector
|
||||
this.netLoginInjector = new NetLoginInjector(reporter, server, this);
|
||||
this.serverInjection = new InjectedServerConnection(reporter, inputStreamLookup, server, netLoginInjector);
|
||||
serverInjection.injectList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getProtocolVersion(Player player) {
|
||||
// Just use the server version
|
||||
return MinecraftProtocolVersion.getCurrentVersion();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves how the server packets are read.
|
||||
* @return Injection method for reading server packets.
|
||||
*/
|
||||
@Override
|
||||
public PlayerInjectHooks getPlayerHook() {
|
||||
return getPlayerHook(GamePhase.PLAYING);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves how the server packets are read.
|
||||
* @param phase - the current game phase.
|
||||
* @return Injection method for reading server packets.
|
||||
*/
|
||||
@Override
|
||||
public PlayerInjectHooks getPlayerHook(GamePhase phase) {
|
||||
switch (phase) {
|
||||
case LOGIN:
|
||||
return loginPlayerHook;
|
||||
case PLAYING:
|
||||
return playingPlayerHook;
|
||||
default:
|
||||
throw new IllegalArgumentException("Cannot retrieve injection hook for both phases at the same time.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasMainThreadListener(PacketType type) {
|
||||
return sendingFilters.contains(type.getLegacyId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets how the server packets are read.
|
||||
* @param playerHook - the new injection method for reading server packets.
|
||||
*/
|
||||
@Override
|
||||
public void setPlayerHook(PlayerInjectHooks playerHook) {
|
||||
setPlayerHook(GamePhase.PLAYING, playerHook);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets how the server packets are read.
|
||||
* @param phase - the current game phase.
|
||||
* @param playerHook - the new injection method for reading server packets.
|
||||
*/
|
||||
@Override
|
||||
public void setPlayerHook(GamePhase phase, PlayerInjectHooks playerHook) {
|
||||
if (phase.hasLogin())
|
||||
loginPlayerHook = playerHook;
|
||||
if (phase.hasPlaying())
|
||||
playingPlayerHook = playerHook;
|
||||
|
||||
// Make sure the current listeners are compatible
|
||||
checkListener(packetListeners);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addPacketHandler(PacketType type, Set<ListenerOptions> options) {
|
||||
sendingFilters.add(type.getLegacyId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removePacketHandler(PacketType type) {
|
||||
sendingFilters.remove(type.getLegacyId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to construct a player hook.
|
||||
* @param player - the player to hook.
|
||||
* @param hook - the hook type.
|
||||
* @return A new player hoook
|
||||
* @throws IllegalAccessException Unable to do our reflection magic.
|
||||
*/
|
||||
private PlayerInjector getHookInstance(Player player, PlayerInjectHooks hook) throws IllegalAccessException {
|
||||
// Construct the correct player hook
|
||||
switch (hook) {
|
||||
case NETWORK_HANDLER_FIELDS:
|
||||
return new NetworkFieldInjector(reporter, player, invoker, sendingFilters);
|
||||
case NETWORK_MANAGER_OBJECT:
|
||||
return new NetworkObjectInjector(reporter, player, invoker, sendingFilters);
|
||||
case NETWORK_SERVER_OBJECT:
|
||||
return new NetworkServerInjector(reporter, player, invoker, sendingFilters, serverInjection);
|
||||
default:
|
||||
throw new IllegalArgumentException("Cannot construct a player injector.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a player by its DataInput connection.
|
||||
* @param inputStream - the associated DataInput connection.
|
||||
* @return The player we found.
|
||||
*/
|
||||
@Override
|
||||
public Player getPlayerByConnection(DataInputStream inputStream) {
|
||||
// Wait until the connection owner has been established
|
||||
SocketInjector injector = inputStreamLookup.waitSocketInjector(inputStream);
|
||||
|
||||
if (injector != null) {
|
||||
return injector.getPlayer();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function that retrieves the injector type of a given player injector.
|
||||
* @param injector - injector type.
|
||||
* @return The injector type.
|
||||
*/
|
||||
private PlayerInjectHooks getInjectorType(PlayerInjector injector) {
|
||||
return injector != null ? injector.getHookType() : PlayerInjectHooks.NONE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a player hook, allowing us to read server packets.
|
||||
* <p>
|
||||
* This call will be ignored if there's no listener that can receive the given events.
|
||||
* @param player - player to hook.
|
||||
* @param strategy - how to handle previous player injections.
|
||||
*/
|
||||
@Override
|
||||
public void injectPlayer(Player player, ConflictStrategy strategy) {
|
||||
// Inject using the player instance itself
|
||||
if (isInjectionNecessary(GamePhase.PLAYING)) {
|
||||
injectPlayer(player, player, strategy, GamePhase.PLAYING);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if it's truly necessary to perform the given player injection.
|
||||
* @param phase - current game phase.
|
||||
* @return TRUE if we should perform the injection, FALSE otherwise.
|
||||
*/
|
||||
public boolean isInjectionNecessary(GamePhase phase) {
|
||||
return injectionFilter.apply(phase);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a player hook, allowing us to read server packets.
|
||||
* <p>
|
||||
* This method will always perform the instructed injection.
|
||||
*
|
||||
* @param player - player to hook.
|
||||
* @param injectionPoint - the object to use during the injection process.
|
||||
* @param phase - the current game phase.
|
||||
* @return The resulting player injector, or NULL if the injection failed.
|
||||
*/
|
||||
PlayerInjector injectPlayer(Player player, Object injectionPoint, ConflictStrategy stategy, GamePhase phase) {
|
||||
if (player == null)
|
||||
throw new IllegalArgumentException("Player cannot be NULL.");
|
||||
if (injectionPoint == null)
|
||||
throw new IllegalArgumentException("injectionPoint cannot be NULL.");
|
||||
if (phase == null)
|
||||
throw new IllegalArgumentException("phase cannot be NULL.");
|
||||
|
||||
// Unfortunately, due to NetLoginHandler, multiple threads may potentially call this method.
|
||||
synchronized (player) {
|
||||
return injectPlayerInternal(player, injectionPoint, stategy, phase);
|
||||
}
|
||||
}
|
||||
|
||||
// Unsafe variant of the above
|
||||
private PlayerInjector injectPlayerInternal(Player player, Object injectionPoint, ConflictStrategy stategy, GamePhase phase) {
|
||||
PlayerInjector injector = playerInjection.get(player);
|
||||
PlayerInjectHooks tempHook = getPlayerHook(phase);
|
||||
PlayerInjectHooks permanentHook = tempHook;
|
||||
|
||||
// The given player object may be fake, so be careful!
|
||||
|
||||
// See if we need to inject something else
|
||||
boolean invalidInjector = injector != null ? !injector.canInject(phase) : true;
|
||||
|
||||
// Don't inject if the class has closed
|
||||
if (!hasClosed && (tempHook != getInjectorType(injector) || invalidInjector)) {
|
||||
while (tempHook != PlayerInjectHooks.NONE) {
|
||||
// Whether or not the current hook method failed completely
|
||||
boolean hookFailed = false;
|
||||
|
||||
// Remove the previous hook, if any
|
||||
cleanupHook(injector);
|
||||
|
||||
try {
|
||||
injector = getHookInstance(player, tempHook);
|
||||
|
||||
// Make sure this injection method supports the current game phase
|
||||
if (injector.canInject(phase)) {
|
||||
injector.initialize(injectionPoint);
|
||||
|
||||
// Get socket and socket injector
|
||||
SocketAddress address = injector.getAddress();
|
||||
|
||||
// Ignore logged out players
|
||||
if (address == null)
|
||||
return null;
|
||||
|
||||
SocketInjector previous = inputStreamLookup.peekSocketInjector(address);
|
||||
Socket socket = injector.getSocket();
|
||||
|
||||
// Close any previously associated hooks before we proceed
|
||||
if (previous != null && !(player instanceof Factory)) {
|
||||
switch (stategy) {
|
||||
case OVERRIDE:
|
||||
uninjectPlayer(previous.getPlayer(), true);
|
||||
break;
|
||||
case BAIL_OUT:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
injector.injectManager();
|
||||
|
||||
saveAddressLookup(address, socket, injector);
|
||||
break;
|
||||
}
|
||||
|
||||
} catch (PlayerLoggedOutException e) {
|
||||
throw e;
|
||||
|
||||
} catch (Exception e) {
|
||||
// Mark this injection attempt as a failure
|
||||
reporter.reportDetailed(this,
|
||||
Report.newBuilder(REPORT_PLAYER_HOOK_FAILED).messageParam(tempHook).callerParam(player, injectionPoint, phase).error(e)
|
||||
);
|
||||
hookFailed = true;
|
||||
}
|
||||
|
||||
// Choose the previous player hook type
|
||||
tempHook = PlayerInjectHooks.values()[tempHook.ordinal() - 1];
|
||||
|
||||
if (hookFailed)
|
||||
reporter.reportWarning(this, Report.newBuilder(REPORT_SWITCHED_PLAYER_HOOK).messageParam(tempHook));
|
||||
|
||||
// Check for UTTER FAILURE
|
||||
if (tempHook == PlayerInjectHooks.NONE) {
|
||||
cleanupHook(injector);
|
||||
injector = null;
|
||||
hookFailed = true;
|
||||
}
|
||||
|
||||
// Should we set the default hook method too?
|
||||
if (hookFailed) {
|
||||
permanentHook = tempHook;
|
||||
}
|
||||
}
|
||||
|
||||
// Update values
|
||||
if (injector != null)
|
||||
lastSuccessfulHook = new WeakReference<PlayerInjector>(injector);
|
||||
if (permanentHook != getPlayerHook(phase))
|
||||
setPlayerHook(phase, tempHook);
|
||||
|
||||
// Save injector
|
||||
if (injector != null) {
|
||||
playerInjection.put(player, injector);
|
||||
}
|
||||
}
|
||||
|
||||
return injector;
|
||||
}
|
||||
|
||||
private void saveAddressLookup(SocketAddress address, Socket socket, SocketInjector injector) {
|
||||
SocketAddress socketAddress = socket != null ? socket.getRemoteSocketAddress() : null;
|
||||
|
||||
if (socketAddress != null && !Objects.equal(socketAddress, address)) {
|
||||
// Save this version as well
|
||||
inputStreamLookup.setSocketInjector(socketAddress, injector);
|
||||
}
|
||||
// Save injector
|
||||
inputStreamLookup.setSocketInjector(address, injector);
|
||||
}
|
||||
|
||||
private void cleanupHook(PlayerInjector injector) {
|
||||
// Clean up as much as possible
|
||||
try {
|
||||
if (injector != null)
|
||||
injector.cleanupAll();
|
||||
} catch (Exception ex) {
|
||||
reporter.reportDetailed(this, Report.newBuilder(REPORT_HOOK_CLEANUP_FAILED).callerParam(injector).error(ex));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke special routines for handling disconnect before a player is uninjected.
|
||||
* @param player - player to process.
|
||||
*/
|
||||
@Override
|
||||
public void handleDisconnect(Player player) {
|
||||
PlayerInjector injector = getInjector(player);
|
||||
|
||||
if (injector != null) {
|
||||
injector.handleDisconnect();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updatePlayer(Player player) {
|
||||
SocketAddress address = player.getAddress();
|
||||
|
||||
// Ignore logged out players
|
||||
if (address != null) {
|
||||
SocketInjector injector = inputStreamLookup.peekSocketInjector(address);
|
||||
|
||||
if (injector != null) {
|
||||
injector.setUpdatedPlayer(player);
|
||||
} else {
|
||||
inputStreamLookup.setSocketInjector(player.getAddress(),
|
||||
new BukkitSocketInjector(player));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters the given player.
|
||||
* @param player - player to unregister.
|
||||
* @return TRUE if a player has been uninjected, FALSE otherwise.
|
||||
*/
|
||||
@Override
|
||||
public boolean uninjectPlayer(Player player) {
|
||||
return uninjectPlayer(player, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters the given player.
|
||||
* @param player - player to unregister.
|
||||
* @param prepareNextHook - whether or not we need to fix any lingering hooks.
|
||||
* @return TRUE if a player has been uninjected, FALSE otherwise.
|
||||
*/
|
||||
private boolean uninjectPlayer(Player player, boolean prepareNextHook) {
|
||||
if (!hasClosed && player != null) {
|
||||
|
||||
PlayerInjector injector = playerInjection.remove(player);
|
||||
|
||||
if (injector != null) {
|
||||
injector.cleanupAll();
|
||||
|
||||
// Remove the "hooked" network manager in our instance as well
|
||||
if (prepareNextHook && injector instanceof NetworkObjectInjector) {
|
||||
try {
|
||||
PlayerInjector dummyInjector = getHookInstance(player, PlayerInjectHooks.NETWORK_SERVER_OBJECT);
|
||||
dummyInjector.initializePlayer(player);
|
||||
dummyInjector.setNetworkManager(injector.getNetworkManager(), true);
|
||||
|
||||
} catch (IllegalAccessException e) {
|
||||
// Let the user know
|
||||
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_REVERT_HOOK).error(e));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters a player by the given address.
|
||||
* <p>
|
||||
* If the server handler has been created before we've gotten a chance to unject the player,
|
||||
* the method will try a workaround to remove the injected hook in the NetServerHandler.
|
||||
*
|
||||
* @param address - address of the player to unregister.
|
||||
* @return TRUE if a player has been uninjected, FALSE otherwise.
|
||||
*/
|
||||
@Override
|
||||
public boolean uninjectPlayer(InetSocketAddress address) {
|
||||
if (!hasClosed && address != null) {
|
||||
SocketInjector injector = inputStreamLookup.peekSocketInjector(address);
|
||||
|
||||
// Clean up
|
||||
if (injector != null)
|
||||
uninjectPlayer(injector.getPlayer(), true);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the given packet to the given receiver.
|
||||
* @param receiver - the player receiver.
|
||||
* @param packet - the packet to send.
|
||||
* @param filters - whether or not to invoke the packet filters.
|
||||
* @throws InvocationTargetException If an error occured during sending.
|
||||
*/
|
||||
@Override
|
||||
public void sendServerPacket(Player receiver, PacketContainer packet, NetworkMarker marker, boolean filters) throws InvocationTargetException {
|
||||
SocketInjector injector = getInjector(receiver);
|
||||
|
||||
// Send the packet, or drop it completely
|
||||
if (injector != null) {
|
||||
injector.sendServerPacket(packet.getHandle(), marker, filters);
|
||||
} else {
|
||||
throw new PlayerLoggedOutException(String.format(
|
||||
"Unable to send packet %s (%s): Player %s has logged out.",
|
||||
packet.getType(), packet, receiver
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recieve a packet as if it were sent by the given player.
|
||||
* @param player - the sender.
|
||||
* @param mcPacket - the packet to process.
|
||||
* @throws IllegalAccessException If the reflection machinery failed.
|
||||
* @throws InvocationTargetException If the underlying method caused an error.
|
||||
*/
|
||||
@Override
|
||||
public void recieveClientPacket(Player player, Object mcPacket) throws IllegalAccessException, InvocationTargetException {
|
||||
PlayerInjector injector = getInjector(player);
|
||||
|
||||
// Process the given packet, or simply give up
|
||||
if (injector != null)
|
||||
injector.processPacket(mcPacket);
|
||||
else
|
||||
throw new PlayerLoggedOutException(String.format(
|
||||
"Unable to receieve packet %s. Player %s has logged out.",
|
||||
mcPacket, player
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the injector associated with this player.
|
||||
* @param player - the player to find.
|
||||
* @return The injector, or NULL if not found.
|
||||
*/
|
||||
private PlayerInjector getInjector(Player player) {
|
||||
PlayerInjector injector = playerInjection.get(player);
|
||||
|
||||
if (injector == null) {
|
||||
// Try getting it from the player itself
|
||||
SocketAddress address = player.getAddress();
|
||||
|
||||
// Must have logged out - there's nothing we can do
|
||||
if (address == null)
|
||||
return null;
|
||||
|
||||
// Look that up without blocking
|
||||
SocketInjector result = inputStreamLookup.peekSocketInjector(address);
|
||||
|
||||
// Ensure that it is non-null and a player injector
|
||||
if (result instanceof PlayerInjector)
|
||||
return (PlayerInjector) result;
|
||||
else
|
||||
// Make a dummy injector them
|
||||
return createDummyInjector(player);
|
||||
|
||||
} else {
|
||||
return injector;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a simple dummy injector incase none has been constructed.
|
||||
* @param player - the CraftPlayer to construct for.
|
||||
* @return A dummy injector, or NULL if the given player is not a CraftPlayer.
|
||||
*/
|
||||
private PlayerInjector createDummyInjector(Player player) {
|
||||
if (!MinecraftReflection.getCraftPlayerClass().isAssignableFrom(player.getClass())) {
|
||||
// No - this is not safe
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
PlayerInjector dummyInjector = getHookInstance(player, PlayerInjectHooks.NETWORK_SERVER_OBJECT);
|
||||
dummyInjector.initializePlayer(player);
|
||||
|
||||
// This probably means the player has disconnected
|
||||
if (dummyInjector.getSocket() == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
inputStreamLookup.setSocketInjector(dummyInjector.getAddress(), dummyInjector);
|
||||
dummyInjectors.put(player, dummyInjector);
|
||||
return dummyInjector;
|
||||
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException("Cannot access fields.", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a player injector by looking for its NetworkManager.
|
||||
* @param networkManager - current network manager.
|
||||
* @return Related player injector.
|
||||
*/
|
||||
PlayerInjector getInjectorByNetworkHandler(Object networkManager) {
|
||||
// That's not legal
|
||||
if (networkManager == null)
|
||||
return null;
|
||||
|
||||
// O(n) is okay in this instance. This is only a backup solution.
|
||||
for (PlayerInjector injector : playerInjection.values()) {
|
||||
if (injector.getNetworkManager() == networkManager)
|
||||
return injector;
|
||||
}
|
||||
|
||||
// None found
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canRecievePackets() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketEvent handlePacketRecieved(PacketContainer packet, InputStream input, byte[] buffered) {
|
||||
throw new UnsupportedOperationException("Proxy injection cannot handle received packets.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the given listeners are valid.
|
||||
* @param listeners - listeners to check.
|
||||
*/
|
||||
@Override
|
||||
public void checkListener(Set<PacketListener> listeners) {
|
||||
// Make sure the current listeners are compatible
|
||||
if (getLastSuccessfulHook() != null) {
|
||||
for (PacketListener listener : listeners) {
|
||||
checkListener(listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the last successful hook.
|
||||
* <p>
|
||||
* May be NULL if the hook has been uninjected.
|
||||
* @return Last successful hook.
|
||||
*/
|
||||
private PlayerInjector getLastSuccessfulHook() {
|
||||
return lastSuccessfulHook != null ? lastSuccessfulHook.get() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a listener is valid or not.
|
||||
* <p>
|
||||
* If not, a warning will be printed to the console.
|
||||
* @param listener - listener to check.
|
||||
*/
|
||||
@Override
|
||||
public void checkListener(PacketListener listener) {
|
||||
PlayerInjector last = getLastSuccessfulHook();
|
||||
|
||||
if (last != null) {
|
||||
UnsupportedListener result = last.checkListener(version, listener);
|
||||
|
||||
// We won't prevent the listener, as it may still have valid packets
|
||||
if (result != null) {
|
||||
reporter.reportWarning(this,
|
||||
Report.newBuilder(REPORT_UNSUPPPORTED_LISTENER).messageParam(PacketAdapter.getPluginName(listener), result)
|
||||
);
|
||||
|
||||
// These are illegal
|
||||
for (int packetID : result.getPackets()) {
|
||||
removePacketHandler(PacketType.findLegacy(packetID, Sender.CLIENT));
|
||||
removePacketHandler(PacketType.findLegacy(packetID, Sender.SERVER));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the current list of registered sending listeners.
|
||||
* @return List of the sending listeners's packet IDs.
|
||||
*/
|
||||
@Override
|
||||
public Set<PacketType> getSendingFilters() {
|
||||
return PacketRegistry.toPacketTypes(sendingFilters.toSet(), Sender.SERVER);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
// Guard
|
||||
if (hasClosed || playerInjection == null)
|
||||
return;
|
||||
|
||||
// Remove everything
|
||||
for (PlayerInjector injection : playerInjection.values()) {
|
||||
if (injection != null) {
|
||||
injection.cleanupAll();
|
||||
}
|
||||
}
|
||||
|
||||
// Remove server handler
|
||||
if (inputStreamLookup != null)
|
||||
inputStreamLookup.cleanupAll();
|
||||
if (serverInjection != null)
|
||||
serverInjection.cleanupAll();
|
||||
if (netLoginInjector != null)
|
||||
netLoginInjector.cleanupAll();
|
||||
inputStreamLookup = null;
|
||||
serverInjection = null;
|
||||
netLoginInjector = null;
|
||||
hasClosed = true;
|
||||
|
||||
playerInjection.clear();
|
||||
invoker = null;
|
||||
}
|
||||
}
|
||||
-370
@@ -1,370 +0,0 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol.injector.player;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
|
||||
import com.google.common.base.Objects;
|
||||
import com.google.common.collect.BiMap;
|
||||
import com.google.common.collect.HashBiMap;
|
||||
|
||||
/**
|
||||
* Represents an array list that wraps another list, while automatically replacing one element with another.
|
||||
* <p>
|
||||
* The replaced elements can be recovered.
|
||||
*
|
||||
* @author Kristian
|
||||
* @param <TKey> - type of the elements we're replacing.
|
||||
*/
|
||||
class ReplacedArrayList<TKey> extends ArrayList<TKey> {
|
||||
/**
|
||||
* Generated by Eclipse.
|
||||
*/
|
||||
private static final long serialVersionUID = 1008492765999744804L;
|
||||
|
||||
private BiMap<TKey, TKey> replaceMap = HashBiMap.create();
|
||||
private List<TKey> underlyingList;
|
||||
|
||||
public ReplacedArrayList(List<TKey> underlyingList) {
|
||||
this.underlyingList = underlyingList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when a element inserted is replaced.
|
||||
* @param inserting - the element inserted.
|
||||
* @param replacement - the element that it should replace.
|
||||
*/
|
||||
protected void onReplacing(TKey inserting, TKey replacement) {
|
||||
// Default is to do nothing.
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when an element is being inserted.
|
||||
* <p>
|
||||
* This should be used to add a "replace" map.
|
||||
* @param inserting - the element to insert.
|
||||
*/
|
||||
protected void onInserting(TKey inserting) {
|
||||
// Default is again nothing
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when an element is being removed.
|
||||
* @param removing - the element being removed.
|
||||
*/
|
||||
protected void onRemoved(TKey removing) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean add(TKey element) {
|
||||
onInserting(element);
|
||||
|
||||
if (replaceMap.containsKey(element)) {
|
||||
TKey replacement = replaceMap.get(element);
|
||||
onReplacing(element, replacement);
|
||||
return delegate().add(replacement);
|
||||
} else {
|
||||
return delegate().add(element);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(int index, TKey element) {
|
||||
onInserting(element);
|
||||
|
||||
if (replaceMap.containsKey(element)) {
|
||||
TKey replacement = replaceMap.get(element);
|
||||
onReplacing(element, replacement);
|
||||
delegate().add(index, replacement);
|
||||
} else {
|
||||
delegate().add(index, element);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addAll(Collection<? extends TKey> collection) {
|
||||
int oldSize = size();
|
||||
|
||||
for (TKey element : collection)
|
||||
add(element);
|
||||
return size() != oldSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addAll(int index, Collection<? extends TKey> elements) {
|
||||
int oldSize = size();
|
||||
|
||||
for (TKey element : elements)
|
||||
add(index++, element);
|
||||
return size() != oldSize;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public boolean remove(Object object) {
|
||||
boolean success = delegate().remove(object);
|
||||
|
||||
if (success)
|
||||
onRemoved((TKey) object);
|
||||
return success;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TKey remove(int index) {
|
||||
TKey removed = delegate().remove(index);
|
||||
|
||||
if (removed != null)
|
||||
onRemoved(removed);
|
||||
return removed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeAll(Collection<?> collection) {
|
||||
int oldSize = size();
|
||||
|
||||
// Use the remove method
|
||||
for (Object element : collection)
|
||||
remove(element);
|
||||
return size() != oldSize;
|
||||
}
|
||||
|
||||
protected List<TKey> delegate() {
|
||||
return underlyingList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
for (TKey element : delegate())
|
||||
onRemoved(element);
|
||||
|
||||
delegate().clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(Object o) {
|
||||
return delegate().contains(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsAll(Collection<?> c) {
|
||||
return delegate().containsAll(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TKey get(int index) {
|
||||
return delegate().get(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int indexOf(Object o) {
|
||||
return delegate().indexOf(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return delegate().isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<TKey> iterator() {
|
||||
return delegate().iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int lastIndexOf(Object o) {
|
||||
return delegate().lastIndexOf(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListIterator<TKey> listIterator() {
|
||||
return delegate().listIterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListIterator<TKey> listIterator(int index) {
|
||||
return delegate().listIterator(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean retainAll(Collection<?> c) {
|
||||
int oldSize = size();
|
||||
|
||||
for (Iterator<TKey> it = delegate().iterator(); it.hasNext(); ) {
|
||||
TKey current = it.next();
|
||||
|
||||
// Remove elements that are not in the list
|
||||
if (!c.contains(current)) {
|
||||
it.remove();
|
||||
onRemoved(current);
|
||||
}
|
||||
}
|
||||
return size() != oldSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TKey set(int index, TKey element) {
|
||||
// Make sure to replace the element
|
||||
if (replaceMap.containsKey(element)) {
|
||||
TKey replacement = replaceMap.get(element);
|
||||
onReplacing(element, replacement);
|
||||
return delegate().set(index, replacement);
|
||||
} else {
|
||||
return delegate().set(index, element);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return delegate().size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<TKey> subList(int fromIndex, int toIndex) {
|
||||
return delegate().subList(fromIndex, toIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] toArray() {
|
||||
return delegate().toArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T[] toArray(T[] a) {
|
||||
return delegate().toArray(a);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a replace rule.
|
||||
* <p>
|
||||
* This automatically replaces every existing element.
|
||||
* @param target - instance to find.
|
||||
* @param replacement - instance to replace with.
|
||||
*/
|
||||
public synchronized void addMapping(TKey target, TKey replacement) {
|
||||
addMapping(target, replacement, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the old value, if it exists.
|
||||
* @param target - the key.
|
||||
* @return The value that was replaced, or NULL.
|
||||
*/
|
||||
public TKey getMapping(TKey target) {
|
||||
return replaceMap.get(target);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a replace rule.
|
||||
* <p>
|
||||
* This automatically replaces every existing element.
|
||||
* @param target - instance to find.
|
||||
* @param replacement - instance to replace with.
|
||||
* @param ignoreExisting - whether or not to ignore the existing elements.
|
||||
*/
|
||||
public synchronized void addMapping(TKey target, TKey replacement, boolean ignoreExisting) {
|
||||
replaceMap.put(target, replacement);
|
||||
|
||||
// Replace existing elements
|
||||
if (!ignoreExisting) {
|
||||
replaceAll(target, replacement);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Revert the given mapping.
|
||||
* @param target - the instance we replaced.
|
||||
* @return The old mapped value, or NULL if nothing was replaced.
|
||||
*/
|
||||
public synchronized TKey removeMapping(TKey target) {
|
||||
// Make sure the mapping exist
|
||||
if (replaceMap.containsKey(target)) {
|
||||
TKey replacement = replaceMap.get(target);
|
||||
replaceMap.remove(target);
|
||||
|
||||
// Revert existing elements
|
||||
replaceAll(replacement, target);
|
||||
return replacement;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Swap the new replaced value with its old value.
|
||||
* @param target - the instance we replaced.
|
||||
* @param The old mapped value, or NULL if nothing was swapped.
|
||||
*/
|
||||
public synchronized TKey swapMapping(TKey target) {
|
||||
// Make sure the mapping exist
|
||||
TKey replacement = removeMapping(target);
|
||||
|
||||
// Add the reverse
|
||||
if (replacement != null) {
|
||||
replaceMap.put(replacement, target);
|
||||
}
|
||||
return replacement;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace all instances of the given object.
|
||||
* @param find - object to find.
|
||||
* @param replace - object to replace it with.
|
||||
*/
|
||||
public synchronized void replaceAll(TKey find, TKey replace) {
|
||||
for (int i = 0; i < underlyingList.size(); i++) {
|
||||
if (Objects.equal(underlyingList.get(i), find)) {
|
||||
onReplacing(find, replace);
|
||||
underlyingList.set(i, replace);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Undo all replacements.
|
||||
*/
|
||||
public synchronized void revertAll() {
|
||||
|
||||
// No need to do anything else
|
||||
if (replaceMap.size() < 1)
|
||||
return;
|
||||
|
||||
BiMap<TKey, TKey> inverse = replaceMap.inverse();
|
||||
|
||||
for (int i = 0; i < underlyingList.size(); i++) {
|
||||
TKey replaced = underlyingList.get(i);
|
||||
|
||||
if (inverse.containsKey(replaced)) {
|
||||
TKey original = inverse.get(replaced);
|
||||
onReplacing(replaced, original);
|
||||
underlyingList.set(i, original);
|
||||
}
|
||||
}
|
||||
|
||||
replaceMap.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void finalize() throws Throwable {
|
||||
revertAll();
|
||||
super.finalize();
|
||||
}
|
||||
}
|
||||
-45
@@ -1,45 +0,0 @@
|
||||
package com.comphenix.protocol.injector.spigot;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.concurrency.PacketTypeSet;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.google.common.collect.Sets;
|
||||
|
||||
/**
|
||||
* Dummy packet injector that simply delegates to its parent Spigot packet injector or receiving filters.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
class DummyPacketInjector extends AbstractPacketInjector {
|
||||
private SpigotPacketInjector injector;
|
||||
private PacketTypeSet lastBufferedPackets = new PacketTypeSet();
|
||||
|
||||
public DummyPacketInjector(SpigotPacketInjector injector, PacketTypeSet reveivedFilters) {
|
||||
super(reveivedFilters);
|
||||
this.injector = injector;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void inputBuffersChanged(Set<PacketType> set) {
|
||||
Set<PacketType> removed = Sets.difference(lastBufferedPackets.values(), set);
|
||||
Set<PacketType> added = Sets.difference(set, lastBufferedPackets.values());
|
||||
|
||||
// Update the proxy packet injector
|
||||
for (PacketType packet : removed) {
|
||||
injector.getProxyPacketInjector().removePacketHandler(packet);
|
||||
}
|
||||
for (PacketType packet : added) {
|
||||
injector.getProxyPacketInjector().addPacketHandler(packet, null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketEvent packetRecieved(PacketContainer packet, Player client, byte[] buffered) {
|
||||
return injector.packetReceived(packet, client, buffered);
|
||||
}
|
||||
}
|
||||
-85
@@ -1,85 +0,0 @@
|
||||
package com.comphenix.protocol.injector.spigot;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.concurrency.PacketTypeSet;
|
||||
import com.comphenix.protocol.events.NetworkMarker;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.utility.MinecraftProtocolVersion;
|
||||
|
||||
/**
|
||||
* Dummy player handler that simply delegates to its parent Spigot packet injector.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
class DummyPlayerHandler extends AbstractPlayerHandler {
|
||||
private SpigotPacketInjector injector;
|
||||
|
||||
@Override
|
||||
public int getProtocolVersion(Player player) {
|
||||
// Just use the server version
|
||||
return MinecraftProtocolVersion.getCurrentVersion();
|
||||
}
|
||||
|
||||
public DummyPlayerHandler(SpigotPacketInjector injector, PacketTypeSet sendingFilters) {
|
||||
super(sendingFilters);
|
||||
this.injector = injector;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean uninjectPlayer(InetSocketAddress address) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean uninjectPlayer(Player player) {
|
||||
injector.uninjectPlayer(player);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendServerPacket(Player receiver, PacketContainer packet, NetworkMarker marker, boolean filters) throws InvocationTargetException {
|
||||
injector.sendServerPacket(receiver, packet, marker, filters);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void recieveClientPacket(Player player, Object mcPacket) throws IllegalAccessException, InvocationTargetException {
|
||||
injector.processPacket(player, mcPacket);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectPlayer(Player player, ConflictStrategy strategy) {
|
||||
// We don't care about strategy
|
||||
injector.injectPlayer(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasMainThreadListener(PacketType type) {
|
||||
return sendingFilters.contains(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleDisconnect(Player player) {
|
||||
// Just ignore
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketEvent handlePacketRecieved(PacketContainer packet, InputStream input, byte[] buffered) {
|
||||
// Associate this buffered data
|
||||
if (buffered != null) {
|
||||
injector.saveBuffered(packet.getHandle(), buffered);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updatePlayer(Player player) {
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
-669
@@ -1,669 +0,0 @@
|
||||
package com.comphenix.protocol.injector.spigot;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
import net.sf.cglib.proxy.Callback;
|
||||
import net.sf.cglib.proxy.CallbackFilter;
|
||||
import net.sf.cglib.proxy.Enhancer;
|
||||
import net.sf.cglib.proxy.Factory;
|
||||
import net.sf.cglib.proxy.MethodInterceptor;
|
||||
import net.sf.cglib.proxy.MethodProxy;
|
||||
import net.sf.cglib.proxy.NoOp;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.PacketType.Sender;
|
||||
import com.comphenix.protocol.concurrency.PacketTypeSet;
|
||||
import com.comphenix.protocol.error.DelegatedErrorReporter;
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.error.Report;
|
||||
import com.comphenix.protocol.error.ReportType;
|
||||
import com.comphenix.protocol.events.ConnectionSide;
|
||||
import com.comphenix.protocol.events.NetworkMarker;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.injector.ListenerInvoker;
|
||||
import com.comphenix.protocol.injector.PacketFilterManager;
|
||||
import com.comphenix.protocol.injector.PlayerLoggedOutException;
|
||||
import com.comphenix.protocol.injector.packet.LegacyNetworkMarker;
|
||||
import com.comphenix.protocol.injector.packet.PacketInjector;
|
||||
import com.comphenix.protocol.injector.player.NetworkObjectInjector;
|
||||
import com.comphenix.protocol.injector.player.PlayerInjectionHandler;
|
||||
import com.comphenix.protocol.reflect.FieldUtils;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.comphenix.protocol.reflect.MethodInfo;
|
||||
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
|
||||
import com.comphenix.protocol.utility.EnhancerFactory;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.MapMaker;
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
/**
|
||||
* Offload all the work to Spigot, if possible.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public class SpigotPacketInjector implements SpigotPacketListener {
|
||||
public static final ReportType REPORT_CANNOT_CLEANUP_SPIGOT = new ReportType("Cannot cleanup Spigot listener.");
|
||||
|
||||
// Lazily retrieve the spigot listener class
|
||||
private static volatile Class<?> spigotListenerClass;
|
||||
private static volatile boolean classChecked;
|
||||
|
||||
// Retrieve the entity player from a PlayerConnection
|
||||
private static volatile Field playerConnectionPlayer;
|
||||
|
||||
// Packets that are not to be processed by the filters
|
||||
private Set<Object> ignoredPackets = Collections.newSetFromMap(new MapMaker().weakKeys().<Object, Boolean>makeMap());
|
||||
|
||||
/**
|
||||
* The amount of ticks to wait before removing all traces of a player.
|
||||
*/
|
||||
private static final int CLEANUP_DELAY = 100;
|
||||
|
||||
// The listener we will register on Spigot.
|
||||
// Unfortunately, due to the use of PlayerConnection, INetworkManager and Packet, we're
|
||||
// unable to reference it directly. But with CGLib, it shouldn't cost us much.
|
||||
private Object dynamicListener;
|
||||
|
||||
// Reference to ProtocolLib
|
||||
private Plugin plugin;
|
||||
|
||||
// Different sending filters
|
||||
private PacketTypeSet queuedFilters;
|
||||
private PacketTypeSet reveivedFilters;
|
||||
|
||||
// NetworkManager to injector and player
|
||||
private ConcurrentMap<Object, NetworkObjectInjector> networkManagerInjector = Maps.newConcurrentMap();
|
||||
|
||||
// Player to injector
|
||||
private ConcurrentMap<Player, NetworkObjectInjector> playerInjector = Maps.newConcurrentMap();
|
||||
|
||||
// For handling read buffered packet data
|
||||
private Map<Object, byte[]> readBufferedPackets = new MapMaker().weakKeys().makeMap();
|
||||
|
||||
// Responsible for informing the PL packet listeners
|
||||
private ListenerInvoker invoker;
|
||||
private ErrorReporter reporter;
|
||||
private Server server;
|
||||
|
||||
// The proxy packet injector
|
||||
private PacketInjector proxyPacketInjector;
|
||||
|
||||
// Background task
|
||||
private static final int BACKGROUND_DELAY = 30 * PacketFilterManager.TICKS_PER_SECOND;
|
||||
private int backgroundId;
|
||||
|
||||
/**
|
||||
* Create a new spigot injector.
|
||||
*/
|
||||
public SpigotPacketInjector(ErrorReporter reporter, ListenerInvoker invoker, Server server) {
|
||||
this.reporter = reporter;
|
||||
this.invoker = invoker;
|
||||
this.server = server;
|
||||
this.queuedFilters = new PacketTypeSet();
|
||||
this.reveivedFilters = new PacketTypeSet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the underlying listener invoker.
|
||||
* @return The invoker.
|
||||
*/
|
||||
public ListenerInvoker getInvoker() {
|
||||
return invoker;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the real proxy packet injector.
|
||||
* @param proxyPacketInjector - the real injector.
|
||||
*/
|
||||
public void setProxyPacketInjector(PacketInjector proxyPacketInjector) {
|
||||
this.proxyPacketInjector = proxyPacketInjector;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the real proxy packet injector.
|
||||
* @return The real injector.
|
||||
*/
|
||||
public PacketInjector getProxyPacketInjector() {
|
||||
return proxyPacketInjector;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the spigot packet listener class.
|
||||
* @return The listener class.
|
||||
*/
|
||||
private static Class<?> getSpigotListenerClass() {
|
||||
if (!classChecked) {
|
||||
try {
|
||||
spigotListenerClass = SpigotPacketInjector.class.getClassLoader().loadClass("org.spigotmc.netty.PacketListener");
|
||||
} catch (ClassNotFoundException e) {
|
||||
return null;
|
||||
} finally {
|
||||
// We've given it a try now
|
||||
classChecked = true;
|
||||
}
|
||||
}
|
||||
return spigotListenerClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the register packet listener method.
|
||||
* @return The method used to register a packet listener.
|
||||
*/
|
||||
private static Method getRegisterMethod() {
|
||||
Class<?> clazz = getSpigotListenerClass();
|
||||
|
||||
if (clazz != null) {
|
||||
try {
|
||||
return clazz.getMethod("register", clazz, Plugin.class);
|
||||
} catch (SecurityException e) {
|
||||
// If this happens, then ... we're doomed
|
||||
throw new RuntimeException("Reflection is not allowed.", e);
|
||||
} catch (NoSuchMethodException e) {
|
||||
throw new IllegalStateException("Cannot find register() method in " + clazz, e);
|
||||
}
|
||||
}
|
||||
|
||||
// Also bad
|
||||
throw new IllegalStateException("Spigot could not be found!");
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if there is a Spigot packet listener.
|
||||
* @return Spigot packet listener.
|
||||
*/
|
||||
public static boolean canUseSpigotListener() {
|
||||
return getSpigotListenerClass() != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the Spigot packet injector.
|
||||
* @param plugin - the parent plugin.
|
||||
* @return TRUE if we registered the plugin, FALSE otherwise.
|
||||
*/
|
||||
public boolean register(Plugin plugin) {
|
||||
if (hasRegistered())
|
||||
return false;
|
||||
|
||||
// Save the plugin too
|
||||
this.plugin = plugin;
|
||||
|
||||
final Callback[] callbacks = new Callback[3];
|
||||
final boolean[] found = new boolean[3];
|
||||
|
||||
// Packets received from the clients
|
||||
callbacks[0] = new MethodInterceptor() {
|
||||
@Override
|
||||
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
|
||||
return SpigotPacketInjector.this.packetReceived(args[0], args[1], args[2]);
|
||||
}
|
||||
};
|
||||
// Packet sent/queued
|
||||
callbacks[1] = new MethodInterceptor() {
|
||||
@Override
|
||||
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
|
||||
return SpigotPacketInjector.this.packetQueued(args[0], args[1], args[2]);
|
||||
}
|
||||
};
|
||||
|
||||
// Don't care for everything else
|
||||
callbacks[2] = NoOp.INSTANCE;
|
||||
|
||||
Enhancer enhancer = EnhancerFactory.getInstance().createEnhancer();
|
||||
enhancer.setSuperclass(getSpigotListenerClass());
|
||||
enhancer.setCallbacks(callbacks);
|
||||
enhancer.setCallbackFilter(new CallbackFilter() {
|
||||
@Override
|
||||
public int accept(Method method) {
|
||||
// We'll be pretty stringent
|
||||
if (matchMethod("packetReceived", method)) {
|
||||
found[0] = true;
|
||||
return 0;
|
||||
} else if (matchMethod("packetQueued", method)) {
|
||||
found[1] = true;
|
||||
return 1;
|
||||
} else {
|
||||
found[2] = true;
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
});
|
||||
dynamicListener = enhancer.create();
|
||||
|
||||
// Verify methods
|
||||
if (!found[0])
|
||||
throw new IllegalStateException("Unable to find a valid packet receiver in Spigot.");
|
||||
if (!found[1])
|
||||
throw new IllegalStateException("Unable to find a valid packet queue in Spigot.");
|
||||
|
||||
// Lets register it too
|
||||
try {
|
||||
getRegisterMethod().invoke(null, dynamicListener, plugin);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Cannot register Spigot packet listener.", e);
|
||||
}
|
||||
|
||||
// Remember to register background task
|
||||
backgroundId = createBackgroundTask();
|
||||
|
||||
// If we succeed
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and register a background task.
|
||||
* @return The background task ID.
|
||||
*/
|
||||
private int createBackgroundTask() {
|
||||
return Bukkit.getScheduler().scheduleSyncRepeatingTask(plugin, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
cleanupInjectors();
|
||||
}
|
||||
}, BACKGROUND_DELAY, BACKGROUND_DELAY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that all disconnected injectors are removed from memory.
|
||||
*/
|
||||
private void cleanupInjectors() {
|
||||
for (NetworkObjectInjector injector : networkManagerInjector.values()) {
|
||||
try {
|
||||
if (injector.getSocket() != null && injector.getSocket().isClosed()) {
|
||||
cleanupInjector(injector);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
reporter.reportMinimal(plugin, "cleanupInjectors", e);
|
||||
|
||||
// What?
|
||||
cleanupInjector(injector);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a given network object injector.
|
||||
* @param injector - the injector.
|
||||
*/
|
||||
private void cleanupInjector(final NetworkObjectInjector injector) {
|
||||
// Clean up
|
||||
playerInjector.remove(injector.getPlayer());
|
||||
playerInjector.remove(injector.getUpdatedPlayer());
|
||||
networkManagerInjector.remove(injector.getNetworkManager());
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the given method is a valid packet receiver or queued method.
|
||||
* @param methodName - the expected name of the method.
|
||||
* @param method - the method we're testing.
|
||||
* @return TRUE if this is a correct method, FALSE otherwise.
|
||||
*/
|
||||
private boolean matchMethod(String methodName, Method method) {
|
||||
return FuzzyMethodContract.newBuilder().
|
||||
nameExact(methodName).
|
||||
parameterCount(3).
|
||||
parameterSuperOf(MinecraftReflection.getNetHandlerClass(), 1).
|
||||
parameterSuperOf(MinecraftReflection.getPacketClass(), 2).
|
||||
returnTypeExact(MinecraftReflection.getPacketClass()).
|
||||
build().
|
||||
isMatch(MethodInfo.fromMethod(method), null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the Spigot packet listener has been registered.
|
||||
* @return TRUE if it has, FALSE otherwise.
|
||||
*/
|
||||
public boolean hasRegistered() {
|
||||
return dynamicListener != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the dummy player injection handler.
|
||||
* @return Dummy player injection handler.
|
||||
*/
|
||||
public PlayerInjectionHandler getPlayerHandler() {
|
||||
return new DummyPlayerHandler(this, queuedFilters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the dummy packet injection handler.
|
||||
* @return Dummy packet injection handler.
|
||||
*/
|
||||
public PacketInjector getPacketInjector() {
|
||||
return new DummyPacketInjector(this, reveivedFilters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the currently registered injector for the given player.
|
||||
* @param player - injected player.
|
||||
* @param createNew - whether or not to create a new injector if the current is missing.
|
||||
* @return The injector.
|
||||
*/
|
||||
NetworkObjectInjector getInjector(Player player, boolean createNew) {
|
||||
NetworkObjectInjector injector = playerInjector.get(player);
|
||||
|
||||
if (injector == null && createNew) {
|
||||
// Check for temporary players ..
|
||||
if ((player instanceof Factory))
|
||||
throw new IllegalArgumentException("Cannot inject tempoary player " + player);
|
||||
|
||||
try {
|
||||
NetworkObjectInjector created = new NetworkObjectInjector(
|
||||
filterImpossibleWarnings(reporter), null, invoker, null);
|
||||
|
||||
created.initializePlayer(player);
|
||||
|
||||
if (created.getNetworkManager() == null)
|
||||
throw new PlayerLoggedOutException("Player " + player + " has logged out.");
|
||||
injector = saveInjector(created.getNetworkManager(), created);
|
||||
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException("Cannot create dummy injector.", e);
|
||||
}
|
||||
}
|
||||
return injector;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve or create a registered injector for the given network manager and connection.
|
||||
* @param networkManager - a INetworkManager object.
|
||||
* @param connection - a Connection (PlayerConnection, PendingConnection) object.
|
||||
* @return The created NetworkObjectInjector with a temporary player.
|
||||
*/
|
||||
NetworkObjectInjector getInjector(Object networkManager, Object connection) {
|
||||
NetworkObjectInjector dummyInjector = networkManagerInjector.get(networkManager);
|
||||
|
||||
if (dummyInjector == null) {
|
||||
// Inject the network manager
|
||||
try {
|
||||
NetworkObjectInjector created = new NetworkObjectInjector(
|
||||
filterImpossibleWarnings(reporter), null, invoker, null);
|
||||
|
||||
if (MinecraftReflection.isLoginHandler(connection)) {
|
||||
created.initialize(connection);
|
||||
created.setPlayer(created.createTemporaryPlayer(server));
|
||||
} else if (MinecraftReflection.isServerHandler(connection)) {
|
||||
// Get the player instead
|
||||
if (playerConnectionPlayer == null)
|
||||
playerConnectionPlayer = FuzzyReflection.fromObject(connection).
|
||||
getFieldByType("player", MinecraftReflection.getEntityPlayerClass());
|
||||
Object entityPlayer = playerConnectionPlayer.get(connection);
|
||||
|
||||
created.initialize(MinecraftReflection.getBukkitEntity(entityPlayer));
|
||||
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unregonized connection in NetworkManager.");
|
||||
}
|
||||
|
||||
dummyInjector = saveInjector(networkManager, created);
|
||||
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException("Cannot create dummy injector.", e);
|
||||
}
|
||||
}
|
||||
|
||||
return dummyInjector;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a delegated error reporter that ignores certain warnings that are irrelevant on Spigot.
|
||||
* @param reporter - error reporter to delegate.
|
||||
* @return The filtered error reporter.
|
||||
*/
|
||||
private ErrorReporter filterImpossibleWarnings(ErrorReporter reporter) {
|
||||
return new DelegatedErrorReporter(reporter) {
|
||||
@Override
|
||||
protected Report filterReport(Object sender, Report report, boolean detailed) {
|
||||
// This doesn't matter - ignore it
|
||||
if (report.getType() == NetworkObjectInjector.REPORT_DETECTED_CUSTOM_SERVER_HANDLER)
|
||||
return null;
|
||||
return report;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a given player injector for later.
|
||||
* @param networkManager - the associated network manager.
|
||||
* @param created - the created network object creator.
|
||||
* @return Any other network injector that came before us.
|
||||
*/
|
||||
private NetworkObjectInjector saveInjector(Object networkManager, NetworkObjectInjector created) {
|
||||
// Concurrency - use the same injector!
|
||||
NetworkObjectInjector result = networkManagerInjector.putIfAbsent(networkManager, created);
|
||||
|
||||
if (result == null) {
|
||||
result = created;
|
||||
}
|
||||
|
||||
// Save the player as well
|
||||
playerInjector.put(created.getPlayer(), created);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the buffered serialized input packet.
|
||||
* @param handle - the associated packet.
|
||||
* @param buffered - the buffere data to save.
|
||||
*/
|
||||
public void saveBuffered(Object handle, byte[] buffered) {
|
||||
readBufferedPackets.put(handle, buffered);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object packetReceived(Object networkManager, Object connection, Object packet) {
|
||||
if (reveivedFilters.contains(packet.getClass())) {
|
||||
@SuppressWarnings("deprecation")
|
||||
Integer id = invoker.getPacketID(packet);
|
||||
|
||||
// Check for ignored packets
|
||||
if (ignoredPackets.remove(packet)) {
|
||||
return packet;
|
||||
}
|
||||
|
||||
Player sender = getInjector(networkManager, connection).getUpdatedPlayer();
|
||||
PacketType type = PacketType.findLegacy(id, Sender.CLIENT);
|
||||
PacketContainer container = new PacketContainer(type, packet);
|
||||
PacketEvent event = packetReceived(container, sender, readBufferedPackets.get(packet));
|
||||
|
||||
if (!event.isCancelled())
|
||||
return event.getPacket().getHandle();
|
||||
else
|
||||
return null; // Cancel
|
||||
}
|
||||
// Don't change anything
|
||||
return packet;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object packetQueued(Object networkManager, Object connection, Object packet) {
|
||||
if (queuedFilters.contains(packet.getClass())) {
|
||||
@SuppressWarnings("deprecation")
|
||||
Integer id = invoker.getPacketID(packet);
|
||||
|
||||
// Check for ignored packets
|
||||
if (ignoredPackets.remove(packet)) {
|
||||
return packet;
|
||||
}
|
||||
|
||||
Player reciever = getInjector(networkManager, connection).getUpdatedPlayer();
|
||||
PacketType type = PacketType.findLegacy(id, Sender.SERVER);
|
||||
PacketContainer container = new PacketContainer(type, packet);
|
||||
PacketEvent event = packetQueued(container, reciever);
|
||||
|
||||
if (!event.isCancelled())
|
||||
return event.getPacket().getHandle();
|
||||
else
|
||||
return null; // Cancel
|
||||
}
|
||||
// Don't change anything
|
||||
return packet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to inform the event listeners of a queued packet.
|
||||
* @param packet - the packet that is to be sent.
|
||||
* @param receiver - the receiver of this packet.
|
||||
* @return The packet event that was used.
|
||||
*/
|
||||
PacketEvent packetQueued(PacketContainer packet, Player receiver) {
|
||||
PacketEvent event = PacketEvent.fromServer(this, packet, receiver);
|
||||
|
||||
invoker.invokePacketSending(event);
|
||||
return event;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to inform the event listeners of a received packet.
|
||||
* @param packet - the packet that has been receieved.
|
||||
* @param sender - the client packet.
|
||||
* @return The packet event that was used.
|
||||
*/
|
||||
PacketEvent packetReceived(PacketContainer packet, Player sender, byte[] buffered) {
|
||||
NetworkMarker marker = buffered != null ? new LegacyNetworkMarker(ConnectionSide.CLIENT_SIDE, buffered, packet.getType()) : null;
|
||||
PacketEvent event = PacketEvent.fromClient(this, packet, marker, sender);
|
||||
|
||||
invoker.invokePacketRecieving(event);
|
||||
return event;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a player has logged in properly.
|
||||
* @param player - the player that has logged in.
|
||||
*/
|
||||
void injectPlayer(Player player) {
|
||||
try {
|
||||
NetworkObjectInjector dummy = new NetworkObjectInjector(
|
||||
filterImpossibleWarnings(reporter), player, invoker, null);
|
||||
dummy.initializePlayer(player);
|
||||
|
||||
// Save this player for the network manager
|
||||
NetworkObjectInjector realInjector = networkManagerInjector.get(dummy.getNetworkManager());
|
||||
|
||||
if (realInjector != null) {
|
||||
// Update all future references
|
||||
realInjector.setUpdatedPlayer(player);
|
||||
playerInjector.put(player, realInjector);
|
||||
} else {
|
||||
// Ah - in that case, save this injector
|
||||
saveInjector(dummy.getNetworkManager(), dummy);
|
||||
}
|
||||
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException("Cannot inject " + player);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Uninject the given player.
|
||||
* @param player - the player to uninject.
|
||||
*/
|
||||
void uninjectPlayer(Player player) {
|
||||
final NetworkObjectInjector injector = getInjector(player, false);
|
||||
|
||||
if (player != null && injector != null) {
|
||||
Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
cleanupInjector(injector);
|
||||
}
|
||||
}, CLEANUP_DELAY);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when a plugin wants to sent a packet.
|
||||
* @param receiver - the packet receiver.
|
||||
* @param packet - the packet to transmit.
|
||||
* @param marker - the network marker object.
|
||||
* @param filters - whether or not to invoke the packet listeners.
|
||||
* @throws InvocationTargetException If anything went wrong.
|
||||
*/
|
||||
void sendServerPacket(Player receiver, PacketContainer packet, NetworkMarker marker, boolean filters) throws InvocationTargetException {
|
||||
NetworkObjectInjector networkObject = getInjector(receiver, true);
|
||||
|
||||
// If TRUE, process this packet like any other
|
||||
if (filters)
|
||||
ignoredPackets.remove(packet.getHandle());
|
||||
else
|
||||
ignoredPackets.add(packet.getHandle());
|
||||
|
||||
networkObject.sendServerPacket(packet.getHandle(), marker, filters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when a plugin wants to simulate receiving a packet.
|
||||
* @param player - the supposed sender.
|
||||
* @param mcPacket - the packet to receieve.
|
||||
* @throws IllegalAccessException Reflection is not permitted.
|
||||
* @throws InvocationTargetException Minecraft threw an exception.
|
||||
*/
|
||||
void processPacket(Player player, Object mcPacket) throws IllegalAccessException, InvocationTargetException {
|
||||
NetworkObjectInjector networkObject = getInjector(player, true);
|
||||
|
||||
// We will always ignore this packet
|
||||
ignoredPackets.add(mcPacket);
|
||||
networkObject.processPacket(mcPacket);
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
private void cleanupListener() {
|
||||
Class<?> listenerClass = getSpigotListenerClass();
|
||||
|
||||
// Yeah ... there's no easy way to remove the listener
|
||||
synchronized (listenerClass) {
|
||||
try {
|
||||
Field listenersField = FieldUtils.getField(listenerClass, "listeners", true);
|
||||
Field bakedField = FieldUtils.getField(listenerClass, "baked", true);
|
||||
|
||||
Map<Object, Plugin> listenerMap = (Map<Object, Plugin>) listenersField.get(null);
|
||||
List<Object> listenerArray = Lists.newArrayList((Object[]) bakedField.get(null));
|
||||
|
||||
listenerMap.remove(dynamicListener);
|
||||
listenerArray.remove(dynamicListener);
|
||||
|
||||
// Save the array back
|
||||
bakedField.set(null, Iterables.toArray(listenerArray, (Class)listenerClass));
|
||||
|
||||
// Success
|
||||
dynamicListener = null;
|
||||
} catch (Exception e) {
|
||||
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_CLEANUP_SPIGOT).
|
||||
callerParam(dynamicListener).error(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when the server is cleaning up.
|
||||
*/
|
||||
public void cleanupAll() {
|
||||
// Cleanup the Spigot listener
|
||||
if (dynamicListener != null) {
|
||||
cleanupListener();
|
||||
}
|
||||
if (backgroundId >= 0) {
|
||||
Bukkit.getScheduler().cancelTask(backgroundId);
|
||||
backgroundId = -1;
|
||||
}
|
||||
|
||||
// Cleanup network marker
|
||||
if (proxyPacketInjector != null) {
|
||||
proxyPacketInjector.cleanupAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
-34
@@ -1,34 +0,0 @@
|
||||
package com.comphenix.protocol.injector.spigot;
|
||||
|
||||
/**
|
||||
* Represents a proxy for a Spigot packet listener.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
interface SpigotPacketListener {
|
||||
/**
|
||||
* Called when a packet has been received and is about to be handled by the
|
||||
* current Connection.
|
||||
* <p>
|
||||
* The returned packet will be the packet passed on for handling, or in the case of
|
||||
* null being returned, not handled at all.
|
||||
*
|
||||
* @param networkManager - the NetworkManager receiving the packet
|
||||
* @param connection - the connection which will handle the packet
|
||||
* @param packet - the received packet
|
||||
* @return the packet to be handled, or null to cancel
|
||||
*/
|
||||
public Object packetReceived(Object networkManager, Object connection, Object packet);
|
||||
|
||||
/**
|
||||
* Called when a packet is queued to be sent.
|
||||
* <p>
|
||||
* The returned packet will be the packet sent. In the case of null being returned,
|
||||
* the packet will not be sent.
|
||||
* @param networkManager - the NetworkManager which will send the packet
|
||||
* @param connection - the connection which queued the packet
|
||||
* @param packet - the queue packet
|
||||
* @return the packet to be sent, or null if the packet will not be sent.
|
||||
*/
|
||||
public Object packetQueued(Object networkManager, Object connection, Object packet);
|
||||
}
|
||||
@@ -1,672 +0,0 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol.metrics;
|
||||
|
||||
/*
|
||||
* Copyright 2011 Tyler Blair. All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification, are
|
||||
* permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this list of
|
||||
* conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
|
||||
* of conditions and the following disclaimer in the documentation and/or other materials
|
||||
* provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY EXPRESS OR IMPLIED
|
||||
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* The views and conclusions contained in the software and documentation are those of the
|
||||
* authors and contributors and should not be interpreted as representing official policies,
|
||||
* either expressed or implied, of anybody else.
|
||||
*/
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.Proxy;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.configuration.InvalidConfigurationException;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.bukkit.plugin.PluginDescriptionFile;
|
||||
|
||||
import com.comphenix.protocol.utility.BukkitUtil;
|
||||
import com.comphenix.protocol.utility.WrappedScheduler;
|
||||
|
||||
/**
|
||||
* <p> The metrics class obtains data about a plugin and submits statistics about it to the metrics backend. </p> <p>
|
||||
* Public methods provided by this class: </p>
|
||||
* <code>
|
||||
* Graph createGraph(String name); <br/>
|
||||
* void addCustomData(BukkitMetrics.Plotter plotter); <br/>
|
||||
* void start(); <br/>
|
||||
* </code>
|
||||
*/
|
||||
public class Metrics {
|
||||
|
||||
/**
|
||||
* The current revision number
|
||||
*/
|
||||
private final static int REVISION = 6;
|
||||
/**
|
||||
* The base url of the metrics domain
|
||||
*/
|
||||
private static final String BASE_URL = "http://mcstats.org";
|
||||
/**
|
||||
* The url used to report a server's status
|
||||
*/
|
||||
private static final String REPORT_URL = "/report/%s";
|
||||
/**
|
||||
* The separator to use for custom data. This MUST NOT change unless you are hosting your own version of metrics and
|
||||
* want to change it.
|
||||
*/
|
||||
private static final String CUSTOM_DATA_SEPARATOR = "~~";
|
||||
/**
|
||||
* Interval of time to ping (in minutes)
|
||||
*/
|
||||
private static final int PING_INTERVAL = 10;
|
||||
/**
|
||||
* The plugin this metrics submits for
|
||||
*/
|
||||
private final Plugin plugin;
|
||||
/**
|
||||
* All of the custom graphs to submit to metrics
|
||||
*/
|
||||
private final Set<Graph> graphs = Collections.synchronizedSet(new HashSet<Graph>());
|
||||
/**
|
||||
* The default graph, used for addCustomData when you don't want a specific graph
|
||||
*/
|
||||
private final Graph defaultGraph = new Graph("Default");
|
||||
/**
|
||||
* The plugin configuration file
|
||||
*/
|
||||
private final YamlConfiguration configuration;
|
||||
/**
|
||||
* The plugin configuration file
|
||||
*/
|
||||
private final File configurationFile;
|
||||
/**
|
||||
* Unique server id
|
||||
*/
|
||||
private final String guid;
|
||||
/**
|
||||
* Debug mode
|
||||
*/
|
||||
private final boolean debug;
|
||||
/**
|
||||
* Lock for synchronization
|
||||
*/
|
||||
private final Object optOutLock = new Object();
|
||||
/**
|
||||
* The scheduled task
|
||||
*/
|
||||
private volatile WrappedScheduler.TaskWrapper task = null;
|
||||
|
||||
public Metrics(final Plugin plugin) throws IOException {
|
||||
if (plugin == null) {
|
||||
throw new IllegalArgumentException("Plugin cannot be null");
|
||||
}
|
||||
|
||||
this.plugin = plugin;
|
||||
|
||||
// load the config
|
||||
configurationFile = getConfigFile();
|
||||
configuration = YamlConfiguration.loadConfiguration(configurationFile);
|
||||
|
||||
// add some defaults
|
||||
configuration.addDefault("opt-out", false);
|
||||
configuration.addDefault("guid", UUID.randomUUID().toString());
|
||||
configuration.addDefault("debug", false);
|
||||
|
||||
// Do we need to create the file?
|
||||
if (configuration.get("guid", null) == null) {
|
||||
configuration.options().header("http://mcstats.org").copyDefaults(true);
|
||||
configuration.save(configurationFile);
|
||||
}
|
||||
|
||||
// Load the guid then
|
||||
guid = configuration.getString("guid");
|
||||
debug = configuration.getBoolean("debug", false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct and create a Graph that can be used to separate specific plotters to their own graphs on the metrics
|
||||
* website. Plotters can be added to the graph object returned.
|
||||
*
|
||||
* @param name The name of the graph
|
||||
* @return Graph object created. Will never return NULL under normal circumstances unless bad parameters are given
|
||||
*/
|
||||
public Graph createGraph(final String name) {
|
||||
if (name == null) {
|
||||
throw new IllegalArgumentException("Graph name cannot be null");
|
||||
}
|
||||
|
||||
// Construct the graph object
|
||||
final Graph graph = new Graph(name);
|
||||
|
||||
// Now we can add our graph
|
||||
graphs.add(graph);
|
||||
|
||||
// and return back
|
||||
return graph;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a Graph object to BukkitMetrics that represents data for the plugin that should be sent to the backend
|
||||
*
|
||||
* @param graph The name of the graph
|
||||
*/
|
||||
public void addGraph(final Graph graph) {
|
||||
if (graph == null) {
|
||||
throw new IllegalArgumentException("Graph cannot be null");
|
||||
}
|
||||
|
||||
graphs.add(graph);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a custom data plotter to the default graph
|
||||
*
|
||||
* @param plotter The plotter to use to plot custom data
|
||||
*/
|
||||
public void addCustomData(final Plotter plotter) {
|
||||
if (plotter == null) {
|
||||
throw new IllegalArgumentException("Plotter cannot be null");
|
||||
}
|
||||
|
||||
// Add the plotter to the graph o/
|
||||
defaultGraph.addPlotter(plotter);
|
||||
|
||||
// Ensure the default graph is included in the submitted graphs
|
||||
graphs.add(defaultGraph);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start measuring statistics. This will immediately create an async repeating task as the plugin and send the
|
||||
* initial data to the metrics backend, and then after that it will post in increments of PING_INTERVAL * 1200
|
||||
* ticks.
|
||||
*
|
||||
* @return True if statistics measuring is running, otherwise false.
|
||||
*/
|
||||
public boolean start() {
|
||||
synchronized (optOutLock) {
|
||||
// Did we opt out?
|
||||
if (isOptOut()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Is metrics already running?
|
||||
if (task != null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Begin hitting the server with glorious data
|
||||
task = WrappedScheduler.runAsynchronouslyRepeat(plugin, new Runnable() {
|
||||
|
||||
private boolean firstPost = true;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
// This has to be synchronized or it can collide with the disable method.
|
||||
synchronized (optOutLock) {
|
||||
// Disable Task, if it is running and the server owner decided to opt-out
|
||||
if (isOptOut() && task != null) {
|
||||
task.cancel();
|
||||
task = null;
|
||||
// Tell all plotters to stop gathering information.
|
||||
for (Graph graph : graphs) {
|
||||
graph.onOptOut();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We use the inverse of firstPost because if it is the first time we are posting,
|
||||
// it is not a interval ping, so it evaluates to FALSE
|
||||
// Each time thereafter it will evaluate to TRUE, i.e PING!
|
||||
postPlugin(!firstPost);
|
||||
|
||||
// After the first post we set firstPost to false
|
||||
// Each post thereafter will be a ping
|
||||
firstPost = false;
|
||||
} catch (IOException e) {
|
||||
if (debug) {
|
||||
Bukkit.getLogger().log(Level.INFO, "[Metrics] " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 0, PING_INTERVAL * 1200);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Has the server owner denied plugin metrics?
|
||||
*
|
||||
* @return true if metrics should be opted out of it
|
||||
*/
|
||||
public boolean isOptOut() {
|
||||
synchronized (optOutLock) {
|
||||
try {
|
||||
// Reload the metrics file
|
||||
configuration.load(getConfigFile());
|
||||
} catch (IOException ex) {
|
||||
if (debug) {
|
||||
Bukkit.getLogger().log(Level.INFO, "[Metrics] " + ex.getMessage());
|
||||
}
|
||||
return true;
|
||||
} catch (InvalidConfigurationException ex) {
|
||||
if (debug) {
|
||||
Bukkit.getLogger().log(Level.INFO, "[Metrics] " + ex.getMessage());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return configuration.getBoolean("opt-out", false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables metrics for the server by setting "opt-out" to false in the config file and starting the metrics task.
|
||||
*
|
||||
* @throws java.io.IOException
|
||||
*/
|
||||
public void enable() throws IOException {
|
||||
// This has to be synchronized or it can collide with the check in the task.
|
||||
synchronized (optOutLock) {
|
||||
// Check if the server owner has already set opt-out, if not, set it.
|
||||
if (isOptOut()) {
|
||||
configuration.set("opt-out", false);
|
||||
configuration.save(configurationFile);
|
||||
}
|
||||
|
||||
// Enable Task, if it is not running
|
||||
if (task == null) {
|
||||
start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables metrics for the server by setting "opt-out" to true in the config file and canceling the metrics task.
|
||||
*
|
||||
* @throws java.io.IOException
|
||||
*/
|
||||
public void disable() throws IOException {
|
||||
// This has to be synchronized or it can collide with the check in the task.
|
||||
synchronized (optOutLock) {
|
||||
// Check if the server owner has already set opt-out, if not, set it.
|
||||
if (!isOptOut()) {
|
||||
configuration.set("opt-out", true);
|
||||
configuration.save(configurationFile);
|
||||
}
|
||||
|
||||
// Disable Task, if it is running
|
||||
if (task != null) {
|
||||
task.cancel();
|
||||
task = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the File object of the config file that should be used to store data such as the GUID and opt-out status
|
||||
*
|
||||
* @return the File object for the config file
|
||||
*/
|
||||
public File getConfigFile() {
|
||||
// I believe the easiest way to get the base folder (e.g craftbukkit set via -P) for plugins to use
|
||||
// is to abuse the plugin object we already have
|
||||
// plugin.getDataFolder() => base/plugins/PluginA/
|
||||
// pluginsFolder => base/plugins/
|
||||
// The base is not necessarily relative to the startup directory.
|
||||
File pluginsFolder = plugin.getDataFolder().getParentFile();
|
||||
|
||||
// return => base/plugins/PluginMetrics/config.yml
|
||||
return new File(new File(pluginsFolder, "PluginMetrics"), "config.yml");
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic method that posts a plugin to the metrics website
|
||||
*/
|
||||
private void postPlugin(final boolean isPing) throws IOException {
|
||||
// Server software specific section
|
||||
PluginDescriptionFile description = plugin.getDescription();
|
||||
String pluginName = description.getName();
|
||||
boolean onlineMode = Bukkit.getServer().getOnlineMode(); // TRUE if online mode is enabled
|
||||
String pluginVersion = description.getVersion();
|
||||
String serverVersion = Bukkit.getVersion();
|
||||
int playersOnline = BukkitUtil.getOnlinePlayers().size();
|
||||
|
||||
// END server software specific section -- all code below does not use any code outside of this class / Java
|
||||
|
||||
// Construct the post data
|
||||
final StringBuilder data = new StringBuilder();
|
||||
|
||||
// The plugin's description file containg all of the plugin data such as name, version, author, etc
|
||||
data.append(encode("guid")).append('=').append(encode(guid));
|
||||
encodeDataPair(data, "version", pluginVersion);
|
||||
encodeDataPair(data, "server", serverVersion);
|
||||
encodeDataPair(data, "players", Integer.toString(playersOnline));
|
||||
encodeDataPair(data, "revision", String.valueOf(REVISION));
|
||||
|
||||
// New data as of R6
|
||||
String osname = System.getProperty("os.name");
|
||||
String osarch = System.getProperty("os.arch");
|
||||
String osversion = System.getProperty("os.version");
|
||||
String java_version = System.getProperty("java.version");
|
||||
int coreCount = Runtime.getRuntime().availableProcessors();
|
||||
|
||||
// normalize os arch .. amd64 -> x86_64
|
||||
if (osarch.equals("amd64")) {
|
||||
osarch = "x86_64";
|
||||
}
|
||||
|
||||
encodeDataPair(data, "osname", osname);
|
||||
encodeDataPair(data, "osarch", osarch);
|
||||
encodeDataPair(data, "osversion", osversion);
|
||||
encodeDataPair(data, "cores", Integer.toString(coreCount));
|
||||
encodeDataPair(data, "online-mode", Boolean.toString(onlineMode));
|
||||
encodeDataPair(data, "java_version", java_version);
|
||||
|
||||
// If we're pinging, append it
|
||||
if (isPing) {
|
||||
encodeDataPair(data, "ping", "true");
|
||||
}
|
||||
|
||||
// Acquire a lock on the graphs, which lets us make the assumption we also lock everything
|
||||
// inside of the graph (e.g plotters)
|
||||
synchronized (graphs) {
|
||||
final Iterator<Graph> iter = graphs.iterator();
|
||||
|
||||
while (iter.hasNext()) {
|
||||
final Graph graph = iter.next();
|
||||
|
||||
for (Plotter plotter : graph.getPlotters()) {
|
||||
// The key name to send to the metrics server
|
||||
// The format is C-GRAPHNAME-PLOTTERNAME where separator - is defined at the top
|
||||
// Legacy (R4) submitters use the format Custom%s, or CustomPLOTTERNAME
|
||||
final String key = String.format("C%s%s%s%s", CUSTOM_DATA_SEPARATOR, graph.getName(), CUSTOM_DATA_SEPARATOR, plotter.getColumnName());
|
||||
|
||||
// The value to send, which for the foreseeable future is just the string
|
||||
// value of plotter.getValue()
|
||||
final String value = Integer.toString(plotter.getValue());
|
||||
|
||||
// Add it to the http post data :)
|
||||
encodeDataPair(data, key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create the url
|
||||
URL url = new URL(BASE_URL + String.format(REPORT_URL, encode(pluginName)));
|
||||
|
||||
// Connect to the website
|
||||
URLConnection connection;
|
||||
|
||||
// Mineshafter creates a socks proxy, so we can safely bypass it
|
||||
// It does not reroute POST requests so we need to go around it
|
||||
if (isMineshafterPresent()) {
|
||||
connection = url.openConnection(Proxy.NO_PROXY);
|
||||
} else {
|
||||
connection = url.openConnection();
|
||||
}
|
||||
|
||||
connection.setDoOutput(true);
|
||||
|
||||
// Write the data
|
||||
final OutputStreamWriter writer = new OutputStreamWriter(connection.getOutputStream());
|
||||
writer.write(data.toString());
|
||||
writer.flush();
|
||||
|
||||
// Now read the response
|
||||
final BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
|
||||
final String response = reader.readLine();
|
||||
|
||||
// close resources
|
||||
writer.close();
|
||||
reader.close();
|
||||
|
||||
if (response == null || response.startsWith("ERR")) {
|
||||
throw new IOException(response); //Throw the exception
|
||||
} else {
|
||||
// Is this the first update this hour?
|
||||
if (response.contains("OK This is your first update this hour")) {
|
||||
synchronized (graphs) {
|
||||
final Iterator<Graph> iter = graphs.iterator();
|
||||
|
||||
while (iter.hasNext()) {
|
||||
final Graph graph = iter.next();
|
||||
|
||||
for (Plotter plotter : graph.getPlotters()) {
|
||||
plotter.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if mineshafter is present. If it is, we need to bypass it to send POST requests
|
||||
*
|
||||
* @return true if mineshafter is installed on the server
|
||||
*/
|
||||
private boolean isMineshafterPresent() {
|
||||
try {
|
||||
Class.forName("mineshafter.MineServer");
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Encode a key/value data pair to be used in a HTTP post request. This INCLUDES a & so the first key/value pair
|
||||
* MUST be included manually, e.g:</p>
|
||||
* <code>
|
||||
* StringBuffer data = new StringBuffer();
|
||||
* data.append(encode("guid")).append('=').append(encode(guid));
|
||||
* encodeDataPair(data, "version", description.getVersion());
|
||||
* </code>
|
||||
*
|
||||
* @param buffer the stringbuilder to append the data pair onto
|
||||
* @param key the key value
|
||||
* @param value the value
|
||||
*/
|
||||
private static void encodeDataPair(final StringBuilder buffer, final String key, final String value) throws UnsupportedEncodingException {
|
||||
buffer.append('&').append(encode(key)).append('=').append(encode(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode text as UTF-8
|
||||
*
|
||||
* @param text the text to encode
|
||||
* @return the encoded text, as UTF-8
|
||||
*/
|
||||
private static String encode(final String text) throws UnsupportedEncodingException {
|
||||
return URLEncoder.encode(text, "UTF-8");
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a custom graph on the website
|
||||
*/
|
||||
public static class Graph {
|
||||
|
||||
/**
|
||||
* The graph's name, alphanumeric and spaces only :) If it does not comply to the above when submitted, it is
|
||||
* rejected
|
||||
*/
|
||||
private final String name;
|
||||
/**
|
||||
* The set of plotters that are contained within this graph
|
||||
*/
|
||||
private final Set<Plotter> plotters = new LinkedHashSet<Plotter>();
|
||||
|
||||
private Graph(final String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the graph's name
|
||||
*
|
||||
* @return the Graph's name
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a plotter to the graph, which will be used to plot entries
|
||||
*
|
||||
* @param plotter the plotter to add to the graph
|
||||
*/
|
||||
public void addPlotter(final Plotter plotter) {
|
||||
plotters.add(plotter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a plotter from the graph
|
||||
*
|
||||
* @param plotter the plotter to remove from the graph
|
||||
*/
|
||||
public void removePlotter(final Plotter plotter) {
|
||||
plotters.remove(plotter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an <b>unmodifiable</b> set of the plotter objects in the graph
|
||||
*
|
||||
* @return an unmodifiable {@link java.util.Set} of the plotter objects
|
||||
*/
|
||||
public Set<Plotter> getPlotters() {
|
||||
return Collections.unmodifiableSet(plotters);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return name.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object object) {
|
||||
if (!(object instanceof Graph)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final Graph graph = (Graph) object;
|
||||
return graph.name.equals(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the server owner decides to opt-out of BukkitMetrics while the server is running.
|
||||
*/
|
||||
protected void onOptOut() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface used to collect custom data for a plugin
|
||||
*/
|
||||
public static abstract class Plotter {
|
||||
|
||||
/**
|
||||
* The plot's name
|
||||
*/
|
||||
private final String name;
|
||||
|
||||
/**
|
||||
* Construct a plotter with the default plot name
|
||||
*/
|
||||
public Plotter() {
|
||||
this("Default");
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a plotter with a specific plot name
|
||||
*
|
||||
* @param name the name of the plotter to use, which will show up on the website
|
||||
*/
|
||||
public Plotter(final String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current value for the plotted point. Since this function defers to an external function it may or may
|
||||
* not return immediately thus cannot be guaranteed to be thread friendly or safe. This function can be called
|
||||
* from any thread so care should be taken when accessing resources that need to be synchronized.
|
||||
*
|
||||
* @return the current value for the point to be plotted.
|
||||
*/
|
||||
public abstract int getValue();
|
||||
|
||||
/**
|
||||
* Get the column name for the plotted point
|
||||
*
|
||||
* @return the plotted point's column name
|
||||
*/
|
||||
public String getColumnName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called after the website graphs have been updated
|
||||
*/
|
||||
public void reset() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return getColumnName().hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object object) {
|
||||
if (!(object instanceof Plotter)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final Plotter plotter = (Plotter) object;
|
||||
return plotter.name.equals(name) && plotter.getValue() == getValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol.metrics;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import com.comphenix.protocol.ProtocolLibrary;
|
||||
import com.comphenix.protocol.ProtocolManager;
|
||||
import com.comphenix.protocol.events.PacketAdapter;
|
||||
import com.comphenix.protocol.events.PacketListener;
|
||||
import com.comphenix.protocol.metrics.Metrics.Graph;
|
||||
|
||||
public class Statistics {
|
||||
|
||||
// Metrics
|
||||
private Metrics metrics;
|
||||
|
||||
public Statistics(Plugin plugin) throws IOException {
|
||||
metrics = new Metrics(plugin);
|
||||
|
||||
// Determine who is using this library
|
||||
addPluginUserGraph(metrics);
|
||||
|
||||
metrics.start();
|
||||
}
|
||||
|
||||
private void addPluginUserGraph(Metrics metrics) {
|
||||
|
||||
Graph pluginUsers = metrics.createGraph("Plugin Users");
|
||||
|
||||
for (Map.Entry<String, Integer> entry : getPluginUsers(ProtocolLibrary.getProtocolManager()).entrySet()) {
|
||||
final int count = entry.getValue();
|
||||
|
||||
// Plot plugins of this type
|
||||
pluginUsers.addPlotter(new Metrics.Plotter(entry.getKey()) {
|
||||
@Override
|
||||
public int getValue() {
|
||||
return count;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Retrieve loaded plugins
|
||||
private Map<String, Integer> getPluginUsers(ProtocolManager manager) {
|
||||
|
||||
Map<String, Integer> users = new HashMap<String, Integer>();
|
||||
|
||||
for (PacketListener listener : manager.getPacketListeners()) {
|
||||
|
||||
String name = PacketAdapter.getPluginName(listener);
|
||||
|
||||
// Increment occurence
|
||||
if (!users.containsKey(name)) {
|
||||
users.put(name, 1);
|
||||
} else {
|
||||
users.put(name, users.get(name) + 1);
|
||||
}
|
||||
}
|
||||
|
||||
return users;
|
||||
}
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol.reflect.cloning;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.comphenix.protocol.reflect.EquivalentConverter;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
import com.comphenix.protocol.wrappers.BukkitConverters;
|
||||
import com.comphenix.protocol.wrappers.ChunkPosition;
|
||||
import com.comphenix.protocol.wrappers.WrappedDataWatcher;
|
||||
import com.comphenix.protocol.wrappers.WrappedServerPing;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
/**
|
||||
* Represents an object that can clone a specific list of Bukkit- and Minecraft-related objects.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public class BukkitCloner implements Cloner {
|
||||
// List of classes we support
|
||||
private Class<?>[] clonableClasses;
|
||||
|
||||
public BukkitCloner() {
|
||||
List<Class<?>> classes = Lists.newArrayList();
|
||||
|
||||
classes.add(MinecraftReflection.getItemStackClass());
|
||||
classes.add(MinecraftReflection.getChunkPositionClass());
|
||||
classes.add(MinecraftReflection.getDataWatcherClass());
|
||||
|
||||
if (MinecraftReflection.isUsingNetty()) {
|
||||
classes.add(MinecraftReflection.getServerPingClass());
|
||||
}
|
||||
this.clonableClasses = classes.toArray(new Class<?>[0]);
|
||||
}
|
||||
|
||||
private int findMatchingClass(Class<?> type) {
|
||||
// See if is a subclass of any of our supported superclasses
|
||||
for (int i = 0; i < clonableClasses.length; i++) {
|
||||
if (clonableClasses[i].isAssignableFrom(type))
|
||||
return i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canClone(Object source) {
|
||||
if (source == null)
|
||||
return false;
|
||||
|
||||
return findMatchingClass(source.getClass()) >= 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object clone(Object source) {
|
||||
if (source == null)
|
||||
throw new IllegalArgumentException("source cannot be NULL.");
|
||||
|
||||
// Convert to a wrapper
|
||||
switch (findMatchingClass(source.getClass())) {
|
||||
case 0:
|
||||
return MinecraftReflection.getMinecraftItemStack(MinecraftReflection.getBukkitItemStack(source).clone());
|
||||
case 1:
|
||||
EquivalentConverter<ChunkPosition> chunkConverter = ChunkPosition.getConverter();
|
||||
return chunkConverter.getGeneric(clonableClasses[1], chunkConverter.getSpecific(source));
|
||||
case 2:
|
||||
EquivalentConverter<WrappedDataWatcher> dataConverter = BukkitConverters.getDataWatcherConverter();
|
||||
return dataConverter.getGeneric(clonableClasses[2], dataConverter.getSpecific(source).deepClone());
|
||||
case 3:
|
||||
EquivalentConverter<WrappedServerPing> serverConverter = BukkitConverters.getWrappedServerPingConverter();
|
||||
return serverConverter.getGeneric(clonableClasses[3], serverConverter.getSpecific(source).deepClone());
|
||||
default:
|
||||
throw new IllegalArgumentException("Cannot clone objects of type " + source.getClass());
|
||||
}
|
||||
}
|
||||
}
|
||||
-57
@@ -1,57 +0,0 @@
|
||||
package com.comphenix.protocol.reflect.compiler;
|
||||
|
||||
import net.sf.cglib.asm.AnnotationVisitor;
|
||||
import net.sf.cglib.asm.Attribute;
|
||||
import net.sf.cglib.asm.ClassVisitor;
|
||||
import net.sf.cglib.asm.FieldVisitor;
|
||||
import net.sf.cglib.asm.MethodVisitor;
|
||||
|
||||
public abstract class EmptyClassVisitor implements ClassVisitor {
|
||||
@Override
|
||||
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
|
||||
// NOP
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
|
||||
// NOP
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitAttribute(Attribute attr) {
|
||||
// NOP
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitEnd() {
|
||||
// NOP
|
||||
}
|
||||
|
||||
@Override
|
||||
public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
|
||||
// NOP
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitInnerClass(String name, String outerName, String innerName, int access) {
|
||||
// NOP
|
||||
}
|
||||
|
||||
@Override
|
||||
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
|
||||
// NOP
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitOuterClass(String owner, String name, String desc) {
|
||||
// NOP
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitSource(String source, String debug) {
|
||||
// NOP
|
||||
}
|
||||
}
|
||||
-132
@@ -1,132 +0,0 @@
|
||||
package com.comphenix.protocol.reflect.compiler;
|
||||
|
||||
import net.sf.cglib.asm.AnnotationVisitor;
|
||||
import net.sf.cglib.asm.Attribute;
|
||||
import net.sf.cglib.asm.Label;
|
||||
import net.sf.cglib.asm.MethodVisitor;
|
||||
|
||||
public class EmptyMethodVisitor implements MethodVisitor {
|
||||
@Override
|
||||
public AnnotationVisitor visitAnnotationDefault() {
|
||||
// NOP
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
|
||||
// NOP
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible) {
|
||||
// NOP
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitAttribute(Attribute attr) {
|
||||
// NOP
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitCode() {
|
||||
// NOP
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack) {
|
||||
// NOP
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitInsn(int opcode) {
|
||||
// NOP
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitIntInsn(int opcode, int operand) {
|
||||
// NOP
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitVarInsn(int opcode, int var) {
|
||||
// NOP
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitTypeInsn(int opcode, String type) {
|
||||
// NOP
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitFieldInsn(int opcode, String owner, String name, String desc) {
|
||||
// NOP
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitMethodInsn(int opcode, String owner, String name, String desc) {
|
||||
// NOP
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitJumpInsn(int opcode, Label label) {
|
||||
// NOP
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitLabel(Label label) {
|
||||
// NOP
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitLdcInsn(Object cst) {
|
||||
// NOP
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitIincInsn(int var, int increment) {
|
||||
// NOP
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitTableSwitchInsn(int min, int max, Label dflt, Label[] labels) {
|
||||
// NOP
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {
|
||||
// NOP
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitMultiANewArrayInsn(String desc, int dims) {
|
||||
// NOP
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
|
||||
// NOP
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitLocalVariable(String name, String desc, String signature, Label start,
|
||||
Label end, int index) {
|
||||
// NOP
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitLineNumber(int line, Label start) {
|
||||
// NOP
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitMaxs(int maxStack, int maxLocals) {
|
||||
// NOP
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitEnd() {
|
||||
// NOP
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
package com.comphenix.protocol.utility;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
/**
|
||||
* Utility methods relating to Bukkit.
|
||||
* @author dmulloy2
|
||||
*/
|
||||
|
||||
public class BukkitUtil {
|
||||
|
||||
private static Method getOnlinePlayers;
|
||||
|
||||
/**
|
||||
* Gets a list of online {@link Player}s. This also provides backwards
|
||||
* compatibility as Bukkit changed <code>getOnlinePlayers</code>.
|
||||
*
|
||||
* @return A list of online Players
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static List<Player> getOnlinePlayers() {
|
||||
try {
|
||||
if (getOnlinePlayers == null)
|
||||
getOnlinePlayers = Bukkit.class.getMethod("getOnlinePlayers");
|
||||
if (getOnlinePlayers.getReturnType() != Collection.class)
|
||||
return Arrays.asList((Player[]) getOnlinePlayers.invoke(null));
|
||||
} catch (Throwable ex) {
|
||||
}
|
||||
return (List<Player>) Bukkit.getOnlinePlayers();
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
package com.comphenix.protocol.utility;
|
||||
|
||||
import net.sf.cglib.proxy.Enhancer;
|
||||
|
||||
/**
|
||||
* Represents a shared enchancer factory.
|
||||
* @author Kristian
|
||||
*/
|
||||
public class EnhancerFactory {
|
||||
private static EnhancerFactory INSTANCE = new EnhancerFactory();
|
||||
|
||||
// The current class loader
|
||||
private ClassLoader loader = EnhancerFactory.class.getClassLoader();
|
||||
|
||||
public static EnhancerFactory getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new CGLib enhancer.
|
||||
* @return The new enhancer.
|
||||
*/
|
||||
public Enhancer createEnhancer() {
|
||||
Enhancer enhancer = new Enhancer();
|
||||
enhancer.setClassLoader(loader);
|
||||
return enhancer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current class loader to use when constructing enhancers.
|
||||
* @param loader - the class loader
|
||||
*/
|
||||
public void setClassLoader(ClassLoader loader) {
|
||||
this.loader = loader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current class loader we are using.
|
||||
* @return The current class loader.
|
||||
*/
|
||||
public ClassLoader getClassLoader() {
|
||||
return loader;
|
||||
}
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
package com.comphenix.protocol.utility;
|
||||
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.bukkit.scheduler.BukkitScheduler;
|
||||
import org.bukkit.scheduler.BukkitTask;
|
||||
|
||||
/**
|
||||
* Allows us to stay backwards compatible with older versions of Bukkit.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public class WrappedScheduler {
|
||||
/**
|
||||
* Represents a backwards compatible Bukkit task.
|
||||
*/
|
||||
public static interface TaskWrapper {
|
||||
/**
|
||||
* Cancel the current task.
|
||||
*/
|
||||
public void cancel();
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule a given task for a single asynchronous execution.
|
||||
* @param plugin - the owner plugin.
|
||||
* @param runnable - the task to run.
|
||||
* @param firstDelay - the amount of time to wait until executing the task.
|
||||
* @return A cancel token.
|
||||
*/
|
||||
public static TaskWrapper runAsynchronouslyOnce(final Plugin plugin, Runnable runnable, long firstDelay) {
|
||||
return runAsynchronouslyRepeat(plugin, plugin.getServer().getScheduler(), runnable, firstDelay, -1L);
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule a given task for multiple asynchronous executions.
|
||||
* @param plugin - the owner plugin.
|
||||
* @param runnable - the task to run.
|
||||
* @param firstDelay - the amount of time to wait until executing the task for the first time.
|
||||
* @param repeatDelay - the amount of time inbetween each execution. If less than zero, the task is only executed once.
|
||||
* @return A cancel token.
|
||||
*/
|
||||
public static TaskWrapper runAsynchronouslyRepeat(final Plugin plugin, Runnable runnable, long firstDelay, long repeatDelay) {
|
||||
return runAsynchronouslyRepeat(plugin, plugin.getServer().getScheduler(), runnable, firstDelay, repeatDelay);
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule a given task for asynchronous execution.
|
||||
* @param plugin - the owner plugin.
|
||||
* @param scheduler - the current Bukkit scheduler.
|
||||
* @param runnable - the task to run.
|
||||
* @param firstDelay - the amount of time to wait until executing the task for the first time.
|
||||
* @param repeatDelay - the amount of time inbetween each execution. If less than zero, the task is only executed once.
|
||||
* @return A cancel token.
|
||||
*/
|
||||
public static TaskWrapper runAsynchronouslyRepeat(final Plugin plugin, final BukkitScheduler scheduler, Runnable runnable, long firstDelay, long repeatDelay) {
|
||||
try {
|
||||
@SuppressWarnings("deprecation")
|
||||
final int taskID = scheduler.scheduleAsyncRepeatingTask(plugin, runnable, firstDelay, repeatDelay);
|
||||
|
||||
// Return the cancellable object
|
||||
return new TaskWrapper() {
|
||||
@Override
|
||||
public void cancel() {
|
||||
scheduler.cancelTask(taskID);
|
||||
}
|
||||
};
|
||||
|
||||
} catch (NoSuchMethodError e) {
|
||||
return tryUpdatedVersion(plugin, scheduler, runnable, firstDelay, repeatDelay);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to do the same with the updated scheduling method.
|
||||
* @param plugin - the owner plugin.
|
||||
* @param scheduler - the current Bukkit scheduler.
|
||||
* @param runnable - the task to run.
|
||||
* @param firstDelay - the amount of time to wait until executing the task for the first time.
|
||||
* @param repeatDelay - the amount of time inbetween each execution. If less than zero, the task is only executed once.
|
||||
* @return A cancel token.
|
||||
*/
|
||||
private static TaskWrapper tryUpdatedVersion(final Plugin plugin, final BukkitScheduler scheduler, Runnable runnable, long firstDelay, long repeatDelay) {
|
||||
final BukkitTask task = scheduler.runTaskTimerAsynchronously(plugin, runnable, firstDelay, repeatDelay);
|
||||
|
||||
return new TaskWrapper() {
|
||||
@Override
|
||||
public void cancel() {
|
||||
task.cancel();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,947 +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.wrappers;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.lang.reflect.Array;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.WorldType;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.potion.PotionEffect;
|
||||
import org.bukkit.potion.PotionEffectType;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.ProtocolLibrary;
|
||||
import com.comphenix.protocol.ProtocolManager;
|
||||
import com.comphenix.protocol.injector.BukkitUnwrapper;
|
||||
import com.comphenix.protocol.injector.PacketConstructor;
|
||||
import com.comphenix.protocol.injector.PacketConstructor.Unwrapper;
|
||||
import com.comphenix.protocol.reflect.EquivalentConverter;
|
||||
import com.comphenix.protocol.reflect.FieldAccessException;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.comphenix.protocol.reflect.StructureModifier;
|
||||
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.reflect.fuzzy.FuzzyMethodContract;
|
||||
import com.comphenix.protocol.reflect.instances.DefaultInstances;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
import com.comphenix.protocol.wrappers.nbt.NbtBase;
|
||||
import com.comphenix.protocol.wrappers.nbt.NbtCompound;
|
||||
import com.comphenix.protocol.wrappers.nbt.NbtFactory;
|
||||
import com.google.common.base.Objects;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
/**
|
||||
* Contains several useful equivalent converters for normal Bukkit types.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public class BukkitConverters {
|
||||
// Check whether or not certain classes exists
|
||||
private static boolean hasWorldType = false;
|
||||
private static boolean hasAttributeSnapshot = false;
|
||||
|
||||
// The static maps
|
||||
private static Map<Class<?>, EquivalentConverter<Object>> specificConverters;
|
||||
private static Map<Class<?>, EquivalentConverter<Object>> genericConverters;
|
||||
private static List<Unwrapper> unwrappers;
|
||||
|
||||
// Used to access the world type
|
||||
private static Method worldTypeName;
|
||||
private static Method worldTypeGetType;
|
||||
|
||||
// Used to get block instances
|
||||
private static MethodAccessor GET_BLOCK;
|
||||
private static MethodAccessor GET_BLOCK_ID;
|
||||
|
||||
// Used for potion effect conversion
|
||||
private static volatile Constructor<?> mobEffectConstructor;
|
||||
private static volatile StructureModifier<Object> mobEffectModifier;
|
||||
|
||||
// Used for fetching the CraftWorld associated with a WorldServer
|
||||
private static FieldAccessor craftWorldField;
|
||||
|
||||
static {
|
||||
try {
|
||||
MinecraftReflection.getWorldTypeClass();
|
||||
hasWorldType = true;
|
||||
} catch (Exception e) {
|
||||
}
|
||||
|
||||
try {
|
||||
MinecraftReflection.getAttributeSnapshotClass();
|
||||
hasAttributeSnapshot = true;
|
||||
} catch (Exception e) {
|
||||
}
|
||||
|
||||
// Fetch CraftWorld field
|
||||
try {
|
||||
craftWorldField = Accessors.getFieldAccessor(
|
||||
MinecraftReflection.getNmsWorldClass(),
|
||||
MinecraftReflection.getCraftWorldClass(), true);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a typical equivalence converter.
|
||||
*
|
||||
* @author Kristian
|
||||
* @param <T> - type that can be converted.
|
||||
*/
|
||||
public static abstract class IgnoreNullConverter<TType> implements EquivalentConverter<TType> {
|
||||
@Override
|
||||
public final Object getGeneric(Class<?> genericType, TType specific) {
|
||||
if (specific != null)
|
||||
return getGenericValue(genericType, specific);
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a copy of the actual generic value.
|
||||
* @param genericType - generic type.
|
||||
* @param specific - the specific type-
|
||||
* @return A copy of the specific type.
|
||||
*/
|
||||
protected abstract Object getGenericValue(Class<?> genericType, TType specific);
|
||||
|
||||
@Override
|
||||
public final TType getSpecific(Object generic) {
|
||||
if (generic != null)
|
||||
return getSpecificValue(generic);
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a copy of the specific type using an instance of the generic type.
|
||||
* @param generic - generic type.
|
||||
* @return A copy of the specific type.
|
||||
*/
|
||||
protected abstract TType getSpecificValue(Object generic);
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
|
||||
if (obj instanceof EquivalentConverter) {
|
||||
EquivalentConverter<?> that = (EquivalentConverter<?>) obj;
|
||||
return Objects.equal(this.getSpecificType(), that.getSpecificType());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hashCode(this.getSpecificType());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a converter that is only valid in a given world.
|
||||
*
|
||||
* @author Kristian
|
||||
* @param <TType> - instance types it converts.
|
||||
*/
|
||||
private static abstract class WorldSpecificConverter<TType> extends IgnoreNullConverter<TType> {
|
||||
protected World world;
|
||||
|
||||
/**
|
||||
* Initialize a new world-specificn converter.
|
||||
* @param world - the given world.
|
||||
*/
|
||||
public WorldSpecificConverter(World world) {
|
||||
super();
|
||||
this.world = world;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == this) return true;
|
||||
|
||||
// Add another constraint
|
||||
if (obj instanceof WorldSpecificConverter && super.equals(obj)) {
|
||||
WorldSpecificConverter<?> that = (WorldSpecificConverter<?>) obj;
|
||||
return Objects.equal(this.world, that.world);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hashCode(this.getSpecificType(), this.world);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve an equivalent converter for a map of generic keys and primitive values.
|
||||
* @param genericItemType - the generic item type.
|
||||
* @param itemConverter - an equivalent converter for the generic type.
|
||||
* @return An equivalent converter.
|
||||
*/
|
||||
public static <T, U> EquivalentConverter<Map<T, U>> getMapConverter(
|
||||
final Class<?> genericKeyType, final EquivalentConverter<T> keyConverter) {
|
||||
// Convert to and from the wrapper
|
||||
return new IgnoreNullConverter<Map<T, U>>() {
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
protected Map<T, U> getSpecificValue(Object generic) {
|
||||
if (generic instanceof Map) {
|
||||
Map<T, U> result = Maps.newHashMap();
|
||||
|
||||
// Copy everything to a new list
|
||||
for (Entry<Object, Object> entry : ((Map<Object, Object>) generic).entrySet()) {
|
||||
result.put(
|
||||
keyConverter.getSpecific(entry.getKey()),
|
||||
(U) entry.getValue()
|
||||
);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Not valid
|
||||
return null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
protected Object getGenericValue(Class<?> genericType, Map<T, U> specific) {
|
||||
Map<Object, Object> newContainer = (Map<Object, Object>) DefaultInstances.DEFAULT.getDefault(genericType);
|
||||
|
||||
// Convert each object
|
||||
for (Entry<T, U> entry : specific.entrySet()) {
|
||||
newContainer.put(
|
||||
keyConverter.getGeneric(genericKeyType, entry.getKey()),
|
||||
entry.getValue()
|
||||
);
|
||||
}
|
||||
return newContainer;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Class<Map<T, U>> getSpecificType() {
|
||||
Class<?> dummy = Map.class;
|
||||
return (Class<Map<T, U>>) dummy;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve an equivalent converter for a list of generic items.
|
||||
* @param genericItemType - the generic item type.
|
||||
* @param itemConverter - an equivalent converter for the generic type.
|
||||
* @return An equivalent converter.
|
||||
*/
|
||||
public static <T> EquivalentConverter<List<T>> getListConverter(final Class<?> genericItemType, final EquivalentConverter<T> itemConverter) {
|
||||
// Convert to and from the wrapper
|
||||
return new IgnoreNullConverter<List<T>>() {
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
protected List<T> getSpecificValue(Object generic) {
|
||||
if (generic instanceof Collection) {
|
||||
List<T> items = new ArrayList<T>();
|
||||
|
||||
// Copy everything to a new list
|
||||
for (Object item : (Collection<Object>) generic) {
|
||||
T result = itemConverter.getSpecific(item);
|
||||
|
||||
if (item != null)
|
||||
items.add(result);
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
// Not valid
|
||||
return null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
protected Object getGenericValue(Class<?> genericType, List<T> specific) {
|
||||
Collection<Object> newContainer = (Collection<Object>) DefaultInstances.DEFAULT.getDefault(genericType);
|
||||
|
||||
// Convert each object
|
||||
for (T position : specific) {
|
||||
Object converted = itemConverter.getGeneric(genericItemType, position);
|
||||
|
||||
if (position == null)
|
||||
newContainer.add(null);
|
||||
else if (converted != null)
|
||||
newContainer.add(converted);
|
||||
}
|
||||
return newContainer;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Class<List<T>> getSpecificType() {
|
||||
// Damn you Java
|
||||
Class<?> dummy = List.class;
|
||||
return (Class<List<T>>) dummy;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve an equivalent converter for an array of generic items.
|
||||
* <p>
|
||||
* The array is wrapped in a list.
|
||||
* @param genericItemType - the generic item type.
|
||||
* @param itemConverter - an equivalent converter for the generic type.
|
||||
* @return An equivalent converter.
|
||||
*/
|
||||
public static <T> EquivalentConverter<Iterable<? extends T>> getArrayConverter(
|
||||
final Class<?> genericItemType, final EquivalentConverter<T> itemConverter) {
|
||||
// Convert to and from the wrapper
|
||||
return new IgnoreNullConverter<Iterable<? extends T>>() {
|
||||
@Override
|
||||
protected List<T> getSpecificValue(Object generic) {
|
||||
if (generic instanceof Object[]) {
|
||||
ImmutableList.Builder<T> builder = ImmutableList.builder();
|
||||
|
||||
// Copy everything to a new list
|
||||
for (Object item : (Object[]) generic) {
|
||||
T result = itemConverter.getSpecific(item);
|
||||
builder.add(result);
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
// Not valid
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object getGenericValue(Class<?> genericType, Iterable<? extends T> specific) {
|
||||
List<T> list = Lists.newArrayList(specific);
|
||||
Object[] output = (Object[]) Array.newInstance(genericItemType, list.size());
|
||||
|
||||
// Convert each object
|
||||
for (int i = 0; i < output.length; i++) {
|
||||
Object converted = itemConverter.getGeneric(genericItemType, list.get(i));
|
||||
output[i] = converted;
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Class<Iterable<? extends T>> getSpecificType() {
|
||||
// Damn you Java
|
||||
Class<?> dummy = Iterable.class;
|
||||
return (Class<Iterable<? extends T>>) dummy;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a converter for wrapped attribute snapshots.
|
||||
* @return Wrapped attribute snapshot converter.
|
||||
*/
|
||||
public static EquivalentConverter<WrappedGameProfile> getWrappedGameProfileConverter() {
|
||||
return new IgnoreNullConverter<WrappedGameProfile>() {
|
||||
@Override
|
||||
protected Object getGenericValue(Class<?> genericType, WrappedGameProfile specific) {
|
||||
return specific.getHandle();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected WrappedGameProfile getSpecificValue(Object generic) {
|
||||
return WrappedGameProfile.fromHandle(generic);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<WrappedGameProfile> getSpecificType() {
|
||||
return WrappedGameProfile.class;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a converter for wrapped chat components.
|
||||
* @return Wrapped chat componetns.
|
||||
*/
|
||||
public static EquivalentConverter<WrappedChatComponent> getWrappedChatComponentConverter() {
|
||||
return new IgnoreNullConverter<WrappedChatComponent>() {
|
||||
@Override
|
||||
protected Object getGenericValue(Class<?> genericType, WrappedChatComponent specific) {
|
||||
return specific.getHandle();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected WrappedChatComponent getSpecificValue(Object generic) {
|
||||
return WrappedChatComponent.fromHandle(generic);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<WrappedChatComponent> getSpecificType() {
|
||||
return WrappedChatComponent.class;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a converter for wrapped attribute snapshots.
|
||||
* @return Wrapped attribute snapshot converter.
|
||||
*/
|
||||
public static EquivalentConverter<WrappedAttribute> getWrappedAttributeConverter() {
|
||||
return new IgnoreNullConverter<WrappedAttribute>() {
|
||||
@Override
|
||||
protected Object getGenericValue(Class<?> genericType, WrappedAttribute specific) {
|
||||
return specific.getHandle();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected WrappedAttribute getSpecificValue(Object generic) {
|
||||
return WrappedAttribute.fromHandle(generic);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<WrappedAttribute> getSpecificType() {
|
||||
return WrappedAttribute.class;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a converter for watchable objects and the respective wrapper.
|
||||
* @return A watchable object converter.
|
||||
*/
|
||||
public static EquivalentConverter<WrappedWatchableObject> getWatchableObjectConverter() {
|
||||
return new IgnoreNullConverter<WrappedWatchableObject>() {
|
||||
@Override
|
||||
protected Object getGenericValue(Class<?> genericType, WrappedWatchableObject specific) {
|
||||
return specific.getHandle();
|
||||
}
|
||||
|
||||
protected WrappedWatchableObject getSpecificValue(Object generic) {
|
||||
if (MinecraftReflection.isWatchableObject(generic))
|
||||
return new WrappedWatchableObject(generic);
|
||||
else if (generic instanceof WrappedWatchableObject)
|
||||
return (WrappedWatchableObject) generic;
|
||||
else
|
||||
throw new IllegalArgumentException("Unrecognized type " + generic.getClass());
|
||||
};
|
||||
|
||||
@Override
|
||||
public Class<WrappedWatchableObject> getSpecificType() {
|
||||
return WrappedWatchableObject.class;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a converter for the NMS DataWatcher class and our wrapper.
|
||||
* @return A DataWatcher converter.
|
||||
*/
|
||||
public static EquivalentConverter<WrappedDataWatcher> getDataWatcherConverter() {
|
||||
return new IgnoreNullConverter<WrappedDataWatcher>() {
|
||||
@Override
|
||||
protected Object getGenericValue(Class<?> genericType, WrappedDataWatcher specific) {
|
||||
return specific.getHandle();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected WrappedDataWatcher getSpecificValue(Object generic) {
|
||||
if (MinecraftReflection.isDataWatcher(generic))
|
||||
return new WrappedDataWatcher(generic);
|
||||
else if (generic instanceof WrappedDataWatcher)
|
||||
return (WrappedDataWatcher) generic;
|
||||
else
|
||||
throw new IllegalArgumentException("Unrecognized type " + generic.getClass());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<WrappedDataWatcher> getSpecificType() {
|
||||
return WrappedDataWatcher.class;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a converter for Bukkit's world type enum and the NMS equivalent.
|
||||
* @return A world type enum converter.
|
||||
*/
|
||||
public static EquivalentConverter<WorldType> getWorldTypeConverter() {
|
||||
// Check that we can actually use this converter
|
||||
if (!hasWorldType)
|
||||
return null;
|
||||
|
||||
final Class<?> worldType = MinecraftReflection.getWorldTypeClass();
|
||||
|
||||
return new IgnoreNullConverter<WorldType>() {
|
||||
@Override
|
||||
protected Object getGenericValue(Class<?> genericType, WorldType specific) {
|
||||
try {
|
||||
// Deduce getType method by parameters alone
|
||||
if (worldTypeGetType == null) {
|
||||
worldTypeGetType = FuzzyReflection.fromClass(worldType).
|
||||
getMethodByParameters("getType", worldType, new Class<?>[] { String.class });
|
||||
}
|
||||
|
||||
// Convert to the Bukkit world type
|
||||
return worldTypeGetType.invoke(this, specific.getName());
|
||||
|
||||
} catch (Exception e) {
|
||||
throw new FieldAccessException("Cannot find the WorldType.getType() method.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected WorldType getSpecificValue(Object generic) {
|
||||
try {
|
||||
if (worldTypeName == null) {
|
||||
try {
|
||||
worldTypeName = worldType.getMethod("name");
|
||||
} catch (Exception e) {
|
||||
// Assume the first method is the one
|
||||
worldTypeName = FuzzyReflection.fromClass(worldType).
|
||||
getMethodByParameters("name", String.class, new Class<?>[] {});
|
||||
}
|
||||
}
|
||||
|
||||
// Dynamically call the namne method
|
||||
String name = (String) worldTypeName.invoke(generic);
|
||||
return WorldType.getByName(name);
|
||||
|
||||
} catch (Exception e) {
|
||||
throw new FieldAccessException("Cannot call the name method in WorldType.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<WorldType> getSpecificType() {
|
||||
return WorldType.class;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve an equivalent converter for net.minecraft.server NBT classes and their wrappers.
|
||||
* @return An equivalent converter for NBT.
|
||||
*/
|
||||
public static EquivalentConverter<NbtBase<?>> getNbtConverter() {
|
||||
return new IgnoreNullConverter<NbtBase<?>>() {
|
||||
@Override
|
||||
protected Object getGenericValue(Class<?> genericType, NbtBase<?> specific) {
|
||||
return NbtFactory.fromBase(specific).getHandle();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected NbtBase<?> getSpecificValue(Object generic) {
|
||||
return NbtFactory.fromNMS(generic, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public Class<NbtBase<?>> getSpecificType() {
|
||||
// Damn you Java AGAIN
|
||||
Class<?> dummy = NbtBase.class;
|
||||
return (Class<NbtBase<?>>) dummy;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a converter for NMS entities and Bukkit entities.
|
||||
* @param world - the current world.
|
||||
* @return A converter between the underlying NMS entity and Bukkit's wrapper.
|
||||
*/
|
||||
public static EquivalentConverter<Entity> getEntityConverter(World world) {
|
||||
final WeakReference<ProtocolManager> managerRef =
|
||||
new WeakReference<ProtocolManager>(ProtocolLibrary.getProtocolManager());
|
||||
|
||||
return new WorldSpecificConverter<Entity>(world) {
|
||||
@Override
|
||||
public Object getGenericValue(Class<?> genericType, Entity specific) {
|
||||
// Simple enough
|
||||
return specific.getEntityId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Entity getSpecificValue(Object generic) {
|
||||
try {
|
||||
Integer id = (Integer) generic;
|
||||
ProtocolManager manager = managerRef.get();
|
||||
|
||||
// Use the entity ID to get a reference to the entity
|
||||
if (id != null && manager != null) {
|
||||
return manager.getEntityFromID(world, id);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
} catch (FieldAccessException e) {
|
||||
throw new RuntimeException("Cannot retrieve entity from ID.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<Entity> getSpecificType() {
|
||||
return Entity.class;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the converter used to convert NMS ItemStacks to Bukkit's ItemStack.
|
||||
* @return Item stack converter.
|
||||
*/
|
||||
public static EquivalentConverter<ItemStack> getItemStackConverter() {
|
||||
return new IgnoreNullConverter<ItemStack>() {
|
||||
@Override
|
||||
protected Object getGenericValue(Class<?> genericType, ItemStack specific) {
|
||||
return MinecraftReflection.getMinecraftItemStack(specific);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ItemStack getSpecificValue(Object generic) {
|
||||
return MinecraftReflection.getBukkitItemStack(generic);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<ItemStack> getSpecificType() {
|
||||
return ItemStack.class;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the converter for the ServerPing packet in {@link PacketType.Status.Server#OUT_SERVER_INFO}.
|
||||
* @return Server ping converter.
|
||||
*/
|
||||
public static EquivalentConverter<WrappedServerPing> getWrappedServerPingConverter() {
|
||||
return new IgnoreNullConverter<WrappedServerPing>() {
|
||||
@Override
|
||||
protected Object getGenericValue(Class<?> genericType, WrappedServerPing specific) {
|
||||
return specific.getHandle();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected WrappedServerPing getSpecificValue(Object generic) {
|
||||
return WrappedServerPing.fromHandle(generic);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<WrappedServerPing> getSpecificType() {
|
||||
return WrappedServerPing.class;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the converter for a statistic.
|
||||
* @return Statistic converter.
|
||||
*/
|
||||
public static EquivalentConverter<WrappedStatistic> getWrappedStatisticConverter() {
|
||||
return new IgnoreNullConverter<WrappedStatistic>() {
|
||||
@Override
|
||||
protected Object getGenericValue(Class<?> genericType, WrappedStatistic specific) {
|
||||
return specific.getHandle();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected WrappedStatistic getSpecificValue(Object generic) {
|
||||
return WrappedStatistic.fromHandle(generic);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<WrappedStatistic> getSpecificType() {
|
||||
return WrappedStatistic.class;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a converter for block instances.
|
||||
* @return A converter for block instances.
|
||||
*/
|
||||
public static EquivalentConverter<Material> getBlockConverter() {
|
||||
// Initialize if we have't already
|
||||
if (GET_BLOCK == null || GET_BLOCK_ID == null) {
|
||||
Class<?> block = MinecraftReflection.getBlockClass();
|
||||
|
||||
FuzzyMethodContract getIdContract = FuzzyMethodContract.newBuilder().
|
||||
parameterExactArray(block).
|
||||
requireModifier(Modifier.STATIC).
|
||||
build();
|
||||
FuzzyMethodContract getBlockContract = FuzzyMethodContract.newBuilder().
|
||||
parameterExactArray(int.class).
|
||||
requireModifier(Modifier.STATIC).
|
||||
build();
|
||||
GET_BLOCK = Accessors.getMethodAccessor(FuzzyReflection.fromClass(block).getMethod(getBlockContract));
|
||||
GET_BLOCK_ID = Accessors.getMethodAccessor(FuzzyReflection.fromClass(block).getMethod(getIdContract));
|
||||
}
|
||||
|
||||
return new IgnoreNullConverter<Material>() {
|
||||
@Override
|
||||
protected Object getGenericValue(Class<?> genericType, Material specific) {
|
||||
return GET_BLOCK.invoke(null, specific.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Material getSpecificValue(Object generic) {
|
||||
return Material.getMaterial((Integer) GET_BLOCK_ID.invoke(null, generic));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<Material> getSpecificType() {
|
||||
return Material.class;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the converter used to convert between a NMS World and a Bukkit world.
|
||||
* @return The world converter.
|
||||
*/
|
||||
public static EquivalentConverter<World> getWorldConverter() {
|
||||
return new IgnoreNullConverter<World>() {
|
||||
@Override
|
||||
protected Object getGenericValue(Class<?> genericType, World specific) {
|
||||
return BukkitUnwrapper.getInstance().unwrapItem(specific);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected World getSpecificValue(Object generic) {
|
||||
return (World) craftWorldField.get(generic);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<World> getSpecificType() {
|
||||
return World.class;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the converter used to convert between a PotionEffect and the equivalent NMS Mobeffect.
|
||||
* @return The potion effect converter.
|
||||
*/
|
||||
public static EquivalentConverter<PotionEffect> getPotionEffectConverter() {
|
||||
return new IgnoreNullConverter<PotionEffect>() {
|
||||
@Override
|
||||
protected Object getGenericValue(Class<?> genericType, PotionEffect specific) {
|
||||
// Locate the constructor
|
||||
if (mobEffectConstructor == null) {
|
||||
try {
|
||||
mobEffectConstructor = MinecraftReflection.getMobEffectClass().
|
||||
getConstructor(int.class, int.class, int.class, boolean.class);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Cannot find mob effect constructor (int, int, int, boolean).", e);
|
||||
}
|
||||
}
|
||||
|
||||
// Create the generic value
|
||||
try {
|
||||
return mobEffectConstructor.newInstance(
|
||||
specific.getType().getId(), specific.getDuration(),
|
||||
specific.getAmplifier(), specific.isAmbient());
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Cannot construct MobEffect.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PotionEffect getSpecificValue(Object generic) {
|
||||
if (mobEffectModifier == null) {
|
||||
mobEffectModifier = new StructureModifier<Object>(MinecraftReflection.getMobEffectClass(), false);
|
||||
}
|
||||
StructureModifier<Integer> ints = mobEffectModifier.withTarget(generic).withType(int.class);
|
||||
StructureModifier<Boolean> bools = mobEffectModifier.withTarget(generic).withType(boolean.class);
|
||||
|
||||
return new PotionEffect(
|
||||
PotionEffectType.getById(ints.read(0)), /* effectId */
|
||||
ints.read(1), /* duration */
|
||||
ints.read(2), /* amplification */
|
||||
bools.read(1) /* ambient */
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<PotionEffect> getSpecificType() {
|
||||
return PotionEffect.class;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps a given equivalent converter in NULL checks, ensuring that such values are ignored.
|
||||
* @param delegate - the underlying equivalent converter.
|
||||
* @return A equivalent converter that ignores NULL values.
|
||||
*/
|
||||
public static <TType> EquivalentConverter<TType> getIgnoreNull(final EquivalentConverter<TType> delegate) {
|
||||
// Automatically wrap all parameters to the delegate with a NULL check
|
||||
return new IgnoreNullConverter<TType>() {
|
||||
@Override
|
||||
public Object getGenericValue(Class<?> genericType, TType specific) {
|
||||
return delegate.getGeneric(genericType, specific);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TType getSpecificValue(Object generic) {
|
||||
return delegate.getSpecific(generic);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<TType> getSpecificType() {
|
||||
return delegate.getSpecificType();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve an equivalent unwrapper for the converter.
|
||||
* @param nativeType - the native NMS type the converter produces.
|
||||
* @param converter - the converter.
|
||||
* @return The equivalent unwrapper.
|
||||
*/
|
||||
public static Unwrapper asUnwrapper(final Class<?> nativeType, final EquivalentConverter<Object> converter) {
|
||||
return new Unwrapper() {
|
||||
@Override
|
||||
public Object unwrapItem(Object wrappedObject) {
|
||||
Class<?> type = PacketConstructor.getClass(wrappedObject);
|
||||
|
||||
// Ensure the type is correct before we test
|
||||
if (converter.getSpecificType().isAssignableFrom(type)) {
|
||||
if (wrappedObject instanceof Class)
|
||||
return nativeType;
|
||||
else
|
||||
return converter.getGeneric(nativeType, wrappedObject);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve every converter that is associated with a specific class.
|
||||
* @return Every converter with a unique specific class.
|
||||
*/
|
||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||
public static Map<Class<?>, EquivalentConverter<Object>> getConvertersForSpecific() {
|
||||
if (specificConverters == null) {
|
||||
// Generics doesn't work, as usual
|
||||
ImmutableMap.Builder<Class<?>, EquivalentConverter<Object>> builder =
|
||||
ImmutableMap.<Class<?>, EquivalentConverter<Object>>builder().
|
||||
put(WrappedDataWatcher.class, (EquivalentConverter) getDataWatcherConverter()).
|
||||
put(ItemStack.class, (EquivalentConverter) getItemStackConverter()).
|
||||
put(NbtBase.class, (EquivalentConverter) getNbtConverter()).
|
||||
put(NbtCompound.class, (EquivalentConverter) getNbtConverter()).
|
||||
put(WrappedWatchableObject.class, (EquivalentConverter) getWatchableObjectConverter()).
|
||||
put(PotionEffect.class, (EquivalentConverter) getPotionEffectConverter()).
|
||||
put(World.class, (EquivalentConverter) getWorldConverter());
|
||||
|
||||
// Types added in 1.7.2
|
||||
if (MinecraftReflection.isUsingNetty()) {
|
||||
builder.put(Material.class, (EquivalentConverter) getBlockConverter());
|
||||
builder.put(WrappedGameProfile.class, (EquivalentConverter) getWrappedGameProfileConverter());
|
||||
builder.put(WrappedChatComponent.class, (EquivalentConverter) getWrappedChatComponentConverter());
|
||||
builder.put(WrappedServerPing.class, (EquivalentConverter) getWrappedServerPingConverter());
|
||||
builder.put(WrappedStatistic.class, (EquivalentConverter) getWrappedStatisticConverter());
|
||||
|
||||
for (Entry<Class<?>, EquivalentConverter<?>> entry : EnumWrappers.getFromWrapperMap().entrySet()) {
|
||||
builder.put((Class) entry.getKey(), (EquivalentConverter) entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
if (hasWorldType)
|
||||
builder.put(WorldType.class, (EquivalentConverter) getWorldTypeConverter());
|
||||
if (hasAttributeSnapshot)
|
||||
builder.put(WrappedAttribute.class, (EquivalentConverter) getWrappedAttributeConverter());
|
||||
specificConverters = builder.build();
|
||||
}
|
||||
return specificConverters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve every converter that is associated with a generic class.
|
||||
* @return Every converter with a unique generic class.
|
||||
*/
|
||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||
public static Map<Class<?>, EquivalentConverter<Object>> getConvertersForGeneric() {
|
||||
if (genericConverters == null) {
|
||||
// Generics doesn't work, as usual
|
||||
ImmutableMap.Builder<Class<?>, EquivalentConverter<Object>> builder =
|
||||
ImmutableMap.<Class<?>, EquivalentConverter<Object>>builder().
|
||||
put(MinecraftReflection.getDataWatcherClass(), (EquivalentConverter) getDataWatcherConverter()).
|
||||
put(MinecraftReflection.getItemStackClass(), (EquivalentConverter) getItemStackConverter()).
|
||||
put(MinecraftReflection.getNBTBaseClass(), (EquivalentConverter) getNbtConverter()).
|
||||
put(MinecraftReflection.getNBTCompoundClass(), (EquivalentConverter) getNbtConverter()).
|
||||
put(MinecraftReflection.getWatchableObjectClass(), (EquivalentConverter) getWatchableObjectConverter()).
|
||||
put(MinecraftReflection.getMobEffectClass(), (EquivalentConverter) getPotionEffectConverter()).
|
||||
put(MinecraftReflection.getNmsWorldClass(), (EquivalentConverter) getWorldConverter());
|
||||
|
||||
if (hasWorldType)
|
||||
builder.put(MinecraftReflection.getWorldTypeClass(), (EquivalentConverter) getWorldTypeConverter());
|
||||
if (hasAttributeSnapshot)
|
||||
builder.put(MinecraftReflection.getAttributeSnapshotClass(), (EquivalentConverter) getWrappedAttributeConverter());
|
||||
|
||||
// Types added in 1.7.2
|
||||
if (MinecraftReflection.isUsingNetty()) {
|
||||
builder.put(MinecraftReflection.getBlockClass(), (EquivalentConverter) getBlockConverter());
|
||||
builder.put(MinecraftReflection.getGameProfileClass(), (EquivalentConverter) getWrappedGameProfileConverter());
|
||||
builder.put(MinecraftReflection.getIChatBaseComponentClass(), (EquivalentConverter) getWrappedChatComponentConverter());
|
||||
builder.put(MinecraftReflection.getServerPingClass(), (EquivalentConverter) getWrappedServerPingConverter());
|
||||
builder.put(MinecraftReflection.getStatisticClass(), (EquivalentConverter) getWrappedStatisticConverter());
|
||||
|
||||
for (Entry<Class<?>, EquivalentConverter<?>> entry : EnumWrappers.getFromNativeMap().entrySet()) {
|
||||
builder.put((Class) entry.getKey(), (EquivalentConverter) entry.getValue());
|
||||
}
|
||||
}
|
||||
genericConverters = builder.build();
|
||||
}
|
||||
return genericConverters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve every NMS <-> Bukkit converter as unwrappers.
|
||||
* @return Every unwrapper.
|
||||
*/
|
||||
public static List<Unwrapper> getUnwrappers() {
|
||||
if (unwrappers == null) {
|
||||
ImmutableList.Builder<Unwrapper> builder = ImmutableList.builder();
|
||||
|
||||
for (Map.Entry<Class<?>, EquivalentConverter<Object>> entry : getConvertersForGeneric().entrySet()) {
|
||||
builder.add(asUnwrapper(entry.getKey(), entry.getValue()));
|
||||
}
|
||||
unwrappers = builder.build();
|
||||
}
|
||||
return unwrappers;
|
||||
}
|
||||
}
|
||||
@@ -1,182 +0,0 @@
|
||||
package com.comphenix.protocol.wrappers;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.bukkit.GameMode;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.PacketType.Protocol;
|
||||
import com.comphenix.protocol.reflect.EquivalentConverter;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
/**
|
||||
* Represents a generic enum converter.
|
||||
* @author Kristian
|
||||
*/
|
||||
public abstract class EnumWrappers {
|
||||
public enum ClientCommand {
|
||||
PERFORM_RESPAWN,
|
||||
REQUEST_STATS,
|
||||
OPEN_INVENTORY_ACHIEVEMENT;
|
||||
}
|
||||
|
||||
public enum ChatVisibility {
|
||||
FULL,
|
||||
SYSTEM,
|
||||
HIDDEN;
|
||||
}
|
||||
|
||||
public enum Difficulty {
|
||||
PEACEFUL,
|
||||
EASY,
|
||||
NORMAL,
|
||||
HARD;
|
||||
}
|
||||
|
||||
public enum EntityUseAction {
|
||||
INTERACT,
|
||||
ATTACK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a native game mode in Minecraft.
|
||||
* <p>
|
||||
* Not to be confused with {@link GameMode} in Bukkit.
|
||||
* @author Kristian
|
||||
*/
|
||||
public enum NativeGameMode {
|
||||
NONE,
|
||||
SURVIVAL,
|
||||
CREATIVE,
|
||||
ADVENTURE;
|
||||
}
|
||||
|
||||
private static Class<?> PROTOCOL_CLASS = null;
|
||||
private static Class<?> CLIENT_COMMAND_CLASS = null;
|
||||
private static Class<?> CHAT_VISIBILITY_CLASS = null;
|
||||
private static Class<?> DIFFICULTY_CLASS = null;
|
||||
private static Class<?> ENTITY_USE_ACTION_CLASS = null;
|
||||
private static Class<?> GAMEMODE_CLASS = null;
|
||||
|
||||
private static Map<Class<?>, EquivalentConverter<?>> FROM_NATIVE = Maps.newHashMap();
|
||||
private static Map<Class<?>, EquivalentConverter<?>> FROM_WRAPPER = Maps.newHashMap();
|
||||
|
||||
/**
|
||||
* Initialize the wrappers, if we haven't already.
|
||||
*/
|
||||
private static void initialize() {
|
||||
if (!MinecraftReflection.isUsingNetty())
|
||||
throw new IllegalArgumentException("Not supported on 1.6.4 and earlier.");
|
||||
|
||||
PROTOCOL_CLASS = getEnum(PacketType.Handshake.Client.SET_PROTOCOL.getPacketClass(), 0);
|
||||
CLIENT_COMMAND_CLASS = getEnum(PacketType.Play.Client.CLIENT_COMMAND.getPacketClass(), 0);
|
||||
CHAT_VISIBILITY_CLASS = getEnum(PacketType.Play.Client.SETTINGS.getPacketClass(), 0);
|
||||
DIFFICULTY_CLASS = getEnum(PacketType.Play.Client.SETTINGS.getPacketClass(), 1);
|
||||
ENTITY_USE_ACTION_CLASS = getEnum(PacketType.Play.Client.USE_ENTITY.getPacketClass(), 0);
|
||||
GAMEMODE_CLASS = getEnum(PacketType.Play.Server.LOGIN.getPacketClass(), 0);
|
||||
|
||||
associate(PROTOCOL_CLASS, Protocol.class, getClientCommandConverter());
|
||||
associate(CLIENT_COMMAND_CLASS, ClientCommand.class, getClientCommandConverter());
|
||||
associate(CHAT_VISIBILITY_CLASS, ChatVisibility.class, getChatVisibilityConverter());
|
||||
associate(DIFFICULTY_CLASS, Difficulty.class, getDifficultyConverter());
|
||||
associate(ENTITY_USE_ACTION_CLASS, EntityUseAction.class, getEntityUseActionConverter());
|
||||
associate(GAMEMODE_CLASS, NativeGameMode.class, getGameModeConverter());
|
||||
}
|
||||
|
||||
private static void associate(Class<?> nativeClass, Class<?> wrapperClass, EquivalentConverter<?> converter) {
|
||||
FROM_NATIVE.put(nativeClass, converter);
|
||||
FROM_WRAPPER.put(wrapperClass, converter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the enum field with the given declaration index (in relation to the other enums).
|
||||
* @param clazz - the declaration class.
|
||||
* @param index - the enum index.
|
||||
* @return The type of the enum field.
|
||||
*/
|
||||
private static Class<?> getEnum(Class<?> clazz, int index) {
|
||||
return FuzzyReflection.fromClass(clazz, true).getFieldListByType(Enum.class).get(index).getType();
|
||||
}
|
||||
|
||||
public static Map<Class<?>, EquivalentConverter<?>> getFromNativeMap() {
|
||||
return FROM_NATIVE;
|
||||
}
|
||||
|
||||
public static Map<Class<?>, EquivalentConverter<?>> getFromWrapperMap() {
|
||||
return FROM_WRAPPER;
|
||||
}
|
||||
|
||||
// Get the native enum classes
|
||||
public static Class<?> getProtocolClass() {
|
||||
initialize();
|
||||
return PROTOCOL_CLASS;
|
||||
}
|
||||
public static Class<?> getClientCommandClass() {
|
||||
initialize();
|
||||
return CLIENT_COMMAND_CLASS;
|
||||
}
|
||||
public static Class<?> getChatVisibilityClass() {
|
||||
initialize();
|
||||
return CHAT_VISIBILITY_CLASS;
|
||||
}
|
||||
public static Class<?> getDifficultyClass() {
|
||||
initialize();
|
||||
return DIFFICULTY_CLASS;
|
||||
}
|
||||
public static Class<?> getEntityUseActionClass() {
|
||||
initialize();
|
||||
return ENTITY_USE_ACTION_CLASS;
|
||||
}
|
||||
public static Class<?> getGameModeClass() {
|
||||
initialize();
|
||||
return GAMEMODE_CLASS;
|
||||
}
|
||||
|
||||
// Get the converters
|
||||
public static EquivalentConverter<Protocol> getProtocolConverter() {
|
||||
return new EnumConverter<Protocol>(Protocol.class);
|
||||
}
|
||||
public static EquivalentConverter<ClientCommand> getClientCommandConverter() {
|
||||
return new EnumConverter<ClientCommand>(ClientCommand.class);
|
||||
}
|
||||
public static EquivalentConverter<ChatVisibility> getChatVisibilityConverter() {
|
||||
return new EnumConverter<ChatVisibility>(ChatVisibility.class);
|
||||
}
|
||||
public static EquivalentConverter<Difficulty> getDifficultyConverter() {
|
||||
return new EnumConverter<Difficulty>(Difficulty.class);
|
||||
}
|
||||
public static EquivalentConverter<EntityUseAction> getEntityUseActionConverter() {
|
||||
return new EnumConverter<EntityUseAction>(EntityUseAction.class);
|
||||
}
|
||||
public static EquivalentConverter<NativeGameMode> getGameModeConverter() {
|
||||
return new EnumConverter<NativeGameMode>(NativeGameMode.class);
|
||||
}
|
||||
|
||||
// The common enum converter
|
||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||
private static class EnumConverter<T extends Enum<T>> implements EquivalentConverter<T> {
|
||||
private Class<T> specificType;
|
||||
|
||||
public EnumConverter(Class<T> specificType) {
|
||||
this.specificType = specificType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T getSpecific(Object generic) {
|
||||
// We know its an enum already!
|
||||
return Enum.valueOf(specificType, ((Enum) generic).name());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getGeneric(Class<?> genericType, T specific) {
|
||||
return Enum.valueOf((Class) genericType, specific.name());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<T> getSpecificType() {
|
||||
return specificType;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,939 +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.wrappers;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReadWriteLock;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import com.comphenix.protocol.annotations.Spigot;
|
||||
import com.comphenix.protocol.injector.BukkitUnwrapper;
|
||||
import com.comphenix.protocol.reflect.FieldAccessException;
|
||||
import com.comphenix.protocol.reflect.FieldUtils;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.comphenix.protocol.reflect.accessors.Accessors;
|
||||
import com.comphenix.protocol.reflect.accessors.ConstructorAccessor;
|
||||
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
|
||||
import com.comphenix.protocol.reflect.accessors.ReadOnlyFieldAccessor;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
import com.comphenix.protocol.wrappers.collection.ConvertedMap;
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.base.Objects;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.Iterators;
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
/**
|
||||
* Wraps a DataWatcher that is used to transmit arbitrary key-value pairs with a given entity.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public class WrappedDataWatcher extends AbstractWrapper implements Iterable<WrappedWatchableObject> {
|
||||
/**
|
||||
* Every custom watchable type in Spigot #1628 and above.
|
||||
* @author Kristian
|
||||
*/
|
||||
@Spigot(minimumBuild = 1628)
|
||||
public enum CustomType {
|
||||
BYTE_SHORT("org.spigotmc.ProtocolData$ByteShort", 0, short.class),
|
||||
DUAL_BYTE("org.spigotmc.ProtocolData$DualByte", 0, byte.class, byte.class),
|
||||
HIDDEN_BYTE("org.spigotmc.ProtocolData$HiddenByte", 0, byte.class),
|
||||
INT_BYTE("org.spigotmc.ProtocolData$IntByte", 2, int.class, byte.class),
|
||||
DUAL_INT("org.spigotmc.ProtocolData$DualInt", 2, int.class, int.class);
|
||||
|
||||
private Class<?> spigotClass;
|
||||
private ConstructorAccessor constructor;
|
||||
private FieldAccessor secondaryValue;
|
||||
private int typeId;
|
||||
|
||||
private CustomType(String className, int typeId, Class<?>... parameters) {
|
||||
try {
|
||||
this.spigotClass = Class.forName(className);
|
||||
this.constructor = Accessors.getConstructorAccessor(spigotClass, parameters);
|
||||
this.secondaryValue = parameters.length > 1 ? Accessors.getFieldAccessor(spigotClass, "value2", true) : null;
|
||||
|
||||
} catch (ClassNotFoundException e) {
|
||||
// System.out.println("[ProtocolLib] Unable to find " + className);
|
||||
this.spigotClass = null;
|
||||
}
|
||||
this.typeId = typeId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new instance of this Spigot type.
|
||||
* @param value - the value. Cannot be NULL.
|
||||
* @return The instance to construct.
|
||||
*/
|
||||
Object newInstance(Object value) {
|
||||
return newInstance(value, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new instance of this Spigot type.
|
||||
* <p>
|
||||
* The secondary value may be NULL if this custom type does not contain a secondary value.
|
||||
* @param value - the value.
|
||||
* @param secondary - optional secondary value.
|
||||
* @return
|
||||
*/
|
||||
Object newInstance(Object value, Object secondary) {
|
||||
Preconditions.checkNotNull(value, "value cannot be NULL.");
|
||||
|
||||
if (hasSecondary()) {
|
||||
return constructor.invoke(value, secondary);
|
||||
} else {
|
||||
if (secondary != null) {
|
||||
throw new IllegalArgumentException("Cannot construct " + this + " with a secondary value");
|
||||
}
|
||||
return constructor.invoke(value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the secondary value of a given type.
|
||||
* @param instance - the instance.
|
||||
* @param secondary - the secondary value.
|
||||
*/
|
||||
void setSecondary(Object instance, Object secondary) {
|
||||
if (!hasSecondary()) {
|
||||
throw new IllegalArgumentException(this + " does not have a secondary value.");
|
||||
}
|
||||
secondaryValue.set(instance, secondary);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the secondary value of a type.
|
||||
* @param instance - the instance.
|
||||
* @return The secondary value.
|
||||
*/
|
||||
Object getSecondary(Object instance) {
|
||||
if (!hasSecondary()) {
|
||||
throw new IllegalArgumentException(this + " does not have a secondary value.");
|
||||
}
|
||||
return secondaryValue.get(instance);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if this type has a secondary value.
|
||||
* @return TRUE if it does, FALSE otherwise.
|
||||
*/
|
||||
public boolean hasSecondary() {
|
||||
return secondaryValue != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Underlying Spigot class.
|
||||
* @return The class.
|
||||
*/
|
||||
public Class<?> getSpigotClass() {
|
||||
return spigotClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* The equivalent type ID.
|
||||
* @return The equivalent ID.
|
||||
*/
|
||||
public int getTypeId() {
|
||||
return typeId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the custom Spigot type of a value.
|
||||
* @param value - the value.
|
||||
* @return The Spigot type, or NULL if not found.
|
||||
*/
|
||||
@Spigot(minimumBuild = 1628)
|
||||
public static CustomType fromValue(Object value) {
|
||||
for (CustomType type : CustomType.values()) {
|
||||
if (type.getSpigotClass().isInstance(value)) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to assign integer IDs to given types.
|
||||
*/
|
||||
private static Map<Class<?>, Integer> TYPE_MAP;
|
||||
|
||||
/**
|
||||
* Custom types in the bountiful update.
|
||||
*/
|
||||
private static Map<Class<?>, Integer> CUSTOM_MAP;
|
||||
|
||||
// Accessors
|
||||
private static FieldAccessor TYPE_MAP_ACCESSOR;
|
||||
private static FieldAccessor VALUE_MAP_ACCESSOR;
|
||||
|
||||
// Fields
|
||||
private static Field READ_WRITE_LOCK_FIELD;
|
||||
private static Field ENTITY_FIELD;
|
||||
|
||||
// Methods
|
||||
private static Method CREATE_KEY_VALUE_METHOD;
|
||||
private static Method UPDATE_KEY_VALUE_METHOD;
|
||||
private static Method GET_KEY_VALUE_METHOD;
|
||||
|
||||
// Constructors
|
||||
private static Constructor<?> CREATE_DATA_WATCHER_CONSTRUCTOR;
|
||||
|
||||
// Entity methods
|
||||
private volatile static Field ENTITY_DATA_FIELD;
|
||||
|
||||
/**
|
||||
* Whether or not this class has already been initialized.
|
||||
*/
|
||||
private static boolean HAS_INITIALIZED;
|
||||
|
||||
// Lock
|
||||
private ReadWriteLock readWriteLock;
|
||||
|
||||
// Map of watchable objects
|
||||
private Map<Integer, Object> watchableObjects;
|
||||
|
||||
// A map view of all the watchable objects
|
||||
private Map<Integer, WrappedWatchableObject> mapView;
|
||||
|
||||
/**
|
||||
* Initialize a new data watcher.
|
||||
* @throws FieldAccessException If we're unable to wrap a DataWatcher.
|
||||
*/
|
||||
public WrappedDataWatcher() {
|
||||
super(MinecraftReflection.getDataWatcherClass());
|
||||
|
||||
// Just create a new watcher
|
||||
try {
|
||||
if (MinecraftReflection.isUsingNetty()) {
|
||||
setHandle(newEntityHandle(null));
|
||||
} else {
|
||||
setHandle(getHandleType().newInstance());
|
||||
}
|
||||
initialize();
|
||||
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Unable to construct DataWatcher.", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a wrapper for a given data watcher.
|
||||
* @param handle - the data watcher to wrap.
|
||||
* @throws FieldAccessException If we're unable to wrap a DataWatcher.
|
||||
*/
|
||||
public WrappedDataWatcher(Object handle) {
|
||||
super(MinecraftReflection.getDataWatcherClass());
|
||||
setHandle(handle);
|
||||
initialize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new data watcher with the given entity.
|
||||
* <p>
|
||||
* In 1.6.4 and ealier, this will fall back to using {@link #WrappedDataWatcher()}.
|
||||
* @param entity - the entity.
|
||||
* @return The wrapped data watcher.
|
||||
*/
|
||||
public static WrappedDataWatcher newWithEntity(Entity entity) {
|
||||
// Use the old constructor
|
||||
if (!MinecraftReflection.isUsingNetty())
|
||||
return new WrappedDataWatcher();
|
||||
return new WrappedDataWatcher(newEntityHandle(entity));
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new native DataWatcher with the given entity.
|
||||
* <p>
|
||||
* Warning: This is only supported in 1.7.2 and above.
|
||||
* @param entity - the entity, or NULL.
|
||||
* @return The data watcher.
|
||||
*/
|
||||
private static Object newEntityHandle(Entity entity) {
|
||||
Class<?> dataWatcher = MinecraftReflection.getDataWatcherClass();
|
||||
|
||||
try {
|
||||
if (CREATE_DATA_WATCHER_CONSTRUCTOR == null)
|
||||
CREATE_DATA_WATCHER_CONSTRUCTOR = dataWatcher.getConstructor(MinecraftReflection.getEntityClass());
|
||||
|
||||
return CREATE_DATA_WATCHER_CONSTRUCTOR.newInstance(
|
||||
BukkitUnwrapper.getInstance().unwrapItem(entity)
|
||||
);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Cannot construct data watcher.", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new data watcher for a list of watchable objects.
|
||||
* <p>
|
||||
* Note that the watchable objects are not cloned, and will be modified in place. Use "deepClone" if
|
||||
* that is not desirable.
|
||||
* <p>
|
||||
* The {@link #removeObject(int)} method will not modify the given list, however.
|
||||
*
|
||||
* @param watchableObjects - list of watchable objects that will be copied.
|
||||
* @throws FieldAccessException Unable to read watchable objects.
|
||||
*/
|
||||
public WrappedDataWatcher(List<WrappedWatchableObject> watchableObjects) throws FieldAccessException {
|
||||
this();
|
||||
|
||||
Lock writeLock = getReadWriteLock().writeLock();
|
||||
Map<Integer, Object> map = getWatchableObjectMap();
|
||||
|
||||
writeLock.lock();
|
||||
|
||||
try {
|
||||
// Add the watchable objects by reference
|
||||
for (WrappedWatchableObject watched : watchableObjects) {
|
||||
map.put(watched.getIndex(), watched.handle);
|
||||
}
|
||||
} finally {
|
||||
writeLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the ID of a given type, if it's allowed to be watched.
|
||||
* @return The ID, or NULL if it cannot be watched.
|
||||
* @throws FieldAccessException If we cannot initialize the reflection machinery.
|
||||
*/
|
||||
public static Integer getTypeID(Class<?> clazz) throws FieldAccessException {
|
||||
initialize();
|
||||
Integer result = TYPE_MAP.get(WrappedWatchableObject.getUnwrappedType(clazz));
|
||||
|
||||
if (result == null) {
|
||||
result = CUSTOM_MAP.get(clazz);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the type of a given ID, if it's allowed to be watched.
|
||||
* @return The type using a given ID, or NULL if it cannot be watched.
|
||||
* @throws FieldAccessException If we cannot initialize the reflection machinery.
|
||||
*/
|
||||
public static Class<?> getTypeClass(int id) throws FieldAccessException {
|
||||
initialize();
|
||||
|
||||
for (Map.Entry<Class<?>, Integer> entry : TYPE_MAP.entrySet()) {
|
||||
if (Objects.equal(entry.getValue(), id)) {
|
||||
return entry.getKey();
|
||||
}
|
||||
}
|
||||
|
||||
// Unknown class type
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a watched byte.
|
||||
* @param index - index of the watched byte.
|
||||
* @return The watched byte, or NULL if this value doesn't exist.
|
||||
* @throws FieldAccessException Cannot read underlying field.
|
||||
*/
|
||||
public Byte getByte(int index) throws FieldAccessException {
|
||||
return (Byte) getObject(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a watched short.
|
||||
* @param index - index of the watched short.
|
||||
* @return The watched short, or NULL if this value doesn't exist.
|
||||
* @throws FieldAccessException Cannot read underlying field.
|
||||
*/
|
||||
public Short getShort(int index) throws FieldAccessException {
|
||||
return (Short) getObject(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a watched integer.
|
||||
* @param index - index of the watched integer.
|
||||
* @return The watched integer, or NULL if this value doesn't exist.
|
||||
* @throws FieldAccessException Cannot read underlying field.
|
||||
*/
|
||||
public Integer getInteger(int index) throws FieldAccessException {
|
||||
return (Integer) getObject(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a watched float.
|
||||
* @param index - index of the watched float.
|
||||
* @return The watched float, or NULL if this value doesn't exist.
|
||||
* @throws FieldAccessException Cannot read underlying field.
|
||||
*/
|
||||
public Float getFloat(int index) throws FieldAccessException {
|
||||
return (Float) getObject(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a watched string.
|
||||
* @param index - index of the watched string.
|
||||
* @return The watched string, or NULL if this value doesn't exist.
|
||||
* @throws FieldAccessException Cannot read underlying field.
|
||||
*/
|
||||
public String getString(int index) throws FieldAccessException {
|
||||
return (String) getObject(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a watched string.
|
||||
* @param index - index of the watched string.
|
||||
* @return The watched string, or NULL if this value doesn't exist.
|
||||
* @throws FieldAccessException Cannot read underlying field.
|
||||
*/
|
||||
public ItemStack getItemStack(int index) throws FieldAccessException {
|
||||
return (ItemStack) getObject(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a watched string.
|
||||
* @param index - index of the watched string.
|
||||
* @return The watched string, or NULL if this value doesn't exist.
|
||||
* @throws FieldAccessException Cannot read underlying field.
|
||||
*/
|
||||
public WrappedChunkCoordinate getChunkCoordinate(int index) throws FieldAccessException {
|
||||
return (WrappedChunkCoordinate) getObject(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a watchable object by index.
|
||||
* @param index - index of the object to retrieve.
|
||||
* @return The watched object.
|
||||
* @throws FieldAccessException Cannot read underlying field.
|
||||
*/
|
||||
public Object getObject(int index) throws FieldAccessException {
|
||||
// The get method will take care of concurrency
|
||||
WrappedWatchableObject object = getWrappedObject(index);
|
||||
return object != null ? object.getValue() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the wrapped object by index.
|
||||
* @param index - the index.
|
||||
* @return The corresponding wrapper, or NULL.
|
||||
*/
|
||||
@Spigot(minimumBuild = 1628)
|
||||
public WrappedWatchableObject getWrappedObject(int index) {
|
||||
// The get method will take care of concurrency
|
||||
Object watchable = getWatchedObject(index);
|
||||
|
||||
if (watchable != null) {
|
||||
return new WrappedWatchableObject(watchable);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve every watchable object in this watcher.
|
||||
* @return Every watchable object.
|
||||
* @throws FieldAccessException If reflection failed.
|
||||
*/
|
||||
public List<WrappedWatchableObject> getWatchableObjects() throws FieldAccessException {
|
||||
Lock readLock = getReadWriteLock().readLock();
|
||||
readLock.lock();
|
||||
|
||||
try {
|
||||
List<WrappedWatchableObject> result = new ArrayList<WrappedWatchableObject>();
|
||||
|
||||
// Add each watchable object to the list
|
||||
for (Object watchable : getWatchableObjectMap().values()) {
|
||||
if (watchable != null) {
|
||||
result.add(new WrappedWatchableObject(watchable));
|
||||
} else {
|
||||
result.add(null);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
|
||||
} finally {
|
||||
readLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
// Quick checks
|
||||
if (obj == this)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
|
||||
if (obj instanceof WrappedDataWatcher) {
|
||||
WrappedDataWatcher other = (WrappedDataWatcher) obj;
|
||||
Iterator<WrappedWatchableObject> first = iterator(), second = other.iterator();
|
||||
|
||||
// Make sure they're the same size
|
||||
if (size() != other.size())
|
||||
return false;
|
||||
|
||||
for (; first.hasNext() && second.hasNext(); ) {
|
||||
// See if the two elements are equal
|
||||
if (!first.next().equals(second.next()))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return getWatchableObjects().hashCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a copy of every index associated with a watched object.
|
||||
* @return Every watched object index.
|
||||
* @throws FieldAccessException If we're unable to read the underlying object.
|
||||
*/
|
||||
public Set<Integer> indexSet() throws FieldAccessException {
|
||||
Lock readLock = getReadWriteLock().readLock();
|
||||
readLock.lock();
|
||||
|
||||
try {
|
||||
return new HashSet<Integer>(getWatchableObjectMap().keySet());
|
||||
} finally {
|
||||
readLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone the content of the current DataWatcher.
|
||||
* @return A cloned data watcher.
|
||||
*/
|
||||
public WrappedDataWatcher deepClone() {
|
||||
WrappedDataWatcher clone = new WrappedDataWatcher();
|
||||
|
||||
// Make a new copy instead
|
||||
for (WrappedWatchableObject watchable : this) {
|
||||
clone.setObject(watchable.getIndex(), watchable.getClonedValue());
|
||||
}
|
||||
return clone;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the number of watched objects.
|
||||
* @return Watched object count.
|
||||
* @throws FieldAccessException If we're unable to read the underlying object.
|
||||
*/
|
||||
public int size() throws FieldAccessException {
|
||||
Lock readLock = getReadWriteLock().readLock();
|
||||
readLock.lock();
|
||||
|
||||
try {
|
||||
return getWatchableObjectMap().size();
|
||||
} finally {
|
||||
readLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a given object from the underlying DataWatcher.
|
||||
* @param index - index of the object to remove.
|
||||
* @return The watchable object that was removed, or NULL If none could be found.
|
||||
*/
|
||||
public WrappedWatchableObject removeObject(int index) {
|
||||
Lock writeLock = getReadWriteLock().writeLock();
|
||||
writeLock.lock();
|
||||
|
||||
try {
|
||||
Object removed = getWatchableObjectMap().remove(index);
|
||||
return removed != null ? new WrappedWatchableObject(removed) : null;
|
||||
} finally {
|
||||
writeLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a watched byte.
|
||||
* @param index - index of the watched byte.
|
||||
* @param newValue - the new watched value.
|
||||
* @throws FieldAccessException Cannot read underlying field.
|
||||
*/
|
||||
public void setObject(int index, Object newValue) throws FieldAccessException {
|
||||
setObject(index, newValue, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a watched byte.
|
||||
* @param index - index of the watched byte.
|
||||
* @param newValue - the new watched value.
|
||||
* @param update - whether or not to refresh every listening client.
|
||||
* @throws FieldAccessException Cannot read underlying field.
|
||||
*/
|
||||
public void setObject(int index, Object newValue, boolean update) throws FieldAccessException {
|
||||
// Aquire write lock
|
||||
Lock writeLock = getReadWriteLock().writeLock();
|
||||
writeLock.lock();
|
||||
|
||||
try {
|
||||
Object watchable = getWatchedObject(index);
|
||||
|
||||
if (watchable != null) {
|
||||
new WrappedWatchableObject(watchable).setValue(newValue, update);
|
||||
} else {
|
||||
CREATE_KEY_VALUE_METHOD.invoke(handle, index, WrappedWatchableObject.getUnwrapped(newValue));
|
||||
}
|
||||
|
||||
// Handle invoking the method
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new FieldAccessException("Cannot convert arguments.", e);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new FieldAccessException("Illegal access.", e);
|
||||
} catch (InvocationTargetException e) {
|
||||
throw new FieldAccessException("Checked exception in Minecraft.", e);
|
||||
} finally {
|
||||
writeLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a watched byte with an optional secondary value.
|
||||
* @param index - index of the watched byte.
|
||||
* @param newValue - the new watched value.
|
||||
* @param secondary - optional secondary value.
|
||||
* @param update - whether or not to refresh every listening client.
|
||||
* @throws FieldAccessException Cannot read underlying field.
|
||||
*/
|
||||
@Spigot(minimumBuild = 1628)
|
||||
public void setObject(int index, Object newValue, Object secondary, boolean update, CustomType type) throws FieldAccessException {
|
||||
Object created = type.newInstance(newValue, secondary);
|
||||
|
||||
// Now update the watcher
|
||||
setObject(index, created, update);
|
||||
}
|
||||
|
||||
private Object getWatchedObject(int index) throws FieldAccessException {
|
||||
// We use the get-method first and foremost
|
||||
if (GET_KEY_VALUE_METHOD != null) {
|
||||
try {
|
||||
return GET_KEY_VALUE_METHOD.invoke(handle, index);
|
||||
} catch (Exception e) {
|
||||
throw new FieldAccessException("Cannot invoke get key method for index " + index, e);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
getReadWriteLock().readLock().lock();
|
||||
return getWatchableObjectMap().get(index);
|
||||
|
||||
} finally {
|
||||
getReadWriteLock().readLock().unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the current read write lock.
|
||||
* @return Current read write lock.
|
||||
* @throws FieldAccessException If we're unable to read the underlying field.
|
||||
*/
|
||||
protected ReadWriteLock getReadWriteLock() throws FieldAccessException {
|
||||
try {
|
||||
// Cache the read write lock
|
||||
if (readWriteLock != null)
|
||||
return readWriteLock;
|
||||
else if (READ_WRITE_LOCK_FIELD != null)
|
||||
return readWriteLock = (ReadWriteLock) FieldUtils.readField(READ_WRITE_LOCK_FIELD, handle, true);
|
||||
else
|
||||
return readWriteLock = new ReentrantReadWriteLock();
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new FieldAccessException("Unable to read lock field.", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the underlying map of key values that stores watchable objects.
|
||||
* @return A map of watchable objects.
|
||||
* @throws FieldAccessException If we don't have permission to perform reflection.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
protected Map<Integer, Object> getWatchableObjectMap() throws FieldAccessException {
|
||||
if (watchableObjects == null)
|
||||
watchableObjects = (Map<Integer, Object>) VALUE_MAP_ACCESSOR.get(handle);
|
||||
return watchableObjects;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the data watcher associated with an entity.
|
||||
* @param entity - the entity to read from.
|
||||
* @return Associated data watcher.
|
||||
* @throws FieldAccessException Reflection failed.
|
||||
*/
|
||||
public static WrappedDataWatcher getEntityWatcher(Entity entity) throws FieldAccessException {
|
||||
if (ENTITY_DATA_FIELD == null)
|
||||
ENTITY_DATA_FIELD = FuzzyReflection.fromClass(MinecraftReflection.getEntityClass(), true).
|
||||
getFieldByType("datawatcher", MinecraftReflection.getDataWatcherClass());
|
||||
|
||||
BukkitUnwrapper unwrapper = new BukkitUnwrapper();
|
||||
|
||||
try {
|
||||
Object nsmWatcher = FieldUtils.readField(ENTITY_DATA_FIELD, unwrapper.unwrapItem(entity), true);
|
||||
|
||||
if (nsmWatcher != null)
|
||||
return new WrappedDataWatcher(nsmWatcher);
|
||||
else
|
||||
return null;
|
||||
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new FieldAccessException("Cannot access DataWatcher field.", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when a data watcher is first used.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private static void initialize() throws FieldAccessException {
|
||||
// This method should only be run once, even if an exception is thrown
|
||||
if (!HAS_INITIALIZED)
|
||||
HAS_INITIALIZED = true;
|
||||
else
|
||||
return;
|
||||
|
||||
FuzzyReflection fuzzy = FuzzyReflection.fromClass(MinecraftReflection.getDataWatcherClass(), true);
|
||||
|
||||
for (Field lookup : fuzzy.getFieldListByType(Map.class)) {
|
||||
if (Modifier.isStatic(lookup.getModifiers())) {
|
||||
// This must be the type map
|
||||
TYPE_MAP_ACCESSOR = Accessors.getFieldAccessor(lookup, true);
|
||||
} else {
|
||||
// If not, then we're probably dealing with the value map
|
||||
VALUE_MAP_ACCESSOR = Accessors.getFieldAccessor(lookup, true);
|
||||
}
|
||||
}
|
||||
// Spigot workaround (not necessary
|
||||
initializeSpigot(fuzzy);
|
||||
|
||||
// Any custom types
|
||||
CUSTOM_MAP = initializeCustom();
|
||||
|
||||
// Initialize static type type
|
||||
TYPE_MAP = (Map<Class<?>, Integer>) TYPE_MAP_ACCESSOR.get(null);
|
||||
|
||||
try {
|
||||
READ_WRITE_LOCK_FIELD = fuzzy.getFieldByType("readWriteLock", ReadWriteLock.class);
|
||||
} catch (IllegalArgumentException e) {
|
||||
// It's not a big deal
|
||||
}
|
||||
|
||||
// Check for the entity field as well
|
||||
if (MinecraftReflection.isUsingNetty()) {
|
||||
ENTITY_FIELD = fuzzy.getFieldByType("entity", MinecraftReflection.getEntityClass());
|
||||
ENTITY_FIELD.setAccessible(true);
|
||||
}
|
||||
initializeMethods(fuzzy);
|
||||
}
|
||||
|
||||
// For Spigot's bountiful update patch
|
||||
private static Map<Class<?>, Integer> initializeCustom() {
|
||||
Map<Class<?>, Integer> map = Maps.newHashMap();
|
||||
|
||||
for (CustomType type : CustomType.values()) {
|
||||
if (type.getSpigotClass() != null) {
|
||||
map.put(type.getSpigotClass(), type.getTypeId());
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
// TODO: Remove, as this was fixed in build #1189 of Spigot
|
||||
private static void initializeSpigot(FuzzyReflection fuzzy) {
|
||||
// See if the workaround is needed
|
||||
if (TYPE_MAP_ACCESSOR != null && VALUE_MAP_ACCESSOR != null)
|
||||
return;
|
||||
|
||||
for (Field lookup : fuzzy.getFields()) {
|
||||
final Class<?> type = lookup.getType();
|
||||
|
||||
if (TroveWrapper.isTroveClass(type)) {
|
||||
// Create a wrapper accessor
|
||||
final ReadOnlyFieldAccessor accessor = TroveWrapper.wrapMapField(
|
||||
Accessors.getFieldAccessor(lookup, true), new Function<Integer, Integer>() {
|
||||
@Override
|
||||
public Integer apply(@Nullable Integer value) {
|
||||
// Do not use zero for no entry value
|
||||
if (value == 0)
|
||||
return -1;
|
||||
return value;
|
||||
}
|
||||
});
|
||||
|
||||
if (Modifier.isStatic(lookup.getModifiers())) {
|
||||
TYPE_MAP_ACCESSOR = accessor;
|
||||
} else {
|
||||
VALUE_MAP_ACCESSOR = accessor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (TYPE_MAP_ACCESSOR == null)
|
||||
throw new IllegalArgumentException("Unable to find static type map.");
|
||||
if (VALUE_MAP_ACCESSOR == null)
|
||||
throw new IllegalArgumentException("Unable to find static value map.");
|
||||
}
|
||||
|
||||
private static void initializeMethods(FuzzyReflection fuzzy) {
|
||||
List<Method> candidates = fuzzy.getMethodListByParameters(Void.TYPE,
|
||||
new Class<?>[] { int.class, Object.class});
|
||||
|
||||
// Load the get-method
|
||||
try {
|
||||
GET_KEY_VALUE_METHOD = fuzzy.getMethodByParameters(
|
||||
"getWatchableObject", MinecraftReflection.getWatchableObjectClass(), new Class[] { int.class });
|
||||
GET_KEY_VALUE_METHOD.setAccessible(true);
|
||||
|
||||
} catch (IllegalArgumentException e) {
|
||||
// Use the fallback method
|
||||
}
|
||||
|
||||
for (Method method : candidates) {
|
||||
if (!method.getName().startsWith("watch")) {
|
||||
CREATE_KEY_VALUE_METHOD = method;
|
||||
} else {
|
||||
UPDATE_KEY_VALUE_METHOD = method;
|
||||
}
|
||||
}
|
||||
|
||||
// Did we succeed?
|
||||
if (UPDATE_KEY_VALUE_METHOD == null || CREATE_KEY_VALUE_METHOD == null) {
|
||||
// Go by index instead
|
||||
if (candidates.size() > 1) {
|
||||
CREATE_KEY_VALUE_METHOD = candidates.get(0);
|
||||
UPDATE_KEY_VALUE_METHOD = candidates.get(1);
|
||||
} else {
|
||||
throw new IllegalStateException("Unable to find create and update watchable object. Update ProtocolLib.");
|
||||
}
|
||||
|
||||
// Be a little scientist - see if this in fact IS the right way around
|
||||
try {
|
||||
WrappedDataWatcher watcher = new WrappedDataWatcher();
|
||||
watcher.setObject(0, 0);
|
||||
watcher.setObject(0, 1);
|
||||
|
||||
if (watcher.getInteger(0) != 1) {
|
||||
throw new IllegalStateException("This cannot be!");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// Nope
|
||||
UPDATE_KEY_VALUE_METHOD = candidates.get(0);
|
||||
CREATE_KEY_VALUE_METHOD = candidates.get(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<WrappedWatchableObject> iterator() {
|
||||
// We'll wrap the iterator instead of creating a new list every time
|
||||
return Iterators.transform(getWatchableObjectMap().values().iterator(),
|
||||
new Function<Object, WrappedWatchableObject>() {
|
||||
|
||||
@Override
|
||||
public WrappedWatchableObject apply(@Nullable Object item) {
|
||||
if (item != null)
|
||||
return new WrappedWatchableObject(item);
|
||||
else
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a view of this DataWatcher as a map.
|
||||
* <p>
|
||||
* Any changes to the map will be reflected in this DataWatcher, and vice versa.
|
||||
* @return A view of the data watcher as a map.
|
||||
*/
|
||||
public Map<Integer, WrappedWatchableObject> asMap() {
|
||||
// Construct corresponding map
|
||||
if (mapView == null) {
|
||||
mapView = new ConvertedMap<Integer, Object, WrappedWatchableObject>(getWatchableObjectMap()) {
|
||||
@Override
|
||||
protected Object toInner(WrappedWatchableObject outer) {
|
||||
if (outer == null)
|
||||
return null;
|
||||
return outer.getHandle();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected WrappedWatchableObject toOuter(Object inner) {
|
||||
if (inner == null)
|
||||
return null;
|
||||
return new WrappedWatchableObject(inner);
|
||||
}
|
||||
};
|
||||
}
|
||||
return mapView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return asMap().toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the entity associated with this data watcher.
|
||||
* <p>
|
||||
* <b>Warning:</b> This is only supported on 1.7.2 and above.
|
||||
* @return The entity, or NULL.
|
||||
*/
|
||||
public Entity getEntity() {
|
||||
if (!MinecraftReflection.isUsingNetty())
|
||||
throw new IllegalStateException("This method is only supported on 1.7.2 and above.");
|
||||
|
||||
try {
|
||||
return (Entity) MinecraftReflection.getBukkitEntity(ENTITY_FIELD.get(handle));
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Unable to retrieve entity.", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the entity associated with this data watcher.
|
||||
* <p>
|
||||
* <b>Warning:</b> This is only supported on 1.7.2 and above.
|
||||
* @param entity - the new entity.
|
||||
*/
|
||||
public void setEntity(Entity entity) {
|
||||
if (!MinecraftReflection.isUsingNetty())
|
||||
throw new IllegalStateException("This method is only supported on 1.7.2 and above.");
|
||||
|
||||
try {
|
||||
ENTITY_FIELD.set(handle, BukkitUnwrapper.getInstance().unwrapItem(entity));
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Unable to set entity.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
-466
@@ -1,466 +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.wrappers;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import com.comphenix.protocol.annotations.Spigot;
|
||||
import com.comphenix.protocol.reflect.EquivalentConverter;
|
||||
import com.comphenix.protocol.reflect.FieldAccessException;
|
||||
import com.comphenix.protocol.reflect.StructureModifier;
|
||||
import com.comphenix.protocol.reflect.instances.DefaultInstances;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
import com.comphenix.protocol.wrappers.WrappedDataWatcher.CustomType;
|
||||
import com.google.common.base.Objects;
|
||||
|
||||
/**
|
||||
* Represents a watchable object.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public class WrappedWatchableObject extends AbstractWrapper {
|
||||
// Whether or not the reflection machinery has been initialized
|
||||
private static boolean hasInitialized;
|
||||
|
||||
// The field containing the value itself
|
||||
private static StructureModifier<Object> baseModifier;
|
||||
|
||||
// Used to create new watchable objects
|
||||
private static Constructor<?> watchableConstructor;
|
||||
|
||||
// The watchable object class type
|
||||
private static Class<?> watchableObjectClass;
|
||||
|
||||
protected StructureModifier<Object> modifier;
|
||||
|
||||
// Type of the stored value
|
||||
private Class<?> typeClass;
|
||||
|
||||
/**
|
||||
* Wrap a given raw Minecraft watchable object.
|
||||
* @param handle - the raw watchable object to wrap.
|
||||
*/
|
||||
public WrappedWatchableObject(Object handle) {
|
||||
super(MinecraftReflection.getWatchableObjectClass());
|
||||
load(handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a watchable object from an index and a given value.
|
||||
* @param index - the index.
|
||||
* @param value - non-null value of specific types.
|
||||
*/
|
||||
public WrappedWatchableObject(int index, Object value) {
|
||||
super(MinecraftReflection.getWatchableObjectClass());
|
||||
|
||||
if (value == null)
|
||||
throw new IllegalArgumentException("Value cannot be NULL.");
|
||||
|
||||
// Get the correct type ID
|
||||
Integer typeID = WrappedDataWatcher.getTypeID(value.getClass());
|
||||
|
||||
if (typeID != null) {
|
||||
if (watchableConstructor == null) {
|
||||
try {
|
||||
watchableConstructor = MinecraftReflection.getWatchableObjectClass().
|
||||
getConstructor(int.class, int.class, Object.class);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Cannot get the WatchableObject(int, int, Object) constructor.", e);
|
||||
}
|
||||
}
|
||||
|
||||
// Create the object
|
||||
try {
|
||||
load(watchableConstructor.newInstance(typeID, index, getUnwrapped(value)));
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Cannot construct underlying WatchableObject.", e);
|
||||
}
|
||||
} else {
|
||||
throw new IllegalArgumentException("Cannot watch the type " + value.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a custom watchable object from an index, value and custom type.
|
||||
* @param index - the index.
|
||||
* @param primary - non-null value of specific types.
|
||||
* @param secondary - optional secondary value, if the type can store it.
|
||||
* @param type - the custom Spigot type.
|
||||
*/
|
||||
@Spigot(minimumBuild = 1628)
|
||||
public WrappedWatchableObject(int index, Object value, Object secondary, CustomType type) {
|
||||
this(index, type.newInstance(value, secondary));
|
||||
}
|
||||
|
||||
// Wrap a NMS object
|
||||
private void load(Object handle) {
|
||||
initialize();
|
||||
this.handle = handle;
|
||||
this.modifier = baseModifier.withTarget(handle);
|
||||
|
||||
// Make sure the type is correct
|
||||
if (!watchableObjectClass.isAssignableFrom(handle.getClass())) {
|
||||
throw new ClassCastException("Cannot cast the class " + handle.getClass().getName() +
|
||||
" to " + watchableObjectClass.getName());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize reflection machinery.
|
||||
*/
|
||||
private static void initialize() {
|
||||
if (!hasInitialized) {
|
||||
hasInitialized = true;
|
||||
watchableObjectClass = MinecraftReflection.getWatchableObjectClass();
|
||||
baseModifier = new StructureModifier<Object>(watchableObjectClass, null, false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the custom type of this object.
|
||||
* @return The custom type, or NULL if not applicable.
|
||||
*/
|
||||
@Spigot(minimumBuild = 1628)
|
||||
public CustomType getCustomType() {
|
||||
return CustomType.fromValue(getValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the correct super type of the current value.
|
||||
* @return Super type.
|
||||
* @throws FieldAccessException Unable to read values.
|
||||
*/
|
||||
public Class<?> getType() throws FieldAccessException {
|
||||
return getWrappedType(getTypeRaw());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the correct super type of the current value, given the raw NMS object.
|
||||
* @return Super type.
|
||||
* @throws FieldAccessException Unable to read values.
|
||||
*/
|
||||
private Class<?> getTypeRaw() throws FieldAccessException {
|
||||
if (typeClass == null) {
|
||||
typeClass = WrappedDataWatcher.getTypeClass(getTypeID());
|
||||
|
||||
if (typeClass == null) {
|
||||
throw new IllegalStateException("Unrecognized data type: " + getTypeID());
|
||||
}
|
||||
}
|
||||
|
||||
return typeClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the index of this watchable object. This is used to identify a value.
|
||||
* @return Object index.
|
||||
* @throws FieldAccessException Reflection failed.
|
||||
*/
|
||||
public int getIndex() throws FieldAccessException {
|
||||
return modifier.<Integer>withType(int.class).read(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the the index of this watchable object.
|
||||
* @param index - the new object index.
|
||||
* @throws FieldAccessException Reflection failed.
|
||||
*/
|
||||
public void setIndex(int index) throws FieldAccessException {
|
||||
modifier.<Integer>withType(int.class).write(1, index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the type ID of a watchable object.
|
||||
* <p>
|
||||
* <table border=1>
|
||||
* <tbody>
|
||||
* <tr>
|
||||
* <th>Type ID</th>
|
||||
* <th>Data Type</th>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>0</td>
|
||||
* <td>Byte</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>1</td>
|
||||
* <td>Short</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>2</td>
|
||||
* <td>Int</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>3</td>
|
||||
* <td>Float</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>4</td>
|
||||
* <td>{@link String}</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>5</td>
|
||||
* <td>{@link org.bukkit.inventory.ItemStack ItemStack}</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>6<sup>*</sup></td>
|
||||
* <td>{@link com.comphenix.protocol.wrappers.ChunkPosition ChunkPosition}</td>
|
||||
* </tr>
|
||||
* </tbody>
|
||||
* </table>
|
||||
* @return Type ID that identifies the type of the value.
|
||||
* @throws FieldAccessException Reflection failed.
|
||||
*/
|
||||
public int getTypeID() throws FieldAccessException {
|
||||
return modifier.<Integer>withType(int.class).read(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the type ID of a watchable object.
|
||||
* @see {@link #getTypeID()} for more information.
|
||||
* @param id - the new ID.
|
||||
* @throws FieldAccessException Reflection failed.
|
||||
*/
|
||||
public void setTypeID(int id) throws FieldAccessException {
|
||||
modifier.<Integer>withType(int.class).write(0, id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the value field.
|
||||
* @param newValue - new value.
|
||||
* @throws FieldAccessException Unable to use reflection.
|
||||
*/
|
||||
public void setValue(Object newValue) throws FieldAccessException {
|
||||
setValue(newValue, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the value field.
|
||||
* @param newValue - new value.
|
||||
* @param updateClient - whether or not to update listening clients.
|
||||
* @throws FieldAccessException Unable to use reflection.
|
||||
*/
|
||||
public void setValue(Object newValue, boolean updateClient) throws FieldAccessException {
|
||||
// Verify a few quick things
|
||||
if (newValue == null)
|
||||
throw new IllegalArgumentException("Cannot watch a NULL value.");
|
||||
if (!getType().isAssignableFrom(newValue.getClass()))
|
||||
throw new IllegalArgumentException("Object " + newValue + " must be of type " + getType().getName());
|
||||
|
||||
// See if we should update the client to
|
||||
if (updateClient)
|
||||
setDirtyState(true);
|
||||
|
||||
// Use the modifier to set the value
|
||||
modifier.withType(Object.class).write(0, getUnwrapped(newValue));
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the underlying value field.
|
||||
* @return The underlying value.
|
||||
*/
|
||||
private Object getNMSValue() {
|
||||
return modifier.withType(Object.class).read(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the value field.
|
||||
* @return The watched value.
|
||||
* @throws FieldAccessException Unable to use reflection.
|
||||
*/
|
||||
public Object getValue() throws FieldAccessException {
|
||||
return getWrapped(modifier.withType(Object.class).read(0));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the secondary value associated with this watchable object.
|
||||
* <p>
|
||||
* This is only applicable for certain {@link CustomType}.
|
||||
* @return The secondary value, or NULL if not found.
|
||||
*/
|
||||
@Spigot(minimumBuild = 1628)
|
||||
public Object getSecondaryValue() {
|
||||
CustomType type = getCustomType();
|
||||
return type != null ? type.getSecondary(getValue()) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the secondary value.
|
||||
* @param secondary - the secondary value.
|
||||
* @throws IllegalStateException If this watchable object does not have a secondary value.
|
||||
*/
|
||||
@Spigot(minimumBuild = 1628)
|
||||
public void setSecondaryValue(Object secondary) {
|
||||
CustomType type = getCustomType();
|
||||
|
||||
if (type == null) {
|
||||
throw new IllegalStateException(this + " does not have a custom type.");
|
||||
}
|
||||
type.setSecondary(getValue(), secondary);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether or not the value must be synchronized with the client.
|
||||
* @param dirty - TRUE if the value should be synchronized, FALSE otherwise.
|
||||
* @throws FieldAccessException Unable to use reflection.
|
||||
*/
|
||||
public void setDirtyState(boolean dirty) throws FieldAccessException {
|
||||
modifier.<Boolean>withType(boolean.class).write(0, dirty);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve whether or not the value must be synchronized with the client.
|
||||
* @return TRUE if the value should be synchronized, FALSE otherwise.
|
||||
* @throws FieldAccessException Unable to use reflection.
|
||||
*/
|
||||
public boolean getDirtyState() throws FieldAccessException {
|
||||
return modifier.<Boolean>withType(boolean.class).read(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the wrapped object value, if needed.
|
||||
* @param value - the raw NMS object to wrap.
|
||||
* @return The wrapped object.
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
static Object getWrapped(Object value) {
|
||||
// Handle the special cases
|
||||
if (MinecraftReflection.isItemStack(value)) {
|
||||
return BukkitConverters.getItemStackConverter().getSpecific(value);
|
||||
} else if (MinecraftReflection.isChunkCoordinates(value)) {
|
||||
return new WrappedChunkCoordinate((Comparable) value);
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the wrapped type, if needed.
|
||||
* @param wrapped - the wrapped class type.
|
||||
* @return The wrapped class type.
|
||||
*/
|
||||
static Class<?> getWrappedType(Class<?> unwrapped) {
|
||||
if (unwrapped.equals(MinecraftReflection.getChunkPositionClass()))
|
||||
return ChunkPosition.class;
|
||||
else if (unwrapped.equals(MinecraftReflection.getChunkCoordinatesClass()))
|
||||
return WrappedChunkCoordinate.class;
|
||||
else if (unwrapped.equals(MinecraftReflection.getItemStackClass()))
|
||||
return ItemStack.class;
|
||||
else
|
||||
return unwrapped;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the raw NMS value.
|
||||
* @param wrapped - the wrapped position.
|
||||
* @return The raw NMS object.
|
||||
*/
|
||||
static Object getUnwrapped(Object wrapped) {
|
||||
// Convert special cases
|
||||
if (wrapped instanceof ChunkPosition)
|
||||
return ChunkPosition.getConverter().getGeneric(
|
||||
MinecraftReflection.getChunkPositionClass(), (ChunkPosition) wrapped);
|
||||
else if (wrapped instanceof WrappedChunkCoordinate)
|
||||
return ((WrappedChunkCoordinate) wrapped).getHandle();
|
||||
else if (wrapped instanceof ItemStack)
|
||||
return BukkitConverters.getItemStackConverter().getGeneric(
|
||||
MinecraftReflection.getItemStackClass(), (ItemStack) wrapped);
|
||||
else
|
||||
return wrapped;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the unwrapped type, if needed.
|
||||
* @param wrapped - the unwrapped class type.
|
||||
* @return The unwrapped class type.
|
||||
*/
|
||||
static Class<?> getUnwrappedType(Class<?> wrapped) {
|
||||
if (wrapped.equals(ChunkPosition.class))
|
||||
return MinecraftReflection.getChunkPositionClass();
|
||||
else if (wrapped.equals(WrappedChunkCoordinate.class))
|
||||
return MinecraftReflection.getChunkCoordinatesClass();
|
||||
else if (ItemStack.class.isAssignableFrom(wrapped))
|
||||
return MinecraftReflection.getItemStackClass();
|
||||
else
|
||||
return wrapped;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone the current wrapped watchable object, along with any contained objects.
|
||||
* @return A deep clone of the current watchable object.
|
||||
* @throws FieldAccessException If we're unable to use reflection.
|
||||
*/
|
||||
public WrappedWatchableObject deepClone() throws FieldAccessException {
|
||||
WrappedWatchableObject clone = new WrappedWatchableObject(
|
||||
DefaultInstances.DEFAULT.getDefault(MinecraftReflection.getWatchableObjectClass()));
|
||||
|
||||
clone.setDirtyState(getDirtyState());
|
||||
clone.setIndex(getIndex());
|
||||
clone.setTypeID(getTypeID());
|
||||
clone.setValue(getClonedValue(), false);
|
||||
return clone;
|
||||
}
|
||||
|
||||
// Helper
|
||||
Object getClonedValue() throws FieldAccessException {
|
||||
Object value = getNMSValue();
|
||||
|
||||
// Only a limited set of references types are supported
|
||||
if (MinecraftReflection.isChunkPosition(value)) {
|
||||
EquivalentConverter<ChunkPosition> converter = ChunkPosition.getConverter();
|
||||
return converter.getGeneric(MinecraftReflection.getChunkPositionClass(), converter.getSpecific(value));
|
||||
} else if (MinecraftReflection.isItemStack(value)) {
|
||||
return MinecraftReflection.getMinecraftItemStack(MinecraftReflection.getBukkitItemStack(value).clone());
|
||||
} else {
|
||||
// A string or primitive wrapper, which are all immutable.
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
// Quick checks
|
||||
if (obj == this)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
|
||||
if (obj instanceof WrappedWatchableObject) {
|
||||
WrappedWatchableObject other = (WrappedWatchableObject) obj;
|
||||
|
||||
return Objects.equal(getIndex(), other.getIndex()) &&
|
||||
Objects.equal(getTypeID(), other.getTypeID()) &&
|
||||
Objects.equal(getValue(), other.getValue());
|
||||
}
|
||||
|
||||
// No, this is not equivalent
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hashCode(getIndex(), getTypeID(), getValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("[%s: %s (%s)]", getIndex(), getValue(), getType().getSimpleName());
|
||||
}
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
package com.comphenix.protocol;
|
||||
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
import net.minecraft.server.v1_7_R4.Block;
|
||||
import net.minecraft.server.v1_7_R4.Item;
|
||||
import net.minecraft.server.v1_7_R4.StatisticList;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.craftbukkit.v1_7_R4.inventory.CraftItemFactory;
|
||||
import org.bukkit.inventory.ItemFactory;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
|
||||
import com.comphenix.protocol.reflect.FieldUtils;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
// Will have to be updated for every version though
|
||||
|
||||
/**
|
||||
* Used to ensure that ProtocolLib and Bukkit is prepared to be tested.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public class BukkitInitialization {
|
||||
private static boolean initialized;
|
||||
|
||||
/**
|
||||
* Initialize Bukkit and ProtocolLib such that we can perfrom unit testing.
|
||||
* @throws IllegalAccessException If we are unable to initialize Bukkit.
|
||||
*/
|
||||
public static void initializeItemMeta() throws IllegalAccessException {
|
||||
if (!initialized) {
|
||||
// Denote that we're done
|
||||
initialized = true;
|
||||
|
||||
initializePackage();
|
||||
|
||||
try {
|
||||
Block.p();
|
||||
Item.l();
|
||||
StatisticList.a();
|
||||
} catch (Exception e) {
|
||||
// Swallow
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
// Mock the server object
|
||||
Server mockedServer = mock(Server.class);
|
||||
ItemFactory mockedFactory = mock(CraftItemFactory.class);
|
||||
ItemMeta mockedMeta = mock(ItemMeta.class);
|
||||
|
||||
when(mockedServer.getItemFactory()).thenReturn(mockedFactory);
|
||||
when(mockedServer.isPrimaryThread()).thenReturn(true);
|
||||
when(mockedFactory.getItemMeta(any(Material.class))).thenReturn(mockedMeta);
|
||||
|
||||
// Inject this fake server
|
||||
FieldUtils.writeStaticField(Bukkit.class, "server", mockedServer, true);
|
||||
|
||||
// And the fake item factory
|
||||
FieldUtils.writeStaticField(CraftItemFactory.class, "instance", mockedFactory, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that package names are correctly set up.
|
||||
*/
|
||||
public static void initializePackage() {
|
||||
// Initialize reflection
|
||||
MinecraftReflection.setMinecraftPackage("net.minecraft.server.v1_7_R4", "org.bukkit.craftbukkit.v1_7_R4");
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package com.comphenix.protocol;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.comphenix.protocol.PacketType.Protocol;
|
||||
import com.comphenix.protocol.PacketType.Sender;
|
||||
|
||||
public class PacketTypeTest {
|
||||
@BeforeClass
|
||||
public static void initializeReflection() throws IllegalAccessException {
|
||||
BukkitInitialization.initializePackage();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindCurrent() {
|
||||
assertEquals(PacketType.Play.Client.STEER_VEHICLE, PacketType.findCurrent(Protocol.PLAY, Sender.CLIENT, 12));
|
||||
}
|
||||
}
|
||||
@@ -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.events;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import net.minecraft.server.v1_7_R4.AttributeModifier;
|
||||
import net.minecraft.server.v1_7_R4.AttributeSnapshot;
|
||||
import net.minecraft.server.v1_7_R4.PacketPlayOutUpdateAttributes;
|
||||
|
||||
import org.apache.commons.lang.SerializationUtils;
|
||||
import org.apache.commons.lang.builder.ToStringBuilder;
|
||||
import org.apache.commons.lang.builder.ToStringStyle;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.WorldType;
|
||||
import org.bukkit.craftbukkit.v1_7_R4.inventory.CraftItemFactory;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.potion.PotionEffect;
|
||||
import org.bukkit.potion.PotionEffectType;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.powermock.core.classloader.annotations.PowerMockIgnore;
|
||||
import org.powermock.core.classloader.annotations.PrepareForTest;
|
||||
|
||||
import com.comphenix.protocol.BukkitInitialization;
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.PacketType.Sender;
|
||||
import com.comphenix.protocol.injector.PacketConstructor;
|
||||
import com.comphenix.protocol.reflect.StructureModifier;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
import com.comphenix.protocol.wrappers.ChunkPosition;
|
||||
import com.comphenix.protocol.wrappers.WrappedChatComponent;
|
||||
import com.comphenix.protocol.wrappers.WrappedDataWatcher;
|
||||
import com.comphenix.protocol.wrappers.WrappedGameProfile;
|
||||
import com.comphenix.protocol.wrappers.WrappedWatchableObject;
|
||||
import com.comphenix.protocol.wrappers.nbt.NbtCompound;
|
||||
import com.comphenix.protocol.wrappers.nbt.NbtFactory;
|
||||
import com.google.common.collect.Lists;
|
||||
// Will have to be updated for every version though
|
||||
|
||||
// Ensure that the CraftItemFactory is mockable
|
||||
@RunWith(org.powermock.modules.junit4.PowerMockRunner.class)
|
||||
@PowerMockIgnore({ "org.apache.log4j.*", "org.apache.logging.*", "org.bukkit.craftbukkit.libs.jline.*" })
|
||||
@PrepareForTest(CraftItemFactory.class)
|
||||
public class PacketContainerTest {
|
||||
// // Helper converters
|
||||
// private EquivalentConverter<WrappedDataWatcher> watchConvert = BukkitConverters.getDataWatcherConverter();
|
||||
// private EquivalentConverter<ItemStack> itemConvert = BukkitConverters.getItemStackConverter();
|
||||
|
||||
@BeforeClass
|
||||
public static void initializeBukkit() throws IllegalAccessException {
|
||||
BukkitInitialization.initializeItemMeta();
|
||||
}
|
||||
|
||||
private <T> void testPrimitive(StructureModifier<T> modifier, int index, T initialValue, T testValue) {
|
||||
// Check initial value
|
||||
assertEquals(initialValue, modifier.read(index));
|
||||
|
||||
// Test assignment
|
||||
modifier.write(index, testValue);
|
||||
assertEquals(testValue, modifier.read(0));
|
||||
}
|
||||
|
||||
private <T> void testObjectArray(StructureModifier<T[]> modifier, int index, T[] initialValue, T[] testValue) {
|
||||
// Check initial value
|
||||
assertNull(modifier.read(index));
|
||||
modifier.writeDefaults();
|
||||
|
||||
// Test initial
|
||||
assertArrayEquals(initialValue, modifier.read(index));
|
||||
|
||||
// Test assignment
|
||||
modifier.write(index, testValue);
|
||||
assertArrayEquals(testValue, modifier.read(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetByteArrays() {
|
||||
// Contains a byte array we will test
|
||||
PacketContainer customPayload = new PacketContainer(PacketType.Play.Server.CUSTOM_PAYLOAD);
|
||||
StructureModifier<byte[]> bytes = customPayload.getByteArrays();
|
||||
byte[] testArray = new byte[] { 1, 2, 3 };
|
||||
|
||||
// It's NULL at first
|
||||
assertArrayEquals(null, bytes.read(0));
|
||||
customPayload.getModifier().writeDefaults();
|
||||
|
||||
// Then it should create an empty array
|
||||
assertArrayEquals(new byte[0], bytes.read(0));
|
||||
|
||||
// Check and see if we can write to it
|
||||
bytes.write(0, testArray);
|
||||
assertArrayEquals(testArray, bytes.read(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetBytes() {
|
||||
PacketContainer spawnMob = new PacketContainer(PacketType.Play.Server.SPAWN_ENTITY_LIVING);
|
||||
testPrimitive(spawnMob.getBytes(), 0, (byte)0, (byte)1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetShorts() {
|
||||
PacketContainer itemData = new PacketContainer(PacketType.Play.Server.TRANSACTION);
|
||||
testPrimitive(itemData.getShorts(), 0, (short)0, (short)1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetIntegers() {
|
||||
PacketContainer updateSign = new PacketContainer(PacketType.Play.Server.UPDATE_SIGN);
|
||||
testPrimitive(updateSign.getIntegers(), 0, 0, 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetLongs() {
|
||||
PacketContainer updateTime = new PacketContainer(PacketType.Play.Server.UPDATE_TIME);
|
||||
testPrimitive(updateTime.getLongs(), 0, (long)0, (long)1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetFloat() {
|
||||
PacketContainer explosion = new PacketContainer(PacketType.Play.Server.EXPLOSION);
|
||||
testPrimitive(explosion.getFloat(), 0, (float)0, (float)0.8);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetDoubles() {
|
||||
PacketContainer explosion = new PacketContainer(PacketType.Play.Server.EXPLOSION);
|
||||
testPrimitive(explosion.getDoubles(), 0, (double)0, 0.8);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetStrings() {
|
||||
PacketContainer explosion = new PacketContainer(PacketType.Play.Client.CHAT);
|
||||
testPrimitive(explosion.getStrings(), 0, null, "hello");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetStringArrays() {
|
||||
PacketContainer explosion = new PacketContainer(PacketType.Play.Server.UPDATE_SIGN);
|
||||
testObjectArray(explosion.getStringArrays(), 0, new String[0], new String[] { "hello", "world" });
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetIntegerArrays() {
|
||||
// Contains a byte array we will test
|
||||
PacketContainer mapChunkBulk = new PacketContainer(PacketType.Play.Server.MAP_CHUNK_BULK);
|
||||
StructureModifier<int[]> integers = mapChunkBulk.getIntegerArrays();
|
||||
int[] testArray = new int[] { 1, 2, 3 };
|
||||
|
||||
// Pre and post conditions
|
||||
assertArrayEquals(null, integers.read(0));
|
||||
mapChunkBulk.getModifier().writeDefaults();
|
||||
assertArrayEquals(new int[0], integers.read(0));
|
||||
|
||||
integers.write(0, testArray);
|
||||
assertArrayEquals(testArray, integers.read(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetItemModifier() {
|
||||
PacketContainer windowClick = new PacketContainer(PacketType.Play.Client.WINDOW_CLICK);
|
||||
|
||||
StructureModifier<ItemStack> items = windowClick.getItemModifier();
|
||||
ItemStack goldAxe = new ItemStack(Material.GOLD_AXE);
|
||||
|
||||
assertNotNull(goldAxe.getType());
|
||||
assertNull(items.read(0));
|
||||
|
||||
// Insert the goldaxe and check if it's there
|
||||
items.write(0, goldAxe);
|
||||
assertTrue("Item " + goldAxe + " != " + items.read(0), equivalentItem(goldAxe, items.read(0)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetItemArrayModifier() {
|
||||
PacketContainer windowItems = new PacketContainer(PacketType.Play.Server.WINDOW_ITEMS);
|
||||
StructureModifier<ItemStack[]> itemAccess = windowItems.getItemArrayModifier();
|
||||
|
||||
ItemStack[] itemArray = new ItemStack[] {
|
||||
new ItemStack(Material.GOLD_AXE),
|
||||
new ItemStack(Material.DIAMOND_AXE)
|
||||
};
|
||||
|
||||
assertNull(itemAccess.read(0));
|
||||
|
||||
// Insert and check that it was succesful
|
||||
itemAccess.write(0, itemArray);
|
||||
|
||||
// Read back array
|
||||
ItemStack[] comparision = itemAccess.read(0);
|
||||
assertEquals(itemArray.length, comparision.length);
|
||||
|
||||
// Check that it is equivalent
|
||||
for (int i = 0; i < itemArray.length; i++) {
|
||||
assertTrue(String.format("Array element %s is not the same: %s != %s",
|
||||
i, itemArray[i], comparision[i]), equivalentItem(itemArray[i], comparision[i]));
|
||||
}
|
||||
}
|
||||
|
||||
private boolean equivalentItem(ItemStack first, ItemStack second) {
|
||||
if (first == null) {
|
||||
return second == null;
|
||||
} else if (second == null) {
|
||||
return false;
|
||||
} else {
|
||||
return first.getType().equals(second.getType());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetWorldTypeModifier() {
|
||||
// Not used in Netty
|
||||
if (MinecraftReflection.isUsingNetty())
|
||||
return;
|
||||
|
||||
PacketContainer loginPacket = new PacketContainer(PacketType.Play.Server.LOGIN);
|
||||
StructureModifier<WorldType> worldAccess = loginPacket.getWorldTypeModifier();
|
||||
|
||||
WorldType testValue = WorldType.LARGE_BIOMES;
|
||||
|
||||
assertNull(worldAccess.read(0));
|
||||
|
||||
// Insert and read back
|
||||
worldAccess.write(0, testValue);
|
||||
assertEquals(testValue, worldAccess.read(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetNbtModifier() {
|
||||
PacketContainer updateTileEntity = new PacketContainer(PacketType.Play.Server.TILE_ENTITY_DATA);
|
||||
|
||||
NbtCompound compound = NbtFactory.ofCompound("test");
|
||||
compound.put("test", "name");
|
||||
compound.put(NbtFactory.ofList("ages", 1, 2, 3));
|
||||
|
||||
updateTileEntity.getNbtModifier().write(0, compound);
|
||||
|
||||
NbtCompound result = (NbtCompound) updateTileEntity.getNbtModifier().read(0);
|
||||
|
||||
assertEquals(compound.getString("test"), result.getString("test"));
|
||||
assertEquals(compound.getList("ages"), result.getList("ages"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetDataWatcherModifier() {
|
||||
PacketContainer mobSpawnPacket = new PacketContainer(PacketType.Play.Server.SPAWN_ENTITY_LIVING);
|
||||
StructureModifier<WrappedDataWatcher> watcherAccessor = mobSpawnPacket.getDataWatcherModifier();
|
||||
|
||||
WrappedDataWatcher dataWatcher = new WrappedDataWatcher();
|
||||
dataWatcher.setObject(1, 100);
|
||||
dataWatcher.setObject(2, 125);
|
||||
|
||||
assertNull(watcherAccessor.read(0));
|
||||
|
||||
// Insert and read back
|
||||
watcherAccessor.write(0, dataWatcher);
|
||||
assertEquals(dataWatcher, watcherAccessor.read(0));
|
||||
}
|
||||
|
||||
// Unfortunately, it might be too difficult to mock this one
|
||||
//
|
||||
// @Test
|
||||
// public void testGetEntityModifier() { }
|
||||
|
||||
// No packet expose this type directly.
|
||||
//
|
||||
// @Test
|
||||
// public void testGetPositionModifier() { }
|
||||
|
||||
@Test
|
||||
public void testGetPositionCollectionModifier() {
|
||||
PacketContainer explosionPacket = new PacketContainer(PacketType.Play.Server.EXPLOSION);
|
||||
StructureModifier<List<ChunkPosition>> positionAccessor = explosionPacket.getPositionCollectionModifier();
|
||||
|
||||
assertNull(positionAccessor.read(0));
|
||||
|
||||
List<ChunkPosition> positions = Lists.newArrayList();
|
||||
positions.add(new ChunkPosition(1, 2, 3));
|
||||
positions.add(new ChunkPosition(3, 4, 5));
|
||||
|
||||
// Insert and read back
|
||||
positionAccessor.write(0, positions);
|
||||
List<ChunkPosition> cloned = positionAccessor.read(0);
|
||||
|
||||
assertEquals(positions, cloned);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetWatchableCollectionModifier() {
|
||||
PacketContainer entityMetadata = new PacketContainer(PacketType.Play.Server.ENTITY_METADATA);
|
||||
StructureModifier<List<WrappedWatchableObject>> watchableAccessor =
|
||||
entityMetadata.getWatchableCollectionModifier();
|
||||
|
||||
assertNull(watchableAccessor.read(0));
|
||||
|
||||
WrappedDataWatcher watcher = new WrappedDataWatcher();
|
||||
watcher.setObject(1, 10);
|
||||
watcher.setObject(8, 10);
|
||||
|
||||
List<WrappedWatchableObject> list = watcher.getWatchableObjects();
|
||||
|
||||
// Insert and read back
|
||||
watchableAccessor.write(0, list);
|
||||
assertEquals(list, watchableAccessor.read(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGameProfiles() {
|
||||
PacketContainer spawnEntity = new PacketContainer(PacketType.Play.Server.NAMED_ENTITY_SPAWN);
|
||||
WrappedGameProfile profile = new WrappedGameProfile("d7047a08-3150-4aa8-a2f2-7c1e2b17e298", "name");
|
||||
spawnEntity.getGameProfiles().write(0, profile);
|
||||
|
||||
assertEquals(profile, spawnEntity.getGameProfiles().read(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChatComponents() {
|
||||
PacketContainer chatPacket = new PacketContainer(PacketType.Play.Server.CHAT);
|
||||
chatPacket.getChatComponents().write(0,
|
||||
WrappedChatComponent.fromChatMessage("You shall not " + ChatColor.ITALIC + "pass!")[0]);
|
||||
|
||||
assertEquals("{\"extra\":[\"You shall not \",{\"italic\":true,\"text\":\"pass!\"}],\"text\":\"\"}",
|
||||
chatPacket.getChatComponents().read(0).getJson());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSerialization() {
|
||||
PacketContainer chat = new PacketContainer(PacketType.Play.Client.CHAT);
|
||||
chat.getStrings().write(0, "Test");
|
||||
|
||||
PacketContainer copy = (PacketContainer) SerializationUtils.clone(chat);
|
||||
|
||||
assertEquals(PacketType.Play.Client.CHAT, copy.getType());
|
||||
assertEquals("Test", copy.getStrings().read(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAttributeList() {
|
||||
PacketContainer attribute = new PacketContainer(PacketType.Play.Server.UPDATE_ATTRIBUTES);
|
||||
attribute.getIntegers().write(0, 123); // Entity ID
|
||||
|
||||
// Initialize some test data
|
||||
List<AttributeModifier> modifiers = Lists.newArrayList(
|
||||
new AttributeModifier(UUID.randomUUID(), "Unknown synced attribute modifier", 10, 0));
|
||||
AttributeSnapshot snapshot = new AttributeSnapshot(
|
||||
(PacketPlayOutUpdateAttributes) attribute.getHandle(), "generic.Maxhealth", 20.0, modifiers);
|
||||
|
||||
attribute.getSpecificModifier(List.class).write(0, Lists.newArrayList(snapshot));
|
||||
PacketContainer cloned = attribute.deepClone();
|
||||
AttributeSnapshot clonedSnapshot = (AttributeSnapshot) cloned.getSpecificModifier(List.class).read(0).get(0);
|
||||
|
||||
assertEquals(
|
||||
ToStringBuilder.reflectionToString(snapshot, ToStringStyle.SHORT_PREFIX_STYLE),
|
||||
ToStringBuilder.reflectionToString(clonedSnapshot, ToStringStyle.SHORT_PREFIX_STYLE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBlocks() {
|
||||
PacketContainer blockAction = new PacketContainer(PacketType.Play.Server.BLOCK_ACTION);
|
||||
blockAction.getBlocks().write(0, Material.STONE);
|
||||
|
||||
assertEquals(Material.STONE, blockAction.getBlocks().read(0));
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Test
|
||||
public void testPotionEffect() {
|
||||
PotionEffect effect = new PotionEffect(PotionEffectType.FIRE_RESISTANCE, 20 * 60, 1);
|
||||
|
||||
// The constructor we want to call
|
||||
PacketConstructor creator = PacketConstructor.DEFAULT.withPacket(
|
||||
PacketType.Play.Server.ENTITY_EFFECT, new Class<?>[] { int.class, PotionEffect.class });
|
||||
PacketContainer packet = creator.createPacket(1, effect);
|
||||
|
||||
assertEquals(1, (int) packet.getIntegers().read(0));
|
||||
assertEquals(effect.getType().getId(), (byte) packet.getBytes().read(0));
|
||||
assertEquals(effect.getAmplifier(), (byte) packet.getBytes().read(1));
|
||||
assertEquals(effect.getDuration(), (short) packet.getShorts().read(0));
|
||||
}
|
||||
|
||||
// @Test
|
||||
// public void testDeepClone() {
|
||||
// // Try constructing all the packets
|
||||
// for (PacketType type : PacketType.values()) {
|
||||
// // Whether or not this packet has been registered
|
||||
// boolean registered = type.isSupported();
|
||||
//
|
||||
// try {
|
||||
// PacketContainer constructed = new PacketContainer(type);
|
||||
//
|
||||
// if (!registered) {
|
||||
// fail("Expected IllegalArgumentException(Packet " + type + " not registered");
|
||||
// }
|
||||
//
|
||||
// // Initialize default values
|
||||
// constructed.getModifier().writeDefaults();
|
||||
//
|
||||
// // Clone the packet
|
||||
// PacketContainer cloned = constructed.deepClone();
|
||||
//
|
||||
// // Make sure they're equivalent
|
||||
// StructureModifier<Object> firstMod = constructed.getModifier(), secondMod = cloned.getModifier();
|
||||
// assertEquals(firstMod.size(), secondMod.size());
|
||||
//
|
||||
// if (PacketType.Status.Server.OUT_SERVER_INFO.equals(type)) {
|
||||
// assertArrayEquals(SerializationUtils.serialize(constructed), SerializationUtils.serialize(cloned));
|
||||
//
|
||||
// } else {
|
||||
// // Make sure all the fields are equivalent
|
||||
// for (int i = 0; i < firstMod.size(); i++) {
|
||||
// if (firstMod.getField(i).getType().isArray())
|
||||
// assertArrayEquals(getArray(firstMod.read(i)), getArray(secondMod.read(i)));
|
||||
// else
|
||||
// testEquality(firstMod.read(i), secondMod.read(i));
|
||||
// }
|
||||
// }
|
||||
// } catch (IllegalArgumentException e) {
|
||||
// if (!registered) {
|
||||
// // Let the test pass
|
||||
// System.err.println("The packet ID " + type + " is not registered.");
|
||||
// // assertEquals(e.getMessage(), "The packet ID " + type + " is not registered.");
|
||||
// } else {
|
||||
// // Something is very wrong
|
||||
// throw e;
|
||||
// }
|
||||
// } catch (RuntimeException e) {
|
||||
// throw new RuntimeException("Failed to serialize packet " + type, e);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
@Test
|
||||
public void testPacketType() {
|
||||
assertEquals(PacketType.Legacy.Server.SET_CREATIVE_SLOT, PacketType.findLegacy(107, Sender.SERVER));
|
||||
}
|
||||
|
||||
// // Convert to objects that support equals()
|
||||
// private void testEquality(Object a, Object b) {
|
||||
// if (a != null && b != null) {
|
||||
// if (MinecraftReflection.isDataWatcher(a)) {
|
||||
// a = watchConvert.getSpecific(a);
|
||||
// b = watchConvert.getSpecific(b);
|
||||
// } else if (MinecraftReflection.isItemStack(a)) {
|
||||
// a = itemConvert.getSpecific(a);
|
||||
// b = itemConvert.getSpecific(b);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// assertEquals(a, b);
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Get the underlying array as an object array.
|
||||
// * @param val - array wrapped as an Object.
|
||||
// * @return An object array.
|
||||
// */
|
||||
// private Object[] getArray(Object val) {
|
||||
// if (val instanceof Object[])
|
||||
// return (Object[]) val;
|
||||
// if (val == null)
|
||||
// return null;
|
||||
//
|
||||
// int arrlength = Array.getLength(val);
|
||||
// Object[] outputArray = new Object[arrlength];
|
||||
//
|
||||
// for (int i = 0; i < arrlength; ++i)
|
||||
// outputArray[i] = Array.get(val, i);
|
||||
// return outputArray;
|
||||
// }
|
||||
}
|
||||
-24
@@ -1,24 +0,0 @@
|
||||
package com.comphenix.protocol.reflect.cloning;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.comphenix.protocol.BukkitInitialization;
|
||||
|
||||
public class AggregateClonerTest {
|
||||
@BeforeClass
|
||||
public static void initializeBukkit() throws IllegalAccessException {
|
||||
BukkitInitialization.initializePackage();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testArrays() {
|
||||
List<Integer> input = Arrays.asList(1, 2, 3);
|
||||
assertEquals(input, AggregateCloner.DEFAULT.clone(input));
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package com.comphenix.protocol.utility;
|
||||
|
||||
import org.junit.BeforeClass;
|
||||
|
||||
import com.comphenix.protocol.BukkitInitialization;
|
||||
|
||||
public class MinecraftMethodsTest {
|
||||
@BeforeClass
|
||||
public static void initializeReflection() throws IllegalAccessException {
|
||||
BukkitInitialization.initializePackage();
|
||||
}
|
||||
|
||||
// @Test
|
||||
// public void testSendPacketMethod() {
|
||||
// assertNotNull(MinecraftMethods.getSendPacketMethod());
|
||||
// }
|
||||
}
|
||||
-111
@@ -1,111 +0,0 @@
|
||||
package com.comphenix.protocol.utility;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import net.minecraft.server.v1_7_R4.ChatComponentText;
|
||||
import net.minecraft.server.v1_7_R4.ChatSerializer;
|
||||
import net.minecraft.server.v1_7_R4.IChatBaseComponent;
|
||||
import net.minecraft.server.v1_7_R4.ServerPing;
|
||||
import net.minecraft.server.v1_7_R4.ServerPingPlayerSample;
|
||||
import net.minecraft.server.v1_7_R4.ServerPingServerData;
|
||||
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.comphenix.protocol.BukkitInitialization;
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
public class MinecraftReflectionTest {
|
||||
// Mocking objects
|
||||
private interface FakeEntity {
|
||||
public Entity getBukkitEntity();
|
||||
}
|
||||
|
||||
private interface FakeBlock {
|
||||
public Block getBukkitEntity();
|
||||
}
|
||||
|
||||
@BeforeClass
|
||||
public static void initializeReflection() throws IllegalAccessException {
|
||||
BukkitInitialization.initializePackage();
|
||||
|
||||
// Set up a package with no class loader knowledge
|
||||
MinecraftReflection.minecraftPackage = new CachedPackage(
|
||||
MinecraftReflection.getMinecraftPackage(),
|
||||
ClassSource.fromMap(Maps.<String, Class<?>>newHashMap())
|
||||
);
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void undoMocking() {
|
||||
// NOP
|
||||
MinecraftReflection.minecraftPackage = null;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBukkitMethod() {
|
||||
FakeEntity entity = mock(FakeEntity.class);
|
||||
FakeBlock block = mock(FakeBlock.class);
|
||||
|
||||
MinecraftReflection.getBukkitEntity(entity);
|
||||
MinecraftReflection.getBukkitEntity(block);
|
||||
|
||||
verify(entity, times(1)).getBukkitEntity();
|
||||
verify(block, times(1)).getBukkitEntity();
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testIllegalClass() {
|
||||
MinecraftReflection.getBukkitEntity("Hello");
|
||||
}
|
||||
|
||||
// @Test
|
||||
// public void testNbtStreamTools() {
|
||||
// assertEquals(NBTCompressedStreamTools.class, MinecraftReflection.getNbtCompressedStreamToolsClass());
|
||||
// }
|
||||
|
||||
@Test
|
||||
public void testChatComponent() {
|
||||
assertEquals(IChatBaseComponent.class, MinecraftReflection.getIChatBaseComponentClass());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChatComponentText() {
|
||||
assertEquals(ChatComponentText.class, MinecraftReflection.getChatComponentTextClass());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChatSerializer() {
|
||||
assertEquals(ChatSerializer.class, MinecraftReflection.getChatSerializerClass());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testServerPing() {
|
||||
assertEquals(ServerPing.class, MinecraftReflection.getServerPingClass());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testServerPingPlayerSample() {
|
||||
assertEquals(ServerPingPlayerSample.class, MinecraftReflection.getServerPingPlayerSampleClass());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testServerPingServerData() {
|
||||
assertEquals(ServerPingServerData.class, MinecraftReflection.getServerPingServerDataClass());
|
||||
}
|
||||
|
||||
// @Test
|
||||
// public void testChunkCoordIntPair() {
|
||||
// assertEquals(ChunkCoordIntPair.class, MinecraftReflection.getChunkCoordIntPair());
|
||||
// }
|
||||
|
||||
// @Test
|
||||
// public void testWatchableObject() {
|
||||
// assertEquals(WatchableObject.class, MinecraftReflection.getWatchableObjectClass());
|
||||
// }
|
||||
}
|
||||
-24
@@ -1,24 +0,0 @@
|
||||
package com.comphenix.protocol.wrappers;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.comphenix.protocol.BukkitInitialization;
|
||||
|
||||
public class WrappedChunkCoordinateTest {
|
||||
@BeforeClass
|
||||
public static void initializeBukkit() throws IllegalAccessException {
|
||||
BukkitInitialization.initializePackage();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
WrappedChunkCoordinate coordinate = new WrappedChunkCoordinate(1, 2, 3);
|
||||
|
||||
assertEquals(1, coordinate.getX());
|
||||
assertEquals(2, coordinate.getY());
|
||||
assertEquals(3, coordinate.getZ());
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
package com.comphenix.protocol.wrappers;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.comphenix.protocol.BukkitInitialization;
|
||||
import com.google.common.base.Charsets;
|
||||
|
||||
public class WrappedGameProfileTest {
|
||||
@BeforeClass
|
||||
public static void initializeBukkit() throws IllegalAccessException {
|
||||
BukkitInitialization.initializePackage();
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Test
|
||||
public void testSkinUpdate() {
|
||||
final UUID uuid = UUID.nameUUIDFromBytes("123".getBytes(Charsets.UTF_8));
|
||||
|
||||
assertEquals(null, new WrappedGameProfile((String)null, "Test").getId());
|
||||
assertEquals(uuid, new WrappedGameProfile("123", "Test").getUUID());
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testNullFailure() {
|
||||
new WrappedGameProfile((String)null, null);
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
package com.comphenix.protocol.wrappers;
|
||||
|
||||
import org.junit.BeforeClass;
|
||||
|
||||
import com.comphenix.protocol.BukkitInitialization;
|
||||
|
||||
public class WrappedServerPingTest {
|
||||
@BeforeClass
|
||||
public static void initializeBukkit() throws IllegalAccessException {
|
||||
BukkitInitialization.initializePackage();
|
||||
}
|
||||
|
||||
// @Test
|
||||
// public void test() throws IOException {
|
||||
// CompressedImage tux = CompressedImage.fromPng(Resources.getResource("tux.png").openStream());
|
||||
// byte[] original = tux.getDataCopy();
|
||||
//
|
||||
// WrappedServerPing serverPing = new WrappedServerPing();
|
||||
// serverPing.setMotD("Hello, this is a test.");
|
||||
// serverPing.setPlayersOnline(5);
|
||||
// serverPing.setPlayersMaximum(10);
|
||||
// serverPing.setVersionName("Minecraft 123");
|
||||
// serverPing.setVersionProtocol(4);
|
||||
// serverPing.setFavicon(tux);
|
||||
//
|
||||
// assertEquals(5, serverPing.getPlayersOnline());
|
||||
// assertEquals(10, serverPing.getPlayersMaximum());
|
||||
// assertEquals("Minecraft 123", serverPing.getVersionName());
|
||||
// assertEquals(4, serverPing.getVersionProtocol());
|
||||
//
|
||||
// assertArrayEquals(original, serverPing.getFavicon().getData());
|
||||
//
|
||||
// CompressedImage copy = CompressedImage.fromBase64Png(Base64Coder.encodeLines(tux.getData()));
|
||||
// assertArrayEquals(copy.getData(), serverPing.getFavicon().getData());
|
||||
// }
|
||||
|
||||
}
|
||||
-33
@@ -1,33 +0,0 @@
|
||||
package com.comphenix.protocol.wrappers;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.craftbukkit.v1_7_R4.inventory.CraftItemFactory;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.powermock.core.classloader.annotations.PowerMockIgnore;
|
||||
import org.powermock.core.classloader.annotations.PrepareForTest;
|
||||
|
||||
import com.comphenix.protocol.BukkitInitialization;
|
||||
|
||||
@RunWith(org.powermock.modules.junit4.PowerMockRunner.class)
|
||||
@PowerMockIgnore({ "org.apache.log4j.*", "org.apache.logging.*", "org.bukkit.craftbukkit.libs.jline.*" })
|
||||
@PrepareForTest(CraftItemFactory.class)
|
||||
public class WrappedWatchableObjectTest {
|
||||
@BeforeClass
|
||||
public static void initializeBukkit() throws IllegalAccessException {
|
||||
BukkitInitialization.initializeItemMeta();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testItemStack() {
|
||||
final ItemStack stack = new ItemStack(Material.GOLD_AXE);
|
||||
final WrappedWatchableObject test = new WrappedWatchableObject(0, stack);
|
||||
|
||||
ItemStack value = (ItemStack) test.getValue();
|
||||
assertEquals(value.getType(), stack.getType());
|
||||
}
|
||||
}
|
||||
@@ -1,151 +1,169 @@
|
||||
ProtocolLib
|
||||
===========
|
||||
|
||||
Certain tasks are impossible to perform with the standard Bukkit API, and may require
|
||||
working with and even modify Minecraft directly. A common technique is to modify incoming
|
||||
and outgoing [packets](http://www.wiki.vg/Protocol), or inject custom packets into the
|
||||
stream. This is quite cumbersome to do, however, and most implementations will break
|
||||
as soon as a new version of Minecraft has been released, mostly due to obfuscation.
|
||||
|
||||
Critically, different plugins that use this approach may _hook_ into the same classes,
|
||||
with unpredictable outcomes. More than often this causes plugins to crash, but it may also
|
||||
lead to more subtle bugs.
|
||||
|
||||
Currently maintained by dmulloy2 on behalf of [SpigotMC](http://www.spigotmc.org/)
|
||||
|
||||
|
||||
### Resources
|
||||
|
||||
* [Downloads](http://ci.shadowvolt.com/job/ProtocolLib)
|
||||
* [JavaDoc](http://ci.shadowvolt.com/job/ProtocolLib/javadoc)
|
||||
|
||||
|
||||
Building
|
||||
--------
|
||||
You can compile this project yourself by using the latest version of Maven.
|
||||
|
||||
|
||||
A new API
|
||||
---------
|
||||
|
||||
__ProtocolLib__ attempts to solve this problem by providing a event API, much like Bukkit,
|
||||
that allow plugins to monitor, modify or cancel packets sent and received. But more importantly,
|
||||
the API also hides all the gritty, obfuscated classes with a simple index based read/write system.
|
||||
You no longer have to reference CraftBukkit!
|
||||
|
||||
|
||||
### Using ProtocolLib
|
||||
|
||||
To use the library, first add ProtocolLib.jar to your Java build path. Then, add ProtocolLib
|
||||
as a dependency (or soft-dependency, if you can live without it) to your plugin.yml file:
|
||||
|
||||
````yml
|
||||
depends: [ProtocolLib]
|
||||
````
|
||||
|
||||
Future versions will be available in a public Maven repository, possibly on Maven central. But it
|
||||
will always be possible to reference ProtocolLib manually.
|
||||
|
||||
Then get a reference to ProtocolManager in onLoad() and you're good to go.
|
||||
|
||||
````java
|
||||
private ProtocolManager protocolManager;
|
||||
|
||||
public void onLoad() {
|
||||
protocolManager = ProtocolLibrary.getProtocolManager();
|
||||
}
|
||||
````
|
||||
|
||||
To listen for packets sent by the server to a client, add a server-side listener:
|
||||
|
||||
````java
|
||||
// Disable all sound effects
|
||||
protocolManager.addPacketListener(
|
||||
new PacketAdapter(this, ListenerPriority.NORMAL,
|
||||
PacketType.Play.Server.NAMED_SOUND_EFFECT) {
|
||||
@Override
|
||||
public void onPacketSending(PacketEvent event) {
|
||||
// Item packets (id: 0x29)
|
||||
if (event.getPacketType() ==
|
||||
PacketType.Play.Server.NAMED_SOUND_EFFECT) {
|
||||
event.setCancelled(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
````
|
||||
|
||||
It's also possible to read and modify the content of these packets. For instance, you can create a global
|
||||
censor by listening for Packet3Chat events:
|
||||
|
||||
````java
|
||||
// Censor
|
||||
protocolManager.addPacketListener(new PacketAdapter(this,
|
||||
ListenerPriority.NORMAL,
|
||||
PacketType.Play.Client.CHAT) {
|
||||
@Override
|
||||
public void onPacketReceiving(PacketEvent event) {
|
||||
if (event.getPacketType() == PacketType.Play.Client.CHAT) {
|
||||
PacketContainer packet = event.getPacket();
|
||||
String message = packet.getStrings().read(0);
|
||||
|
||||
if (message.contains("shit")
|
||||
|| message.contains("damn")) {
|
||||
event.setCancelled(true);
|
||||
event.getPlayer().sendMessage("Bad manners!");
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
````
|
||||
|
||||
### Sending packets
|
||||
|
||||
Normally, you might have to do something ugly like the following:
|
||||
|
||||
````java
|
||||
Packet60Explosion fakeExplosion = new Packet60Explosion();
|
||||
|
||||
fakeExplosion.a = player.getLocation().getX();
|
||||
fakeExplosion.b = player.getLocation().getY();
|
||||
fakeExplosion.c = player.getLocation().getZ();
|
||||
fakeExplosion.d = 3.0F;
|
||||
fakeExplosion.e = new ArrayList<Object>();
|
||||
|
||||
((CraftPlayer) player).getHandle().netServerHandler.sendPacket(fakeExplosion);
|
||||
````
|
||||
|
||||
But with ProtocolLib, you can turn that into something more manageable. Notice that
|
||||
you don't have to create an ArrayList with this version:
|
||||
|
||||
````java
|
||||
|
||||
fakeExplosion.getDoubles().
|
||||
write(0, player.getLocation().getX()).
|
||||
write(1, player.getLocation().getY()).
|
||||
write(2, player.getLocation().getZ());
|
||||
fakeExplosion.getFloat().write(0, 3.0F);
|
||||
|
||||
try {
|
||||
protocolManager.sendServerPacket(player, fakeExplosion);
|
||||
} catch (InvocationTargetException e) {
|
||||
throw new RuntimeException(
|
||||
"Cannot send packet " + fakeExplosion, e);
|
||||
}
|
||||
````
|
||||
|
||||
Compatiblity
|
||||
------------
|
||||
|
||||
One of the main goals of this project was to achieve maximum compatibility with CraftBukkit. And the end
|
||||
result is quite flexible - in tests I successfully ran an unmodified ProtocolLib on CraftBukkit 1.8.0, and
|
||||
it should be resiliant against future changes. It's likely that I won't have to update ProtocolLib for
|
||||
anything but bug and performance fixes.
|
||||
|
||||
How is this possible? It all comes down to reflection in the end. Essentially, no name is hard coded -
|
||||
every field, method and class is deduced by looking at field types, package names or parameter
|
||||
types. It's remarkably consistent across different versions.
|
||||
|
||||
|
||||
### Incompatiblity
|
||||
|
||||
The following plugins (to be expanded) are not compatible with ProtocolLib:
|
||||
# ProtocolLib
|
||||
|
||||
Certain tasks are impossible to perform with the standard Bukkit API, and may require
|
||||
working with and even modify Minecraft directly. A common technique is to modify incoming
|
||||
and outgoing [packets](https://www.wiki.vg/Protocol), or inject custom packets into the
|
||||
stream. This is quite cumbersome to do, however, and most implementations will break
|
||||
as soon as a new version of Minecraft has been released, mostly due to obfuscation.
|
||||
|
||||
Critically, different plugins that use this approach may _hook_ into the same classes,
|
||||
with unpredictable outcomes. More than often this causes plugins to crash, but it may also
|
||||
lead to more subtle bugs.
|
||||
|
||||
Currently maintained by dmulloy2 on behalf of [Spigot](https://www.spigotmc.org/).
|
||||
|
||||
### Resources
|
||||
|
||||
* [Resource Page](https://www.spigotmc.org/resources/protocollib.1997/)
|
||||
* [Dev Builds](https://ci.dmulloy2.net/job/ProtocolLib)
|
||||
* [JavaDoc](https://ci.dmulloy2.net/job/ProtocolLib/javadoc)
|
||||
|
||||
### Compilation
|
||||
|
||||
ProtocolLib is built with Maven and requires Spigot and SpigotAPI, which can be found [here](https://www.spigotmc.org/wiki/buildtools/).
|
||||
|
||||
### A new API
|
||||
|
||||
__ProtocolLib__ attempts to solve this problem by providing a event API, much like Bukkit,
|
||||
that allows plugins to monitor, modify, or cancel packets sent and received. But, more importantly,
|
||||
the API also hides all the gritty, obfuscated classes with a simple index based read/write system.
|
||||
You no longer have to reference CraftBukkit!
|
||||
|
||||
### Using ProtocolLib
|
||||
|
||||
To use this library, first add ProtocolLib.jar to your Java build path. Then, add ProtocolLib
|
||||
as a dependency or soft dependency to your plugin.yml file like any other plugin:
|
||||
|
||||
````yml
|
||||
depend: [ProtocolLib]
|
||||
````
|
||||
|
||||
You can also add ProtocolLib as a Maven dependency:
|
||||
|
||||
````xml
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>dmulloy2-repo</id>
|
||||
<url>https://repo.dmulloy2.net/repository/public/</url>
|
||||
</repository>
|
||||
...
|
||||
</repositories>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.comphenix.protocol</groupId>
|
||||
<artifactId>ProtocolLib</artifactId>
|
||||
<version>4.6.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
````
|
||||
|
||||
Or use the maven dependency with gradle:
|
||||
|
||||
```gradle
|
||||
repositories {
|
||||
maven { url "https://repo.dmulloy2.net/repository/public/" }
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly group: "com.comphenix.protocol", name: "ProtocolLib", version: "4.6.0";
|
||||
}
|
||||
```
|
||||
|
||||
Then get a reference to ProtocolManager in onLoad() or onEnable() and you're good to go.
|
||||
|
||||
````java
|
||||
private ProtocolManager protocolManager;
|
||||
|
||||
public void onLoad() {
|
||||
protocolManager = ProtocolLibrary.getProtocolManager();
|
||||
}
|
||||
````
|
||||
|
||||
To listen for packets sent by the server to a client, add a server-side listener:
|
||||
|
||||
````java
|
||||
// Disable all sound effects
|
||||
protocolManager.addPacketListener(
|
||||
new PacketAdapter(this, ListenerPriority.NORMAL,
|
||||
PacketType.Play.Server.NAMED_SOUND_EFFECT) {
|
||||
@Override
|
||||
public void onPacketSending(PacketEvent event) {
|
||||
// Item packets (id: 0x29)
|
||||
if (event.getPacketType() ==
|
||||
PacketType.Play.Server.NAMED_SOUND_EFFECT) {
|
||||
event.setCancelled(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
````
|
||||
|
||||
It's also possible to read and modify the content of these packets. For instance, you can create a global
|
||||
censor by listening for Packet3Chat events:
|
||||
|
||||
````java
|
||||
// Censor
|
||||
protocolManager.addPacketListener(new PacketAdapter(this,
|
||||
ListenerPriority.NORMAL,
|
||||
PacketType.Play.Client.CHAT) {
|
||||
@Override
|
||||
public void onPacketReceiving(PacketEvent event) {
|
||||
if (event.getPacketType() == PacketType.Play.Client.CHAT) {
|
||||
PacketContainer packet = event.getPacket();
|
||||
String message = packet.getStrings().read(0);
|
||||
|
||||
if (message.contains("shit")
|
||||
|| message.contains("damn")) {
|
||||
event.setCancelled(true);
|
||||
event.getPlayer().sendMessage("Bad manners!");
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
````
|
||||
|
||||
### Sending packets
|
||||
|
||||
Normally, you might have to do something ugly like the following:
|
||||
|
||||
````java
|
||||
Packet60Explosion fakeExplosion = new Packet60Explosion();
|
||||
|
||||
fakeExplosion.a = player.getLocation().getX();
|
||||
fakeExplosion.b = player.getLocation().getY();
|
||||
fakeExplosion.c = player.getLocation().getZ();
|
||||
fakeExplosion.d = 3.0F;
|
||||
fakeExplosion.e = new ArrayList<Object>();
|
||||
|
||||
((CraftPlayer) player).getHandle().netServerHandler.sendPacket(fakeExplosion);
|
||||
````
|
||||
|
||||
But with ProtocolLib, you can turn that into something more manageable. Notice that
|
||||
you don't have to create an ArrayList with this version:
|
||||
|
||||
````java
|
||||
|
||||
PacketContainer fakeExplosion = new PacketContainer(PacketType.Play.Server.EXPLOSION);
|
||||
fakeExplosion.getDoubles().
|
||||
write(0, player.getLocation().getX()).
|
||||
write(1, player.getLocation().getY()).
|
||||
write(2, player.getLocation().getZ());
|
||||
fakeExplosion.getFloat().write(0, 3.0F);
|
||||
|
||||
try {
|
||||
protocolManager.sendServerPacket(player, fakeExplosion);
|
||||
} catch (InvocationTargetException e) {
|
||||
throw new RuntimeException(
|
||||
"Cannot send packet " + fakeExplosion, e);
|
||||
}
|
||||
````
|
||||
|
||||
### Compatibility
|
||||
|
||||
One of the main goals of this project was to achieve maximum compatibility with CraftBukkit. And the end
|
||||
result is quite flexible. Aside from netty package changes, it should be resilient against future changes.
|
||||
It's likely that I won't have to update ProtocolLib for anything but bug fixes and new features.
|
||||
|
||||
How is this possible? It all comes down to reflection in the end. Essentially, no name is hard coded -
|
||||
every field, method and class is deduced by looking at field types, package names or parameter
|
||||
types. It's remarkably consistent across different versions.
|
||||
@@ -3,32 +3,29 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>com.comphenix.tinyprotocol</groupId>
|
||||
<artifactId>TinyProtocol</artifactId>
|
||||
<version>0.1.0</version>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
<name>TinyProtocol</name>
|
||||
<description>Intercept packets without ProtocolLib.</description>
|
||||
|
||||
<distributionManagement>
|
||||
<repository>
|
||||
<id>comphenix-releases</id>
|
||||
<name>Comphenix Maven Releases</name>
|
||||
<url>http://repo.comphenix.net/content/repositories/releases/</url>
|
||||
</repository>
|
||||
<snapshotRepository>
|
||||
<id>comphenix-snapshots</id>
|
||||
<name>Comphenix Maven Snapshots</name>
|
||||
<url>http://repo.comphenix.net/content/repositories/snapshots/</url>
|
||||
</snapshotRepository>
|
||||
</distributionManagement>
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<parent>
|
||||
<groupId>com.comphenix.protocol</groupId>
|
||||
<artifactId>ProtocolLib-Parent</artifactId>
|
||||
<version>v4-SNAPSHOT</version>
|
||||
<relativePath>../../</relativePath>
|
||||
</parent>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>md_5-repo</id>
|
||||
<url>http://repo.md-5.net/content/repositories/public/</url>
|
||||
<id>spigot-repo</id>
|
||||
<url>https://hub.spigotmc.org/nexus/content/groups/public/</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>comphenix-rep</id>
|
||||
<name>Comphenix Maven Releases</name>
|
||||
<url>http://repo.comphenix.net/content/groups/public</url>
|
||||
<id>dmulloy2-repo</id>
|
||||
<url>http://repo.dmulloy2.net/content/groups/public/</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
@@ -45,26 +42,41 @@
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.0</version>
|
||||
<configuration>
|
||||
<source>1.6</source>
|
||||
<target>1.6</target>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<version>2.5</version>
|
||||
<configuration>
|
||||
<archive>
|
||||
<addMavenDescriptor>false</addMavenDescriptor>
|
||||
</archive>
|
||||
<finalName>TinyProtocol</finalName>
|
||||
<outputDirectory>../../target/</outputDirectory>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<version>2.7</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<id>attach-javadocs</id>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<skip>true</skip>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.spigotmc</groupId>
|
||||
<artifactId>spigot-api</artifactId>
|
||||
<version>1.7.10-R0.1-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.spigotmc</groupId>
|
||||
<artifactId>spigot</artifactId>
|
||||
<version>1.7.10-R0.1-SNAPSHOT</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>release-sign-artifacts</id>
|
||||
+23
-15
@@ -1,5 +1,7 @@
|
||||
package com.comphenix.tinyprotocol;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.command.Command;
|
||||
@@ -10,13 +12,13 @@ import org.bukkit.plugin.java.JavaPlugin;
|
||||
import com.comphenix.tinyprotocol.Reflection.ConstructorInvoker;
|
||||
import com.comphenix.tinyprotocol.Reflection.FieldAccessor;
|
||||
|
||||
import net.minecraft.util.io.netty.channel.Channel;
|
||||
|
||||
/**
|
||||
* Represents an example plugin utilizing TinyProtocol
|
||||
*/
|
||||
public class ExamplePlugin extends JavaPlugin {
|
||||
// Chat packets
|
||||
private FieldAccessor<String> CHAT_MESSAGE = Reflection.getField(
|
||||
"{nms}.PacketPlayInChat", String.class, 0);
|
||||
|
||||
private FieldAccessor<String> CHAT_MESSAGE = Reflection.getField("{nms}.PacketPlayInChat", String.class, 0);
|
||||
|
||||
// Explosion packet
|
||||
private Class<?> particleClass = Reflection.getClass("{nms}.PacketPlayOutWorldParticles");
|
||||
private FieldAccessor<String> particleName = Reflection.getField(particleClass, String.class, 0);
|
||||
@@ -24,7 +26,7 @@ public class ExamplePlugin extends JavaPlugin {
|
||||
private FieldAccessor<Float> particleY = Reflection.getField(particleClass, float.class, 1);
|
||||
private FieldAccessor<Float> particleZ = Reflection.getField(particleClass, float.class, 2);
|
||||
private FieldAccessor<Integer> particleCount = Reflection.getField(particleClass, int.class, 0);
|
||||
|
||||
|
||||
// Server info packet
|
||||
private Class<?> serverInfoClass = Reflection.getClass("{nms}.PacketStatusOutServerInfo");
|
||||
private Class<Object> serverPingClass = Reflection.getUntypedClass("{nms}.ServerPing");
|
||||
@@ -32,12 +34,13 @@ public class ExamplePlugin extends JavaPlugin {
|
||||
private FieldAccessor<Object> serverPing = Reflection.getField(serverInfoClass, serverPingClass, 0);
|
||||
private FieldAccessor<Object> playerSample = Reflection.getField(serverPingClass, playerSampleClass, 0);
|
||||
private ConstructorInvoker playerSampleInvoker = Reflection.getConstructor(playerSampleClass, int.class, int.class);
|
||||
|
||||
|
||||
private TinyProtocol protocol;
|
||||
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
protocol = new TinyProtocol(this) {
|
||||
|
||||
@Override
|
||||
public Object onPacketInAsync(Player sender, Channel channel, Object packet) {
|
||||
// Cancel chat packets
|
||||
@@ -47,24 +50,28 @@ public class ExamplePlugin extends JavaPlugin {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (particleName.hasField(packet)) {
|
||||
System.out.println("Sending particle field:" + packet);
|
||||
}
|
||||
|
||||
return super.onPacketInAsync(sender, channel, packet);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Object onPacketOutAsync(Player reciever, Channel channel, Object packet) {
|
||||
if (serverInfoClass.isInstance(packet)) {
|
||||
Object ping = serverPing.get(packet);
|
||||
playerSample.set(ping, playerSampleInvoker.invoke(1000, 0));
|
||||
|
||||
|
||||
// Which is equivalent to:
|
||||
//serverPing.get(packet).setPlayerSample(new ServerPingPlayerSample(1000, 0));
|
||||
// serverPing.get(packet).setPlayerSample(new ServerPingPlayerSample(1000, 0));
|
||||
return packet;
|
||||
}
|
||||
|
||||
return super.onPacketOutAsync(reciever, channel, packet);
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
@@ -72,7 +79,7 @@ public class ExamplePlugin extends JavaPlugin {
|
||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
if (sender instanceof Player) {
|
||||
Player player = (Player) sender;
|
||||
|
||||
|
||||
// Toggle injection
|
||||
if (protocol.hasInjected(player)) {
|
||||
protocol.uninjectPlayer(player);
|
||||
@@ -81,13 +88,15 @@ public class ExamplePlugin extends JavaPlugin {
|
||||
protocol.injectPlayer(player);
|
||||
sender.sendMessage(ChatColor.DARK_GREEN + "Player " + player + " has been injected.");
|
||||
}
|
||||
|
||||
return true;
|
||||
} else {
|
||||
sender.sendMessage(ChatColor.RED + "Can only be invoked by a player.");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void sendExplosion(Player player) {
|
||||
try {
|
||||
// Only visible for the client
|
||||
@@ -101,7 +110,6 @@ public class ExamplePlugin extends JavaPlugin {
|
||||
|
||||
// Send the packet to the player
|
||||
protocol.sendPacket(player, explosionPacket);
|
||||
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Cannot send packet.", e);
|
||||
}
|
||||
@@ -0,0 +1,403 @@
|
||||
package com.comphenix.tinyprotocol;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
|
||||
/**
|
||||
* An utility class that simplifies reflection in Bukkit plugins.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public final class Reflection {
|
||||
/**
|
||||
* An interface for invoking a specific constructor.
|
||||
*/
|
||||
public interface ConstructorInvoker {
|
||||
/**
|
||||
* Invoke a constructor for a specific class.
|
||||
*
|
||||
* @param arguments - the arguments to pass to the constructor.
|
||||
* @return The constructed object.
|
||||
*/
|
||||
public Object invoke(Object... arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* An interface for invoking a specific method.
|
||||
*/
|
||||
public interface MethodInvoker {
|
||||
/**
|
||||
* Invoke a method on a specific target object.
|
||||
*
|
||||
* @param target - the target object, or NULL for a static method.
|
||||
* @param arguments - the arguments to pass to the method.
|
||||
* @return The return value, or NULL if is void.
|
||||
*/
|
||||
public Object invoke(Object target, Object... arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* An interface for retrieving the field content.
|
||||
*
|
||||
* @param <T> - field type.
|
||||
*/
|
||||
public interface FieldAccessor<T> {
|
||||
/**
|
||||
* Retrieve the content of a field.
|
||||
*
|
||||
* @param target - the target object, or NULL for a static field.
|
||||
* @return The value of the field.
|
||||
*/
|
||||
public T get(Object target);
|
||||
|
||||
/**
|
||||
* Set the content of a field.
|
||||
*
|
||||
* @param target - the target object, or NULL for a static field.
|
||||
* @param value - the new value of the field.
|
||||
*/
|
||||
public void set(Object target, Object value);
|
||||
|
||||
/**
|
||||
* Determine if the given object has this field.
|
||||
*
|
||||
* @param target - the object to test.
|
||||
* @return TRUE if it does, FALSE otherwise.
|
||||
*/
|
||||
public boolean hasField(Object target);
|
||||
}
|
||||
|
||||
// Deduce the net.minecraft.server.v* package
|
||||
private static String OBC_PREFIX = Bukkit.getServer().getClass().getPackage().getName();
|
||||
private static String NMS_PREFIX = OBC_PREFIX.replace("org.bukkit.craftbukkit", "net.minecraft.server");
|
||||
private static String VERSION = OBC_PREFIX.replace("org.bukkit.craftbukkit", "").replace(".", "");
|
||||
|
||||
// Variable replacement
|
||||
private static Pattern MATCH_VARIABLE = Pattern.compile("\\{([^\\}]+)\\}");
|
||||
|
||||
private Reflection() {
|
||||
// Seal class
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a field accessor for a specific field type and name.
|
||||
*
|
||||
* @param target - the target type.
|
||||
* @param name - the name of the field, or NULL to ignore.
|
||||
* @param fieldType - a compatible field type.
|
||||
* @return The field accessor.
|
||||
*/
|
||||
public static <T> FieldAccessor<T> getField(Class<?> target, String name, Class<T> fieldType) {
|
||||
return getField(target, name, fieldType, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a field accessor for a specific field type and name.
|
||||
*
|
||||
* @param className - lookup name of the class, see {@link #getClass(String)}.
|
||||
* @param name - the name of the field, or NULL to ignore.
|
||||
* @param fieldType - a compatible field type.
|
||||
* @return The field accessor.
|
||||
*/
|
||||
public static <T> FieldAccessor<T> getField(String className, String name, Class<T> fieldType) {
|
||||
return getField(getClass(className), name, fieldType, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a field accessor for a specific field type and name.
|
||||
*
|
||||
* @param target - the target type.
|
||||
* @param fieldType - a compatible field type.
|
||||
* @param index - the number of compatible fields to skip.
|
||||
* @return The field accessor.
|
||||
*/
|
||||
public static <T> FieldAccessor<T> getField(Class<?> target, Class<T> fieldType, int index) {
|
||||
return getField(target, null, fieldType, index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a field accessor for a specific field type and name.
|
||||
*
|
||||
* @param className - lookup name of the class, see {@link #getClass(String)}.
|
||||
* @param fieldType - a compatible field type.
|
||||
* @param index - the number of compatible fields to skip.
|
||||
* @return The field accessor.
|
||||
*/
|
||||
public static <T> FieldAccessor<T> getField(String className, Class<T> fieldType, int index) {
|
||||
return getField(getClass(className), fieldType, index);
|
||||
}
|
||||
|
||||
// Common method
|
||||
private static <T> FieldAccessor<T> getField(Class<?> target, String name, Class<T> fieldType, int index) {
|
||||
for (final Field field : target.getDeclaredFields()) {
|
||||
if ((name == null || field.getName().equals(name)) && fieldType.isAssignableFrom(field.getType()) && index-- <= 0) {
|
||||
field.setAccessible(true);
|
||||
|
||||
// A function for retrieving a specific field value
|
||||
return new FieldAccessor<T>() {
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public T get(Object target) {
|
||||
try {
|
||||
return (T) field.get(target);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException("Cannot access reflection.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(Object target, Object value) {
|
||||
try {
|
||||
field.set(target, value);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException("Cannot access reflection.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasField(Object target) {
|
||||
// target instanceof DeclaringClass
|
||||
return field.getDeclaringClass().isAssignableFrom(target.getClass());
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Search in parent classes
|
||||
if (target.getSuperclass() != null)
|
||||
return getField(target.getSuperclass(), name, fieldType, index);
|
||||
|
||||
throw new IllegalArgumentException("Cannot find field with type " + fieldType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for the first publicly and privately defined method of the given name and parameter count.
|
||||
*
|
||||
* @param className - lookup name of the class, see {@link #getClass(String)}.
|
||||
* @param methodName - the method name, or NULL to skip.
|
||||
* @param params - the expected parameters.
|
||||
* @return An object that invokes this specific method.
|
||||
* @throws IllegalStateException If we cannot find this method.
|
||||
*/
|
||||
public static MethodInvoker getMethod(String className, String methodName, Class<?>... params) {
|
||||
return getTypedMethod(getClass(className), methodName, null, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for the first publicly and privately defined method of the given name and parameter count.
|
||||
*
|
||||
* @param clazz - a class to start with.
|
||||
* @param methodName - the method name, or NULL to skip.
|
||||
* @param params - the expected parameters.
|
||||
* @return An object that invokes this specific method.
|
||||
* @throws IllegalStateException If we cannot find this method.
|
||||
*/
|
||||
public static MethodInvoker getMethod(Class<?> clazz, String methodName, Class<?>... params) {
|
||||
return getTypedMethod(clazz, methodName, null, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for the first publicly and privately defined method of the given name and parameter count.
|
||||
*
|
||||
* @param clazz - a class to start with.
|
||||
* @param methodName - the method name, or NULL to skip.
|
||||
* @param returnType - the expected return type, or NULL to ignore.
|
||||
* @param params - the expected parameters.
|
||||
* @return An object that invokes this specific method.
|
||||
* @throws IllegalStateException If we cannot find this method.
|
||||
*/
|
||||
public static MethodInvoker getTypedMethod(Class<?> clazz, String methodName, Class<?> returnType, Class<?>... params) {
|
||||
for (final Method method : clazz.getDeclaredMethods()) {
|
||||
if ((methodName == null || method.getName().equals(methodName))
|
||||
&& (returnType == null || method.getReturnType().equals(returnType))
|
||||
&& Arrays.equals(method.getParameterTypes(), params)) {
|
||||
method.setAccessible(true);
|
||||
|
||||
return new MethodInvoker() {
|
||||
|
||||
@Override
|
||||
public Object invoke(Object target, Object... arguments) {
|
||||
try {
|
||||
return method.invoke(target, arguments);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Cannot invoke method " + method, e);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Search in every superclass
|
||||
if (clazz.getSuperclass() != null)
|
||||
return getMethod(clazz.getSuperclass(), methodName, params);
|
||||
|
||||
throw new IllegalStateException(String.format("Unable to find method %s (%s).", methodName, Arrays.asList(params)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for the first publically and privately defined constructor of the given name and parameter count.
|
||||
*
|
||||
* @param className - lookup name of the class, see {@link #getClass(String)}.
|
||||
* @param params - the expected parameters.
|
||||
* @return An object that invokes this constructor.
|
||||
* @throws IllegalStateException If we cannot find this method.
|
||||
*/
|
||||
public static ConstructorInvoker getConstructor(String className, Class<?>... params) {
|
||||
return getConstructor(getClass(className), params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for the first publically and privately defined constructor of the given name and parameter count.
|
||||
*
|
||||
* @param clazz - a class to start with.
|
||||
* @param params - the expected parameters.
|
||||
* @return An object that invokes this constructor.
|
||||
* @throws IllegalStateException If we cannot find this method.
|
||||
*/
|
||||
public static ConstructorInvoker getConstructor(Class<?> clazz, Class<?>... params) {
|
||||
for (final Constructor<?> constructor : clazz.getDeclaredConstructors()) {
|
||||
if (Arrays.equals(constructor.getParameterTypes(), params)) {
|
||||
constructor.setAccessible(true);
|
||||
|
||||
return new ConstructorInvoker() {
|
||||
|
||||
@Override
|
||||
public Object invoke(Object... arguments) {
|
||||
try {
|
||||
return constructor.newInstance(arguments);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Cannot invoke constructor " + constructor, e);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
throw new IllegalStateException(String.format("Unable to find constructor for %s (%s).", clazz, Arrays.asList(params)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a class from its full name, without knowing its type on compile time.
|
||||
* <p>
|
||||
* This is useful when looking up fields by a NMS or OBC type.
|
||||
* <p>
|
||||
*
|
||||
* @see {@link #getClass()} for more information.
|
||||
* @param lookupName - the class name with variables.
|
||||
* @return The class.
|
||||
*/
|
||||
public static Class<Object> getUntypedClass(String lookupName) {
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
Class<Object> clazz = (Class) getClass(lookupName);
|
||||
return clazz;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a class from its full name.
|
||||
* <p>
|
||||
* Strings enclosed with curly brackets - such as {TEXT} - will be replaced according to the following table:
|
||||
* <p>
|
||||
* <table border="1">
|
||||
* <tr>
|
||||
* <th>Variable</th>
|
||||
* <th>Content</th>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>{nms}</td>
|
||||
* <td>Actual package name of net.minecraft.server.VERSION</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>{obc}</td>
|
||||
* <td>Actual pacakge name of org.bukkit.craftbukkit.VERSION</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>{version}</td>
|
||||
* <td>The current Minecraft package VERSION, if any.</td>
|
||||
* </tr>
|
||||
* </table>
|
||||
*
|
||||
* @param lookupName - the class name with variables.
|
||||
* @return The looked up class.
|
||||
* @throws IllegalArgumentException If a variable or class could not be found.
|
||||
*/
|
||||
public static Class<?> getClass(String lookupName) {
|
||||
return getCanonicalClass(expandVariables(lookupName));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a class in the net.minecraft.server.VERSION.* package.
|
||||
*
|
||||
* @param name - the name of the class, excluding the package.
|
||||
* @throws IllegalArgumentException If the class doesn't exist.
|
||||
*/
|
||||
public static Class<?> getMinecraftClass(String name) {
|
||||
return getCanonicalClass(NMS_PREFIX + "." + name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a class in the org.bukkit.craftbukkit.VERSION.* package.
|
||||
*
|
||||
* @param name - the name of the class, excluding the package.
|
||||
* @throws IllegalArgumentException If the class doesn't exist.
|
||||
*/
|
||||
public static Class<?> getCraftBukkitClass(String name) {
|
||||
return getCanonicalClass(OBC_PREFIX + "." + name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a class by its canonical name.
|
||||
*
|
||||
* @param canonicalName - the canonical name.
|
||||
* @return The class.
|
||||
*/
|
||||
private static Class<?> getCanonicalClass(String canonicalName) {
|
||||
try {
|
||||
return Class.forName(canonicalName);
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new IllegalArgumentException("Cannot find " + canonicalName, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand variables such as "{nms}" and "{obc}" to their corresponding packages.
|
||||
*
|
||||
* @param name - the full name of the class.
|
||||
* @return The expanded string.
|
||||
*/
|
||||
private static String expandVariables(String name) {
|
||||
StringBuffer output = new StringBuffer();
|
||||
Matcher matcher = MATCH_VARIABLE.matcher(name);
|
||||
|
||||
while (matcher.find()) {
|
||||
String variable = matcher.group(1);
|
||||
String replacement = "";
|
||||
|
||||
// Expand all detected variables
|
||||
if ("nms".equalsIgnoreCase(variable))
|
||||
replacement = NMS_PREFIX;
|
||||
else if ("obc".equalsIgnoreCase(variable))
|
||||
replacement = OBC_PREFIX;
|
||||
else if ("version".equalsIgnoreCase(variable))
|
||||
replacement = VERSION;
|
||||
else
|
||||
throw new IllegalArgumentException("Unknown variable: " + variable);
|
||||
|
||||
// Assume the expanded variables are all packages, and append a dot
|
||||
if (replacement.length() > 0 && matcher.end() < name.length() && name.charAt(matcher.end()) != '.')
|
||||
replacement += ".";
|
||||
matcher.appendReplacement(output, Matcher.quoteReplacement(replacement));
|
||||
}
|
||||
|
||||
matcher.appendTail(output);
|
||||
return output.toString();
|
||||
}
|
||||
}
|
||||
+171
-121
@@ -1,5 +1,14 @@
|
||||
package com.comphenix.tinyprotocol;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelDuplexHandler;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.ChannelPipeline;
|
||||
import io.netty.channel.ChannelPromise;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -8,17 +17,6 @@ import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import net.minecraft.util.com.mojang.authlib.GameProfile;
|
||||
// These are not versioned, but they require CraftBukkit
|
||||
import net.minecraft.util.io.netty.channel.Channel;
|
||||
import net.minecraft.util.io.netty.channel.ChannelDuplexHandler;
|
||||
import net.minecraft.util.io.netty.channel.ChannelFuture;
|
||||
import net.minecraft.util.io.netty.channel.ChannelHandlerContext;
|
||||
import net.minecraft.util.io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
import net.minecraft.util.io.netty.channel.ChannelInitializer;
|
||||
import net.minecraft.util.io.netty.channel.ChannelPipeline;
|
||||
import net.minecraft.util.io.netty.channel.ChannelPromise;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
@@ -28,182 +26,212 @@ import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerLoginEvent;
|
||||
import org.bukkit.event.server.PluginDisableEvent;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.bukkit.scheduler.BukkitRunnable;
|
||||
|
||||
import com.comphenix.tinyprotocol.Reflection.FieldAccessor;
|
||||
import com.comphenix.tinyprotocol.Reflection.MethodInvoker;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.MapMaker;
|
||||
import com.mojang.authlib.GameProfile;
|
||||
|
||||
/**
|
||||
* Represents a very tiny alternative to ProtocolLib in 1.7.2.
|
||||
* Represents a very tiny alternative to ProtocolLib.
|
||||
* <p>
|
||||
* It now supports intercepting packets during login and status ping (such as OUT_SERVER_PING)!
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public abstract class TinyProtocol {
|
||||
private static final AtomicInteger ID = new AtomicInteger(0);
|
||||
|
||||
|
||||
// Used in order to lookup a channel
|
||||
private static final MethodInvoker getPlayerHandle = Reflection.getMethod("{obc}.entity.CraftPlayer", "getHandle");
|
||||
private static final FieldAccessor<Object> getConnection = Reflection.getField("{nms}.EntityPlayer", "playerConnection", Object.class);
|
||||
private static final FieldAccessor<Object> getManager = Reflection.getField("{nms}.PlayerConnection", "networkManager", Object.class);
|
||||
private static final FieldAccessor<Channel> getChannel = Reflection.getField("{nms}.NetworkManager", Channel.class, 0);
|
||||
|
||||
|
||||
// Looking up ServerConnection
|
||||
private static final Class<Object> minecraftServerClass = Reflection.getUntypedClass("{nms}.MinecraftServer");
|
||||
private static final Class<Object> serverConnectionClass = Reflection.getUntypedClass("{nms}.ServerConnection");
|
||||
private static final FieldAccessor<Object> getMinecraftServer = Reflection.getField("{obc}.CraftServer", minecraftServerClass, 0);
|
||||
private static final FieldAccessor<Object> getServerConnection = Reflection.getField(minecraftServerClass, serverConnectionClass, 0);
|
||||
private static final MethodInvoker getNetworkMarkers = Reflection.getTypedMethod(serverConnectionClass, null, List.class, serverConnectionClass);
|
||||
|
||||
|
||||
// Packets we have to intercept
|
||||
private static final Class<?> PACKET_LOGIN_IN_START = Reflection.getMinecraftClass("PacketLoginInStart");
|
||||
private static final FieldAccessor<GameProfile> getGameProfile = Reflection.getField(PACKET_LOGIN_IN_START, GameProfile.class, 0);
|
||||
|
||||
|
||||
// Speedup channel lookup
|
||||
private Map<String, Channel> channelLookup = new MapMaker().weakValues().makeMap();
|
||||
private Listener listener;
|
||||
|
||||
|
||||
// Channels that have already been removed
|
||||
private Set<Channel> uninjectedChannels = Collections.newSetFromMap(new MapMaker().weakKeys().<Channel, Boolean>makeMap());
|
||||
|
||||
|
||||
// List of network markers
|
||||
private List<Object> networkManagers;
|
||||
|
||||
|
||||
// Injected channel handlers
|
||||
private List<Channel> serverChannels = Lists.newArrayList();
|
||||
private ChannelInboundHandlerAdapter serverChannelHandler;
|
||||
private ChannelInitializer<Channel> beginInitProtocol;
|
||||
private ChannelInitializer<Channel> endInitProtocol;
|
||||
|
||||
|
||||
// Current handler name
|
||||
private String handlerName;
|
||||
|
||||
|
||||
protected volatile boolean closed;
|
||||
protected Plugin plugin;
|
||||
|
||||
|
||||
/**
|
||||
* Construct a new instance of TinyProtocol, and start intercepting packets for all connected clients and future clients.
|
||||
* Construct a new instance of TinyProtocol, and start intercepting packets for all connected clients and future clients.
|
||||
* <p>
|
||||
* You can construct multiple instances per plugin.
|
||||
*
|
||||
* @param plugin - the plugin.
|
||||
*/
|
||||
public TinyProtocol(Plugin plugin) {
|
||||
public TinyProtocol(final Plugin plugin) {
|
||||
this.plugin = plugin;
|
||||
|
||||
|
||||
// Compute handler name
|
||||
this.handlerName = getHandlerName();
|
||||
|
||||
|
||||
// Prepare existing players
|
||||
registerBukkitEvents();
|
||||
registerChannelHandler();
|
||||
registerPlayers(plugin);
|
||||
|
||||
try {
|
||||
registerChannelHandler();
|
||||
registerPlayers(plugin);
|
||||
} catch (IllegalArgumentException ex) {
|
||||
// Damn you, late bind
|
||||
plugin.getLogger().info("[TinyProtocol] Delaying server channel injection due to late bind.");
|
||||
|
||||
new BukkitRunnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
registerChannelHandler();
|
||||
registerPlayers(plugin);
|
||||
plugin.getLogger().info("[TinyProtocol] Late bind injection successful.");
|
||||
}
|
||||
}.runTask(plugin);
|
||||
}
|
||||
}
|
||||
|
||||
private void createServerChannelHandler() {
|
||||
// Handle connected channels
|
||||
endInitProtocol = new ChannelInitializer<Channel>() {
|
||||
@Override
|
||||
protected void initChannel(Channel channel) throws Exception {
|
||||
try {
|
||||
// This can take a while, so we need to stop the main thread from interfering
|
||||
synchronized (networkManagers) {
|
||||
// Stop injecting channels
|
||||
if (closed)
|
||||
return;
|
||||
injectChannelInternal(channel);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Cannot inject incomming channel " + channel, e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// This is executed before Minecraft's channel handler
|
||||
beginInitProtocol = new ChannelInitializer<Channel>() {
|
||||
@Override
|
||||
protected void initChannel(Channel channel) throws Exception {
|
||||
channel.pipeline().addLast(endInitProtocol);
|
||||
}
|
||||
};
|
||||
|
||||
serverChannelHandler = new ChannelInboundHandlerAdapter() {
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
||||
Channel channel = (Channel) msg;
|
||||
// Handle connected channels
|
||||
endInitProtocol = new ChannelInitializer<Channel>() {
|
||||
|
||||
// Prepare to initialize ths channel
|
||||
channel.pipeline().addFirst(beginInitProtocol);
|
||||
ctx.fireChannelRead(msg);
|
||||
}
|
||||
};
|
||||
}
|
||||
@Override
|
||||
protected void initChannel(Channel channel) throws Exception {
|
||||
try {
|
||||
// This can take a while, so we need to stop the main thread from interfering
|
||||
synchronized (networkManagers) {
|
||||
// Stop injecting channels
|
||||
if (!closed) {
|
||||
channel.eventLoop().submit(() -> injectChannelInternal(channel));
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Cannot inject incomming channel " + channel, e);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// This is executed before Minecraft's channel handler
|
||||
beginInitProtocol = new ChannelInitializer<Channel>() {
|
||||
|
||||
@Override
|
||||
protected void initChannel(Channel channel) throws Exception {
|
||||
channel.pipeline().addLast(endInitProtocol);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
serverChannelHandler = new ChannelInboundHandlerAdapter() {
|
||||
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
||||
Channel channel = (Channel) msg;
|
||||
|
||||
// Prepare to initialize ths channel
|
||||
channel.pipeline().addFirst(beginInitProtocol);
|
||||
ctx.fireChannelRead(msg);
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Register bukkit events.
|
||||
*/
|
||||
private void registerBukkitEvents() {
|
||||
listener = new Listener() {
|
||||
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
public final void onPlayerLogin(PlayerLoginEvent e) {
|
||||
if (closed)
|
||||
if (closed)
|
||||
return;
|
||||
|
||||
Channel channel = getChannel(e.getPlayer());
|
||||
|
||||
|
||||
// Don't inject players that have been explicitly uninjected
|
||||
if (!uninjectedChannels.contains(channel)) {
|
||||
injectPlayer(e.getPlayer());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@EventHandler
|
||||
public final void onPluginDisable(PluginDisableEvent e) {
|
||||
if (e.getPlugin().equals(plugin)) {
|
||||
close();
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
this.plugin.getServer().getPluginManager().registerEvents(listener, plugin);
|
||||
|
||||
plugin.getServer().getPluginManager().registerEvents(listener, plugin);
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void registerChannelHandler() {
|
||||
Object mcServer = getMinecraftServer.get(Bukkit.getServer());
|
||||
Object serverConnection = getServerConnection.get(mcServer);
|
||||
boolean looking = true;
|
||||
|
||||
|
||||
// We need to synchronize against this list
|
||||
networkManagers = (List<Object>) getNetworkMarkers.invoke(null, serverConnection);
|
||||
createServerChannelHandler();
|
||||
|
||||
|
||||
// Find the correct list, or implicitly throw an exception
|
||||
for (int i = 0; looking; i++) {
|
||||
List<Object> list = Reflection.getField(serverConnection.getClass(), List.class, i).get(serverConnection);
|
||||
|
||||
|
||||
for (Object item : list) {
|
||||
if (!ChannelFuture.class.isInstance(item))
|
||||
if (!ChannelFuture.class.isInstance(item))
|
||||
break;
|
||||
|
||||
|
||||
// Channel future that contains the server connection
|
||||
Channel serverChannel = ((ChannelFuture)item).channel();
|
||||
|
||||
Channel serverChannel = ((ChannelFuture) item).channel();
|
||||
|
||||
serverChannels.add(serverChannel);
|
||||
serverChannel.pipeline().addFirst(serverChannelHandler);
|
||||
looking = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void unregisterChannelHandler() {
|
||||
if (serverChannelHandler == null)
|
||||
return;
|
||||
|
||||
|
||||
for (Channel serverChannel : serverChannels) {
|
||||
final ChannelPipeline pipeline = serverChannel.pipeline();
|
||||
|
||||
|
||||
// Remove channel handler
|
||||
serverChannel.eventLoop().execute(new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
pipeline.remove(serverChannelHandler);
|
||||
@@ -211,26 +239,28 @@ public abstract class TinyProtocol {
|
||||
// That's fine
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void registerPlayers(Plugin plugin) {
|
||||
for (Player player : plugin.getServer().getOnlinePlayers()) {
|
||||
injectPlayer(player);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Invoked when the server is starting to send a packet to a player.
|
||||
* <p>
|
||||
* Note that this is not executed on the main thread.
|
||||
* @param reciever - the receiving player, NULL for early login/status packets.
|
||||
* @param remoteAddress - remote address of the sending client. Never NULL.
|
||||
*
|
||||
* @param receiver - the receiving player, NULL for early login/status packets.
|
||||
* @param channel - the channel that received the packet. Never NULL.
|
||||
* @param packet - the packet being sent.
|
||||
* @return The packet to send instead, or NULL to cancel the transmission.
|
||||
*/
|
||||
public Object onPacketOutAsync(Player reciever, Channel channel, Object packet) {
|
||||
public Object onPacketOutAsync(Player receiver, Channel channel, Object packet) {
|
||||
return packet;
|
||||
}
|
||||
|
||||
@@ -238,6 +268,7 @@ public abstract class TinyProtocol {
|
||||
* Invoked when the server has received a packet from a given player.
|
||||
* <p>
|
||||
* Use {@link Channel#remoteAddress()} to get the remote address of the client.
|
||||
*
|
||||
* @param sender - the player that sent the packet, NULL for early login/status packets.
|
||||
* @param channel - channel that received the packet. Never NULL.
|
||||
* @param packet - the packet being received.
|
||||
@@ -246,133 +277,145 @@ public abstract class TinyProtocol {
|
||||
public Object onPacketInAsync(Player sender, Channel channel, Object packet) {
|
||||
return packet;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Send a packet to a particular player.
|
||||
* <p>
|
||||
* Note that {@link #onPacketOutAsync(Player, Object)} will be invoked with this packet.
|
||||
* Note that {@link #onPacketOutAsync(Player, Channel, Object)} will be invoked with this packet.
|
||||
*
|
||||
* @param player - the destination player.
|
||||
* @param packet - the packet to send.
|
||||
*/
|
||||
public void sendPacket(Player player, Object packet) {
|
||||
sendPacket(getChannel(player), packet);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Send a packet to a particular client.
|
||||
* <p>
|
||||
* Note that {@link #onPacketOutAsync(Player, Object)} will be invoked with this packet.
|
||||
* Note that {@link #onPacketOutAsync(Player, Channel, Object)} will be invoked with this packet.
|
||||
*
|
||||
* @param channel - client identified by a channel.
|
||||
* @param packet - the packet to send.
|
||||
*/
|
||||
public void sendPacket(Channel channel, Object packet) {
|
||||
channel.pipeline().writeAndFlush(packet);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Pretend that a given packet has been received from a player.
|
||||
* <p>
|
||||
* Note that {@link #onPacketInAsync(Player, Object)} will be invoked with this packet.
|
||||
* Note that {@link #onPacketInAsync(Player, Channel, Object)} will be invoked with this packet.
|
||||
*
|
||||
* @param player - the player that sent the packet.
|
||||
* @param packet - the packet that will be received by the server.
|
||||
*/
|
||||
public void receivePacket(Player player, Object packet) {
|
||||
receivePacket(getChannel(player), packet);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Pretend that a given packet has been received from a given client.
|
||||
* <p>
|
||||
* Note that {@link #onPacketInAsync(Player, Object)} will be invoked with this packet.
|
||||
* Note that {@link #onPacketInAsync(Player, Channel, Object)} will be invoked with this packet.
|
||||
*
|
||||
* @param channel - client identified by a channel.
|
||||
* @param packet - the packet that will be received by the server.
|
||||
*/
|
||||
public void receivePacket(Channel channel, Object packet) {
|
||||
channel.pipeline().context("encoder").fireChannelRead(packet);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve the name of the channel injector, default implementation is "tiny-" + plugin name + "-" + a unique ID.
|
||||
* <p>
|
||||
* Note that this method will only be invoked once. It is no longer necessary to override this to support multiple instances.
|
||||
*
|
||||
* @return A unique channel handler name.
|
||||
*/
|
||||
protected String getHandlerName() {
|
||||
return "tiny-" + plugin.getName() + "-" + ID.incrementAndGet();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add a custom channel handler to the given player's channel pipeline,
|
||||
* allowing us to intercept sent and received packets.
|
||||
* Add a custom channel handler to the given player's channel pipeline, allowing us to intercept sent and received packets.
|
||||
* <p>
|
||||
* This will automatically be called when a player has logged in.
|
||||
*
|
||||
* @param player - the player to inject.
|
||||
*/
|
||||
public void injectPlayer(Player player) {
|
||||
injectChannelInternal(getChannel(player)).player = player;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add a custom channel handler to the given channel.
|
||||
* @param player - the channel to inject.
|
||||
*
|
||||
* @param channel - the channel to inject.
|
||||
* @return The intercepted channel, or NULL if it has already been injected.
|
||||
*/
|
||||
public void injectChannel(Channel channel) {
|
||||
injectChannelInternal(channel);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add a custom channel handler to the given channel.
|
||||
* @param player - the channel to inject.
|
||||
*
|
||||
* @param channel - the channel to inject.
|
||||
* @return The packet interceptor.
|
||||
*/
|
||||
private PacketInterceptor injectChannelInternal(Channel channel) {
|
||||
try {
|
||||
PacketInterceptor interceptor = (PacketInterceptor) channel.pipeline().get(handlerName);
|
||||
|
||||
|
||||
// Inject our packet interceptor
|
||||
if (interceptor == null) {
|
||||
interceptor = new PacketInterceptor();
|
||||
channel.pipeline().addBefore("packet_handler", handlerName, interceptor);
|
||||
uninjectedChannels.remove(channel);
|
||||
}
|
||||
return interceptor;
|
||||
|
||||
return interceptor;
|
||||
} catch (IllegalArgumentException e) {
|
||||
// Try again
|
||||
return (PacketInterceptor) channel.pipeline().get(handlerName);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve the Netty channel associated with a player. This is cached.
|
||||
*
|
||||
* @param player - the player.
|
||||
* @return The Netty channel.
|
||||
*/
|
||||
public Channel getChannel(Player player) {
|
||||
Channel channel = channelLookup.get(player.getName());
|
||||
|
||||
|
||||
// Lookup channel again
|
||||
if (channel == null) {
|
||||
Object connection = getConnection.get(getPlayerHandle.invoke(player));
|
||||
Object manager = getManager.get(connection);
|
||||
|
||||
|
||||
channelLookup.put(player.getName(), channel = getChannel.get(manager));
|
||||
}
|
||||
|
||||
return channel;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Uninject a specific player.
|
||||
*
|
||||
* @param player - the injected player.
|
||||
*/
|
||||
public void uninjectPlayer(Player player) {
|
||||
uninjectChannel(getChannel(player));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Uninject a specific channel.
|
||||
* <p>
|
||||
* This will also disable the automatic channel injection that occurs when a player has properly logged in.
|
||||
*
|
||||
* @param channel - the injected channel.
|
||||
*/
|
||||
public void uninjectChannel(final Channel channel) {
|
||||
@@ -380,88 +423,95 @@ public abstract class TinyProtocol {
|
||||
if (!closed) {
|
||||
uninjectedChannels.add(channel);
|
||||
}
|
||||
|
||||
|
||||
// See ChannelInjector in ProtocolLib, line 590
|
||||
channel.eventLoop().execute(new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
channel.pipeline().remove(handlerName);
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determine if the given player has been injected by TinyProtocol.
|
||||
*
|
||||
* @param player - the player.
|
||||
* @return TRUE if it is, FALSE otherwise.
|
||||
*/
|
||||
public boolean hasInjected(Player player) {
|
||||
return hasInjected(getChannel(player));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determine if the given channel has been injected by TinyProtocol.
|
||||
*
|
||||
* @param channel - the channel.
|
||||
* @return TRUE if it is, FALSE otherwise.
|
||||
*/
|
||||
public boolean hasInjected(Channel channel) {
|
||||
return channel.pipeline().get(handlerName) != null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Cease listening for packets. This is called automatically when your plugin is disabled.
|
||||
*/
|
||||
public final void close() {
|
||||
if (!closed) {
|
||||
closed = true;
|
||||
|
||||
|
||||
// Remove our handlers
|
||||
for (Player player : plugin.getServer().getOnlinePlayers()) {
|
||||
uninjectPlayer(player);
|
||||
}
|
||||
|
||||
// Clean up Bukkit
|
||||
HandlerList.unregisterAll(listener);
|
||||
unregisterChannelHandler();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Channel handler that is inserted into the player's channel pipeline, allowing us to intercept sent and received packets.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
private final class PacketInterceptor extends ChannelDuplexHandler {
|
||||
private final class PacketInterceptor extends ChannelDuplexHandler {
|
||||
// Updated by the login event
|
||||
public volatile Player player;
|
||||
|
||||
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
||||
// Intercept channel
|
||||
final Channel channel = ctx.channel();
|
||||
handleLoginStart(channel, msg);
|
||||
|
||||
|
||||
try {
|
||||
msg = onPacketInAsync(player, channel, msg);
|
||||
} catch (Exception e) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Error in onPacketInAsync().", e);
|
||||
}
|
||||
|
||||
|
||||
if (msg != null) {
|
||||
super.channelRead(ctx, msg);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
|
||||
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
|
||||
try {
|
||||
msg = onPacketOutAsync(player, ctx.channel(), msg);
|
||||
} catch (Exception e) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Error in onPacketOutAsync().", e);
|
||||
}
|
||||
|
||||
|
||||
if (msg != null) {
|
||||
super.write(ctx, msg, promise);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void handleLoginStart(Channel channel, Object packet) {
|
||||
if (PACKET_LOGIN_IN_START.isInstance(packet)) {
|
||||
GameProfile profile = getGameProfile.get(packet);
|
||||
File renamed without changes.
@@ -0,0 +1,5 @@
|
||||
before_install:
|
||||
- source "$HOME/.sdkman/bin/sdkman-init.sh"
|
||||
- sdk update
|
||||
- sdk install java 16.0.1.hs-adpt
|
||||
- sdk use java 16.0.1.hs-adpt
|
||||
@@ -0,0 +1,338 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>com.comphenix.protocol</groupId>
|
||||
<artifactId>ProtocolLib</artifactId>
|
||||
<name>ProtocolLib</name>
|
||||
<version>4.7.0-SNAPSHOT</version>
|
||||
|
||||
<description>Provides read/write access to the Minecraft protocol.</description>
|
||||
<url>https://github.com/dmulloy2/ProtocolLib</url>
|
||||
<packaging>jar</packaging>
|
||||
<inceptionYear>2012</inceptionYear>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<project.build.number></project.build.number>
|
||||
<project.fullVersion>${project.version}</project.fullVersion>
|
||||
|
||||
<powermock.version>2.0.7</powermock.version>
|
||||
<spigot.version>1.17.1-R0.1-SNAPSHOT</spigot.version>
|
||||
</properties>
|
||||
|
||||
<build>
|
||||
<defaultGoal>clean install</defaultGoal>
|
||||
<sourceDirectory>src/main/java</sourceDirectory>
|
||||
<testSourceDirectory>src/test/java</testSourceDirectory>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>src/main/resources</directory>
|
||||
<filtering>true</filtering>
|
||||
<excludes>
|
||||
<exclude>**/*.java</exclude>
|
||||
</excludes>
|
||||
</resource>
|
||||
</resources>
|
||||
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>3.2.4</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<shadedArtifactAttached>false</shadedArtifactAttached>
|
||||
<createDependencyReducedPom>false</createDependencyReducedPom>
|
||||
|
||||
<relocations>
|
||||
<relocation>
|
||||
<pattern>net.bytebuddy</pattern>
|
||||
<shadedPattern>com.comphenix.net.bytebuddy</shadedPattern>
|
||||
</relocation>
|
||||
</relocations>
|
||||
|
||||
<artifactSet>
|
||||
<excludes>
|
||||
<exclude>org.spigotmc:spigot</exclude>
|
||||
<exclude>org.spigotmc:spigot-api</exclude>
|
||||
<exclude>junit:junit</exclude>
|
||||
<exclude>io.netty:*</exclude>
|
||||
</excludes>
|
||||
</artifactSet>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.8.1</version>
|
||||
<configuration>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<version>3.2.0</version>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifestEntries>
|
||||
<Main-Class>com.comphenix.protocol.Application</Main-Class>
|
||||
<Implementation-Title>ProtocolLib</Implementation-Title>
|
||||
<Implementation-Version>${project.fullVersion}</Implementation-Version>
|
||||
<Implementation-Vendor>dmulloy2</Implementation-Vendor>
|
||||
</manifestEntries>
|
||||
</archive>
|
||||
<finalName>${project.name}</finalName>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>3.0.0-M5</version>
|
||||
<configuration>
|
||||
<trimStackTrace>false</trimStackTrace>
|
||||
<useFile>false</useFile>
|
||||
<systemProperties>
|
||||
<property>
|
||||
<name>projectVersion</name>
|
||||
<value>${project.version}</value>
|
||||
</property>
|
||||
</systemProperties>
|
||||
<!-- TODO figure out a better way to do this before Java 17 -->
|
||||
<!-- We're currently waiting on powermock, but we may need to switch -->
|
||||
<argLine>--illegal-access=permit</argLine>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<version>3.2.0</version>
|
||||
<configuration>
|
||||
<failOnError>false</failOnError>
|
||||
<encoding>ISO-8859-1</encoding>
|
||||
<doctitle>ProtocolLib JavaDocs</doctitle>
|
||||
<windowtitle>ProtocolLib JavaDocs</windowtitle>
|
||||
<bottom>Copyright © {inceptionYear}–{currentYear}
|
||||
Comphenix and dmulloy2. Licensed under the GNU GPL v2.</bottom>
|
||||
<additionalOptions>
|
||||
<additionalOption>-Xdoclint:none</additionalOption>
|
||||
</additionalOptions>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>install</phase>
|
||||
<goals>
|
||||
<goal>aggregate</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>jenkins</id>
|
||||
<activation>
|
||||
<property>
|
||||
<name>env.BUILD_NUMBER</name>
|
||||
</property>
|
||||
</activation>
|
||||
<properties>
|
||||
<project.build.number>-b${env.BUILD_NUMBER}</project.build.number>
|
||||
<project.fullVersion>${project.version}${project.build.number}</project.fullVersion>
|
||||
<!--<project.fullVersion>${project.version}</project.fullVersion>-->
|
||||
</properties>
|
||||
</profile>
|
||||
|
||||
<profile>
|
||||
<id>release-sign-artifacts</id>
|
||||
<activation>
|
||||
<property>
|
||||
<name>performRelease</name>
|
||||
<value>true</value>
|
||||
</property>
|
||||
</activation>
|
||||
<properties>
|
||||
<project.fullVersion>${project.version}</project.fullVersion>
|
||||
</properties>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-source-plugin</artifactId>
|
||||
<version>3.2.1</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-sources</id>
|
||||
<goals>
|
||||
<goal>jar</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<version>3.2.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-javadocs</id>
|
||||
<goals>
|
||||
<goal>jar</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<!-- TODO: Look into signing releases
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-gpg-plugin</artifactId>
|
||||
<version>1.6</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>sign-artifacts</id>
|
||||
<phase>verify</phase>
|
||||
<goals>
|
||||
<goal>sign</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
-->
|
||||
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
</profiles>
|
||||
|
||||
<scm>
|
||||
<connection>scm:git:git://github.com/dmulloy2/ProtocolLib.git</connection>
|
||||
<developerConnection>scm:git:git@github.com:dmulloy2/ProtocolLib.git</developerConnection>
|
||||
<url>https://github.com/dmulloy2/ProtocolLib</url>
|
||||
</scm>
|
||||
|
||||
<licenses>
|
||||
<license>
|
||||
<name>GNU GENERAL PUBLIC LICENSE - Version 2, June 1991</name>
|
||||
<url>http://www.gnu.org/licenses/gpl-2.0.txt</url>
|
||||
<distribution>repo</distribution>
|
||||
</license>
|
||||
</licenses>
|
||||
|
||||
<developers>
|
||||
<developer>
|
||||
<id>dmulloy2</id>
|
||||
<name>Dan Mulloy</name>
|
||||
<url>http://dmulloy2.net/</url>
|
||||
<roles>
|
||||
<role>developer</role>
|
||||
<role>maintainer</role>
|
||||
</roles>
|
||||
</developer>
|
||||
<developer>
|
||||
<id>aadnk</id>
|
||||
<name>Kristian S. Stangeland</name>
|
||||
<email>kr_stang@hotmail.com</email>
|
||||
<url>http://comphenix.net/</url>
|
||||
<roles>
|
||||
<role>former author</role>
|
||||
</roles>
|
||||
<timezone>1</timezone>
|
||||
</developer>
|
||||
</developers>
|
||||
|
||||
<distributionManagement>
|
||||
<repository>
|
||||
<id>dmulloy2-releases</id>
|
||||
<url>https://repo.dmulloy2.net/repository/releases/</url>
|
||||
</repository>
|
||||
<snapshotRepository>
|
||||
<id>dmulloy2-snapshots</id>
|
||||
<url>https://repo.dmulloy2.net/repository/snapshots/</url>
|
||||
</snapshotRepository>
|
||||
</distributionManagement>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>dmulloy2-repo</id>
|
||||
<url>https://repo.dmulloy2.net/repository/public/</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>spigot-repo</id>
|
||||
<url>https://hub.spigotmc.org/nexus/content/groups/public/</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<dependencies>
|
||||
<!-- Compile with the old version of Netty for backwards compat -->
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-all</artifactId>
|
||||
<version>4.0.23.Final</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.spigotmc</groupId>
|
||||
<artifactId>spigot-api</artifactId>
|
||||
<version>${spigot.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.spigotmc</groupId>
|
||||
<artifactId>spigot</artifactId>
|
||||
<version>${spigot.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-gson</artifactId>
|
||||
<version>4.5.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/net.bytebuddy/byte-buddy -->
|
||||
<dependency>
|
||||
<groupId>net.bytebuddy</groupId>
|
||||
<artifactId>byte-buddy</artifactId>
|
||||
<version>1.11.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Testing dependencies -->
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>4.13.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
<version>3.5.10</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.powermock</groupId>
|
||||
<artifactId>powermock-module-junit4</artifactId>
|
||||
<version>${powermock.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.powermock</groupId>
|
||||
<artifactId>powermock-api-mockito2</artifactId>
|
||||
<version>${powermock.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
+25
-45
@@ -2,16 +2,16 @@
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
@@ -42,83 +42,63 @@ public interface AsynchronousManager {
|
||||
* @param listener - the packet listener that will receive these asynchronous events.
|
||||
* @return An asynchronous handler.
|
||||
*/
|
||||
public abstract AsyncListenerHandler registerAsyncHandler(PacketListener listener);
|
||||
AsyncListenerHandler registerAsyncHandler(PacketListener listener);
|
||||
|
||||
/**
|
||||
* Unregisters and closes the given asynchronous handler.
|
||||
* @param handler - asynchronous handler.
|
||||
*/
|
||||
public abstract void unregisterAsyncHandler(AsyncListenerHandler handler);
|
||||
void unregisterAsyncHandler(AsyncListenerHandler handler);
|
||||
|
||||
/**
|
||||
* Unregisters and closes the first asynchronous handler associated with the given listener.
|
||||
* @param handler - asynchronous handler.
|
||||
* @param listener - asynchronous listener
|
||||
*/
|
||||
public abstract void unregisterAsyncHandler(PacketListener listener);
|
||||
void unregisterAsyncHandler(PacketListener listener);
|
||||
|
||||
/**
|
||||
* Unregisters every asynchronous handler associated with this plugin.
|
||||
* @param plugin - the original plugin.
|
||||
*/
|
||||
public void unregisterAsyncHandlers(Plugin plugin);
|
||||
|
||||
/**
|
||||
* Retrieves a immutable set containing the ID of the sent server packets that will be
|
||||
* observed by the asynchronous listeners.
|
||||
* <p>
|
||||
* Deprecated: Use {@link #getSendingTypes()} instead.
|
||||
* @return Every filtered server packet.
|
||||
*/
|
||||
@Deprecated
|
||||
public abstract Set<Integer> getSendingFilters();
|
||||
void unregisterAsyncHandlers(Plugin plugin);
|
||||
|
||||
/**
|
||||
* Retrieves a immutable set containing the types of the sent server packets that will be
|
||||
* Retrieves a immutable set containing the types of the sent server packets that will be
|
||||
* observed by the asynchronous listeners.
|
||||
* @return Every filtered server packet.
|
||||
*/
|
||||
public abstract Set<PacketType> getSendingTypes();
|
||||
|
||||
/**
|
||||
* Retrieves a immutable set containing the ID of the recieved client packets that will be
|
||||
* <p>
|
||||
* Deprecated: Use {@link #getReceivingTypes()} instead.
|
||||
* observed by the asynchronous listeners.
|
||||
* @return Every filtered client packet.
|
||||
*/
|
||||
@Deprecated
|
||||
public abstract Set<Integer> getReceivingFilters();
|
||||
|
||||
Set<PacketType> getSendingTypes();
|
||||
|
||||
/**
|
||||
* Retrieves a immutable set containing the types of the received client packets that will be
|
||||
* observed by the asynchronous listeners.
|
||||
* @return Every filtered client packet.
|
||||
*/
|
||||
public abstract Set<PacketType> getReceivingTypes();
|
||||
Set<PacketType> getReceivingTypes();
|
||||
|
||||
/**
|
||||
* Determine if a given synchronous packet has asynchronous listeners.
|
||||
* @param packet - packet to test.
|
||||
* @return TRUE if it does, FALSE otherwise.
|
||||
*/
|
||||
public abstract boolean hasAsynchronousListeners(PacketEvent packet);
|
||||
boolean hasAsynchronousListeners(PacketEvent packet);
|
||||
|
||||
/**
|
||||
* Retrieve the default packet stream.
|
||||
* @return Default packet stream.
|
||||
*/
|
||||
public abstract PacketStream getPacketStream();
|
||||
PacketStream getPacketStream();
|
||||
|
||||
/**
|
||||
* Retrieve the default error reporter.
|
||||
* @return Default reporter.
|
||||
*/
|
||||
public abstract ErrorReporter getErrorReporter();
|
||||
ErrorReporter getErrorReporter();
|
||||
|
||||
/**
|
||||
* Remove listeners, close threads and transmit every delayed packet.
|
||||
*/
|
||||
public abstract void cleanupAll();
|
||||
void cleanupAll();
|
||||
|
||||
/**
|
||||
* Signal that a packet is ready to be transmitted.
|
||||
@@ -127,29 +107,29 @@ public interface AsynchronousManager {
|
||||
* has been called previously.
|
||||
* @param packet - packet to signal.
|
||||
*/
|
||||
public abstract void signalPacketTransmission(PacketEvent packet);
|
||||
void signalPacketTransmission(PacketEvent packet);
|
||||
|
||||
/**
|
||||
* Register a synchronous listener that handles packets when they time out.
|
||||
* @param listener - synchronous listener that will handle timed out packets.
|
||||
*/
|
||||
public abstract void registerTimeoutHandler(PacketListener listener);
|
||||
void registerTimeoutHandler(PacketListener listener);
|
||||
|
||||
/**
|
||||
* Unregisters a given timeout listener.
|
||||
* @param listener - the timeout listener to unregister.
|
||||
*/
|
||||
public abstract void unregisterTimeoutHandler(PacketListener listener);
|
||||
void unregisterTimeoutHandler(PacketListener listener);
|
||||
|
||||
/**
|
||||
* Get a immutable set of every registered timeout handler.
|
||||
* @return Set of every registered timeout handler.
|
||||
*/
|
||||
public abstract Set<PacketListener> getTimeoutHandlers();
|
||||
Set<PacketListener> getTimeoutHandlers();
|
||||
|
||||
/**
|
||||
* Get an immutable set of every registered asynchronous packet listener.
|
||||
* @return Set of every asynchronous packet listener.
|
||||
*/
|
||||
public abstract Set<PacketListener> getAsyncHandlers();
|
||||
}
|
||||
Set<PacketListener> getAsyncHandlers();
|
||||
}
|
||||
+11
-11
@@ -2,16 +2,16 @@
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
@@ -66,6 +66,7 @@ abstract class CommandBase implements CommandExecutor {
|
||||
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;
|
||||
@@ -75,13 +76,12 @@ abstract class CommandBase implements CommandExecutor {
|
||||
if (args != null && args.length >= minimumArgumentCount) {
|
||||
return handleCommand(sender, args);
|
||||
} else {
|
||||
sender.sendMessage(ChatColor.RED + "Insufficient commands. You need at least " + minimumArgumentCount);
|
||||
sender.sendMessage(ChatColor.RED + "Insufficient arguments. You need at least " + minimumArgumentCount);
|
||||
return false;
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
reporter.reportDetailed(this,
|
||||
Report.newBuilder(REPORT_COMMAND_ERROR).error(e).messageParam(name).callerParam(sender, label, args)
|
||||
} catch (Throwable ex) {
|
||||
reporter.reportDetailed(this,
|
||||
Report.newBuilder(REPORT_COMMAND_ERROR).error(ex).messageParam(name).callerParam(sender, label, args)
|
||||
);
|
||||
return true;
|
||||
}
|
||||
+3
-4
@@ -295,7 +295,7 @@ public class CommandFilter extends CommandBase {
|
||||
@Override
|
||||
public boolean handle(PacketEvent event, Filter filter, Exception ex) {
|
||||
reporter.reportMinimal(plugin, "filterEvent(PacketEvent)", ex, event);
|
||||
reporter.reportWarning(this,
|
||||
reporter.reportWarning(this,
|
||||
Report.newBuilder(REPORT_FILTER_REMOVED_FOR_ERROR).messageParam(filter.getName(), ex.getClass().getSimpleName())
|
||||
);
|
||||
return false;
|
||||
@@ -321,7 +321,6 @@ public class CommandFilter extends CommandBase {
|
||||
* @param event - the event.
|
||||
* @param handler - failure handler.
|
||||
* @return TRUE if we should, FALSE otherwise.
|
||||
* @throws FilterFailedException If one of the filters failed.
|
||||
*/
|
||||
public boolean filterEvent(PacketEvent event, FilterFailedHandler handler) {
|
||||
for (Iterator<Filter> it = filters.iterator(); it.hasNext(); ) {
|
||||
@@ -389,7 +388,7 @@ public class CommandFilter extends CommandBase {
|
||||
|
||||
// Make sure we can use the conversable interface
|
||||
if (sender instanceof Conversable) {
|
||||
final MultipleLinesPrompt prompt =
|
||||
final MultipleLinesPrompt prompt =
|
||||
new MultipleLinesPrompt(new CompilationSuccessCanceller(), "function(event, packet) {");
|
||||
|
||||
new ConversationFactory(plugin).
|
||||
@@ -424,7 +423,7 @@ public class CommandFilter extends CommandBase {
|
||||
whom.sendRawMessage(ChatColor.RED + "Cancelled filter.");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
reporter.reportDetailed(this,
|
||||
reporter.reportDetailed(this,
|
||||
Report.newBuilder(REPORT_CANNOT_HANDLE_CONVERSATION).error(e).callerParam(event)
|
||||
);
|
||||
}
|
||||
+3
-71
@@ -29,13 +29,12 @@ import java.util.WeakHashMap;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import net.sf.cglib.proxy.Factory;
|
||||
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import com.comphenix.protocol.utility.ByteBuddyGenerated;
|
||||
import com.comphenix.protocol.PacketType.Sender;
|
||||
import com.comphenix.protocol.concurrency.PacketTypeSet;
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
@@ -77,11 +76,6 @@ class CommandPacket extends CommandBase {
|
||||
*/
|
||||
public static final int PAGE_LINE_COUNT = 9;
|
||||
|
||||
/**
|
||||
* Number of bytes before we do a hex dump.
|
||||
*/
|
||||
private static final int HEX_DUMP_THRESHOLD = 256;
|
||||
|
||||
private Plugin plugin;
|
||||
private Logger logger;
|
||||
private ProtocolManager manager;
|
||||
@@ -371,7 +365,7 @@ class CommandPacket extends CommandBase {
|
||||
logger.info("Initial packet:\n" + original + " -> ");
|
||||
}
|
||||
|
||||
logger.info(shortDescription + ":\n" + getPacketDescription(
|
||||
logger.info(shortDescription + ":\n" + HexDumper.getPacketDescription(
|
||||
event.getPacket())
|
||||
);
|
||||
} catch (IllegalAccessException e) {
|
||||
@@ -428,7 +422,7 @@ class CommandPacket extends CommandBase {
|
||||
*/
|
||||
private void savePacketState(PacketEvent event) {
|
||||
try {
|
||||
originalPackets.put(event, getPacketDescription(event.getPacket()));
|
||||
originalPackets.put(event, HexDumper.getPacketDescription(event.getPacket()));
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException("Cannot read packet.", e);
|
||||
}
|
||||
@@ -450,68 +444,6 @@ class CommandPacket extends CommandBase {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a detailed string representation of the given packet.
|
||||
* @param packetContainer - the packet to describe.
|
||||
* @return The detailed description.
|
||||
* @throws IllegalAccessException An error occured.
|
||||
*/
|
||||
public String getPacketDescription(PacketContainer packetContainer) throws IllegalAccessException {
|
||||
Object packet = packetContainer.getHandle();
|
||||
Class<?> clazz = packet.getClass();
|
||||
|
||||
// Get the first Minecraft super class
|
||||
while (clazz != null && clazz != Object.class &&
|
||||
(!MinecraftReflection.isMinecraftClass(clazz) ||
|
||||
Factory.class.isAssignableFrom(clazz))) {
|
||||
clazz = clazz.getSuperclass();
|
||||
}
|
||||
|
||||
return PrettyPrinter.printObject(packet, clazz, MinecraftReflection.getPacketClass(), PrettyPrinter.RECURSE_DEPTH, new ObjectPrinter() {
|
||||
@Override
|
||||
public boolean print(StringBuilder output, Object value) {
|
||||
// Special case
|
||||
if (value instanceof byte[]) {
|
||||
byte[] data = (byte[]) value;
|
||||
|
||||
if (data.length > HEX_DUMP_THRESHOLD) {
|
||||
output.append("[");
|
||||
HexDumper.defaultDumper().appendTo(output, data);
|
||||
output.append("]");
|
||||
return true;
|
||||
}
|
||||
} else if (value != null) {
|
||||
EquivalentConverter<Object> converter = findConverter(value.getClass());
|
||||
|
||||
if (converter != null) {
|
||||
output.append(converter.getSpecific(value));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the closest equivalent converter to a specific class.
|
||||
* @param clazz - the class.
|
||||
* @return The closest converter, or NULL if not found,
|
||||
*/
|
||||
private EquivalentConverter<Object> findConverter(Class<?> clazz) {
|
||||
Map<Class<?>, EquivalentConverter<Object>> converters = BukkitConverters.getConvertersForGeneric();
|
||||
|
||||
while (clazz != null) {
|
||||
EquivalentConverter<Object> result = converters.get(clazz);
|
||||
|
||||
if (result != null)
|
||||
return result;
|
||||
else
|
||||
clazz = clazz.getSuperclass();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public PacketListener updatePacketListener() {
|
||||
if (listener != null) {
|
||||
@@ -0,0 +1,292 @@
|
||||
/**
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
package com.comphenix.protocol;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.bukkit.plugin.PluginDescriptionFile;
|
||||
|
||||
import com.comphenix.protocol.error.DetailedErrorReporter;
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.events.PacketListener;
|
||||
import com.comphenix.protocol.timing.TimedListenerManager;
|
||||
import com.comphenix.protocol.timing.TimingReportGenerator;
|
||||
import com.comphenix.protocol.updater.Updater;
|
||||
import com.comphenix.protocol.updater.Updater.UpdateType;
|
||||
import com.comphenix.protocol.utility.Closer;
|
||||
|
||||
/**
|
||||
* Handles the "protocol" administration command.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
class CommandProtocol extends CommandBase {
|
||||
/**
|
||||
* Name of this command.
|
||||
*/
|
||||
public static final String NAME = "protocol";
|
||||
|
||||
private Plugin plugin;
|
||||
private Updater updater;
|
||||
private ProtocolConfig config;
|
||||
|
||||
public CommandProtocol(ErrorReporter reporter, Plugin plugin, Updater updater, ProtocolConfig config) {
|
||||
super(reporter, CommandBase.PERMISSION_ADMIN, NAME, 1);
|
||||
this.plugin = plugin;
|
||||
this.updater = updater;
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean handleCommand(CommandSender sender, String[] args) {
|
||||
String subCommand = args[0];
|
||||
|
||||
// Only return TRUE if we executed the correct command
|
||||
if (subCommand.equalsIgnoreCase("config") || subCommand.equalsIgnoreCase("reload")) {
|
||||
reloadConfiguration(sender);
|
||||
} else if (subCommand.equalsIgnoreCase("check")) {
|
||||
checkVersion(sender, true);
|
||||
} else if (subCommand.equalsIgnoreCase("update")) {
|
||||
updateVersion(sender, true);
|
||||
} else if (subCommand.equalsIgnoreCase("timings")) {
|
||||
toggleTimings(sender, args);
|
||||
} else if (subCommand.equalsIgnoreCase("listeners")) {
|
||||
printListeners(sender);
|
||||
} else if (subCommand.equalsIgnoreCase("version")) {
|
||||
printVersion(sender);
|
||||
} else if (subCommand.equalsIgnoreCase("dump")) {
|
||||
dump(sender);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void checkVersion(final CommandSender sender, boolean command) {
|
||||
performUpdate(sender, UpdateType.NO_DOWNLOAD, command);
|
||||
}
|
||||
|
||||
public void updateVersion(final CommandSender sender, boolean command) {
|
||||
performUpdate(sender, UpdateType.DEFAULT, command);
|
||||
}
|
||||
|
||||
// Display every listener on the server
|
||||
private void printListeners(final CommandSender sender) {
|
||||
ProtocolManager manager = ProtocolLibrary.getProtocolManager();
|
||||
|
||||
sender.sendMessage(ChatColor.GOLD + "Packet listeners:");
|
||||
for (PacketListener listener : manager.getPacketListeners()) {
|
||||
sender.sendMessage(ChatColor.GOLD + " - " + listener);
|
||||
}
|
||||
|
||||
// Along with every asynchronous listener
|
||||
sender.sendMessage(ChatColor.GOLD + "Asynchronous listeners:");
|
||||
for (PacketListener listener : manager.getAsynchronousManager().getAsyncHandlers()) {
|
||||
sender.sendMessage(ChatColor.GOLD + " - " + listener);
|
||||
}
|
||||
}
|
||||
|
||||
private void performUpdate(final CommandSender sender, UpdateType type, final boolean command) {
|
||||
if (updater.isChecking()) {
|
||||
sender.sendMessage(ChatColor.RED + "Already checking for an update.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Perform on an async thread
|
||||
Runnable notify = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (command) {
|
||||
sender.sendMessage(ChatColor.YELLOW + "[ProtocolLib] " + updater.getResult());
|
||||
String remoteVersion = updater.getRemoteVersion();
|
||||
if (remoteVersion != null) {
|
||||
sender.sendMessage(ChatColor.YELLOW + "Remote version: " + remoteVersion);
|
||||
sender.sendMessage(ChatColor.YELLOW + "Current version: " + plugin.getDescription().getVersion());
|
||||
}
|
||||
} else if (updater.shouldNotify() || config.isDebug()) {
|
||||
sender.sendMessage(ChatColor.YELLOW + "[ProtocolLib] " + updater.getResult());
|
||||
}
|
||||
|
||||
updater.removeListener(this);
|
||||
updateFinished();
|
||||
}
|
||||
};
|
||||
updater.start(type);
|
||||
updater.addListener(notify);
|
||||
}
|
||||
|
||||
private void toggleTimings(CommandSender sender, String[] args) {
|
||||
TimedListenerManager manager = TimedListenerManager.getInstance();
|
||||
boolean state = !manager.isTiming(); // toggle
|
||||
|
||||
// Parse the boolean parameter
|
||||
if (args.length == 2) {
|
||||
Boolean parsed = parseBoolean(toQueue(args, 2), "start");
|
||||
|
||||
if (parsed != null) {
|
||||
state = parsed;
|
||||
} else {
|
||||
sender.sendMessage(ChatColor.RED + "Specify a state: ON or OFF.");
|
||||
return;
|
||||
}
|
||||
} else if (args.length > 2) {
|
||||
sender.sendMessage(ChatColor.RED + "Too many parameters.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Now change the state
|
||||
if (state) {
|
||||
if (manager.startTiming())
|
||||
sender.sendMessage(ChatColor.GOLD + "Started timing packet listeners.");
|
||||
else
|
||||
sender.sendMessage(ChatColor.RED + "Packet timing already started.");
|
||||
} else {
|
||||
if (manager.stopTiming()) {
|
||||
saveTimings(manager);
|
||||
sender.sendMessage(ChatColor.GOLD + "Stopped and saved result in plugin folder.");
|
||||
} else {
|
||||
sender.sendMessage(ChatColor.RED + "Packet timing already stopped.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void saveTimings(TimedListenerManager manager) {
|
||||
try {
|
||||
File destination = new File(plugin.getDataFolder(), "Timings - " + System.currentTimeMillis() + ".txt");
|
||||
TimingReportGenerator generator = new TimingReportGenerator();
|
||||
|
||||
// Print to a text file
|
||||
generator.saveTo(destination, manager);
|
||||
manager.clear();
|
||||
|
||||
} catch (IOException e) {
|
||||
reporter.reportMinimal(plugin, "saveTimings()", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent further automatic updates until the next delay.
|
||||
*/
|
||||
public void updateFinished() {
|
||||
long currentTime = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis());
|
||||
|
||||
config.setAutoLastTime(currentTime);
|
||||
config.saveAll();
|
||||
}
|
||||
|
||||
public void reloadConfiguration(CommandSender sender) {
|
||||
plugin.reloadConfig();
|
||||
sender.sendMessage(ChatColor.YELLOW + "Reloaded configuration!");
|
||||
}
|
||||
|
||||
private void printVersion(CommandSender sender) {
|
||||
PluginDescriptionFile desc = plugin.getDescription();
|
||||
|
||||
sender.sendMessage(ChatColor.GREEN + desc.getName() + ChatColor.WHITE + " v" + ChatColor.GREEN + desc.getVersion());
|
||||
sender.sendMessage(ChatColor.WHITE + "Authors: " + ChatColor.GREEN + "dmulloy2" + ChatColor.WHITE + " and " + ChatColor.GREEN + "Comphenix");
|
||||
sender.sendMessage(ChatColor.WHITE + "Issues: " + ChatColor.GREEN + "https://github.com/dmulloy2/ProtocolLib/issues");
|
||||
}
|
||||
|
||||
private static SimpleDateFormat FILE_FORMAT;
|
||||
private static SimpleDateFormat TIMESTAMP_FORMAT;
|
||||
|
||||
private void dump(CommandSender sender) {
|
||||
Closer closer = Closer.create();
|
||||
|
||||
if (FILE_FORMAT == null)
|
||||
FILE_FORMAT = new SimpleDateFormat("yyyy-MM-dd_HH.mm.ss");
|
||||
if (TIMESTAMP_FORMAT == null)
|
||||
TIMESTAMP_FORMAT = new SimpleDateFormat("MM/dd/yy HH:mm:ss");
|
||||
|
||||
try {
|
||||
Date date = new Date();
|
||||
File file = new File(plugin.getDataFolder(), "dump-" + FILE_FORMAT.format(date) + ".txt");
|
||||
if (file.exists()) {
|
||||
file.delete();
|
||||
}
|
||||
|
||||
file.createNewFile();
|
||||
|
||||
FileWriter fw = closer.register(new FileWriter(file));
|
||||
PrintWriter pw = closer.register(new PrintWriter(fw));
|
||||
|
||||
pw.println("ProtocolLib Dump");
|
||||
pw.println("Timestamp: " + TIMESTAMP_FORMAT.format(date));
|
||||
pw.println();
|
||||
|
||||
pw.println("ProtocolLib Version: " + plugin.toString());
|
||||
pw.println("Bukkit Version: " + plugin.getServer().getBukkitVersion());
|
||||
pw.println("Server Version: " + plugin.getServer().getVersion());
|
||||
pw.println("Java Version: " + System.getProperty("java.version"));
|
||||
pw.println();
|
||||
|
||||
ProtocolManager manager = ProtocolLibrary.getProtocolManager();
|
||||
pw.println("ProtocolLib: " + DetailedErrorReporter.getStringDescription(plugin));
|
||||
pw.println("Manager: " + DetailedErrorReporter.getStringDescription(manager));
|
||||
pw.println();
|
||||
|
||||
Set<PacketListener> listeners = manager.getPacketListeners();
|
||||
Set<Plugin> plugins = new HashSet<>();
|
||||
|
||||
if (!listeners.isEmpty()) {
|
||||
pw.println("Listeners:");
|
||||
|
||||
for (PacketListener listener : listeners) {
|
||||
pw.println(DetailedErrorReporter.getStringDescription(listener));
|
||||
|
||||
Plugin plugin = listener.getPlugin();
|
||||
if (plugin != null) {
|
||||
plugins.add(plugin);
|
||||
} else {
|
||||
pw.println("(Missing plugin!)");
|
||||
}
|
||||
}
|
||||
|
||||
pw.println();
|
||||
} else {
|
||||
pw.println("No listeners");
|
||||
}
|
||||
|
||||
if (!plugins.isEmpty()) {
|
||||
pw.println("Plugins Using ProtocolLib:");
|
||||
for (Plugin plugin : plugins) {
|
||||
pw.println(plugin.getName() + " by " + plugin.getDescription().getAuthors());
|
||||
}
|
||||
}
|
||||
|
||||
sender.sendMessage("Data dump written to " + file.getAbsolutePath());
|
||||
} catch (IOException ex) {
|
||||
ProtocolLogger.log(Level.SEVERE, "Failed to create dump:", ex);
|
||||
sender.sendMessage(ChatColor.RED + "Failed to create dump! Check console!");
|
||||
} finally {
|
||||
closer.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
File renamed without changes.
@@ -0,0 +1,268 @@
|
||||
/**
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2017 Dan Mulloy
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
package com.comphenix.protocol;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.text.MessageFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.logging.*;
|
||||
|
||||
import com.comphenix.protocol.PacketType.Protocol;
|
||||
import com.comphenix.protocol.PacketType.Sender;
|
||||
import com.comphenix.protocol.events.ListeningWhitelist;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.events.PacketListener;
|
||||
import com.comphenix.protocol.injector.netty.WirePacket;
|
||||
import com.google.common.base.Charsets;
|
||||
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.craftbukkit.libs.org.apache.commons.io.HexDump;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
/**
|
||||
* Logs packets to a given stream
|
||||
* @author dmulloy2
|
||||
*/
|
||||
public class PacketLogging implements CommandExecutor, PacketListener {
|
||||
public static final String NAME = "packetlog";
|
||||
|
||||
private List<PacketType> sendingTypes = new ArrayList<>();
|
||||
private List<PacketType> receivingTypes = new ArrayList<>();
|
||||
|
||||
private ListeningWhitelist sendingWhitelist;
|
||||
private ListeningWhitelist receivingWhitelist;
|
||||
|
||||
private Logger fileLogger;
|
||||
private LogLocation location = LogLocation.FILE;
|
||||
|
||||
private final ProtocolManager manager;
|
||||
private final Plugin plugin;
|
||||
|
||||
PacketLogging(Plugin plugin, ProtocolManager manager) {
|
||||
this.plugin = plugin;
|
||||
this.manager = manager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
PacketType type = null;
|
||||
|
||||
try {
|
||||
if (args.length > 2) {
|
||||
Protocol protocol;
|
||||
|
||||
try {
|
||||
protocol = Protocol.valueOf(args[0].toUpperCase());
|
||||
} catch (IllegalArgumentException ex) {
|
||||
sender.sendMessage(ChatColor.RED + "Unknown protocol " + args[0]);
|
||||
return true;
|
||||
}
|
||||
|
||||
Sender pSender;
|
||||
|
||||
try {
|
||||
pSender = Sender.valueOf(args[1].toUpperCase());
|
||||
} catch (IllegalArgumentException ex) {
|
||||
sender.sendMessage(ChatColor.RED + "Unknown sender: " + args[1]);
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
try { // Try IDs first
|
||||
int id = Integer.parseInt(args[2]);
|
||||
type = PacketType.findCurrent(protocol, pSender, id);
|
||||
} catch (NumberFormatException ex) { // Check packet names
|
||||
String name = args[2];
|
||||
for (PacketType packet : PacketType.values()) {
|
||||
if (packet.getProtocol() == protocol && packet.getSender() == pSender) {
|
||||
if (packet.name().equalsIgnoreCase(name)) {
|
||||
type = packet;
|
||||
break;
|
||||
}
|
||||
for (String className : packet.getClassNames()) {
|
||||
if (className.equalsIgnoreCase(name)) {
|
||||
type = packet;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IllegalArgumentException ex) { // RIP
|
||||
type = null;
|
||||
}
|
||||
|
||||
if (type == null) {
|
||||
sender.sendMessage(ChatColor.RED + "Unknown packet: " + args[2]);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (args.length > 3) {
|
||||
if (args[3].equalsIgnoreCase("console")) {
|
||||
this.location = LogLocation.CONSOLE;
|
||||
} else {
|
||||
this.location = LogLocation.FILE;
|
||||
}
|
||||
}
|
||||
|
||||
if (pSender == Sender.CLIENT) {
|
||||
if (receivingTypes.contains(type)) {
|
||||
receivingTypes.remove(type);
|
||||
} else {
|
||||
receivingTypes.add(type);
|
||||
}
|
||||
} else {
|
||||
if (sendingTypes.contains(type)) {
|
||||
sendingTypes.remove(type);
|
||||
} else {
|
||||
sendingTypes.add(type);
|
||||
}
|
||||
}
|
||||
|
||||
startLogging();
|
||||
sender.sendMessage(ChatColor.GREEN + "Now logging " + type.getPacketClass().getSimpleName());
|
||||
return true;
|
||||
}
|
||||
|
||||
sender.sendMessage(ChatColor.RED + "Invalid syntax: /packetlog <protocol> <sender> <packet> [location]");
|
||||
return true;
|
||||
} catch (Throwable ex) {
|
||||
sender.sendMessage(ChatColor.RED + "Failed to parse command: " + ex.toString());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private void startLogging() {
|
||||
manager.removePacketListener(this);
|
||||
|
||||
if (sendingTypes.isEmpty() && receivingTypes.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.sendingWhitelist = ListeningWhitelist.newBuilder().types(sendingTypes).build();
|
||||
this.receivingWhitelist = ListeningWhitelist.newBuilder().types(receivingTypes).build();
|
||||
|
||||
// Setup the file logger if it hasn't been already
|
||||
if (location == LogLocation.FILE && fileLogger == null) {
|
||||
fileLogger = Logger.getLogger("ProtocolLib-FileLogging");
|
||||
|
||||
for (Handler handler : fileLogger.getHandlers())
|
||||
fileLogger.removeHandler(handler);
|
||||
fileLogger.setUseParentHandlers(false);
|
||||
|
||||
try {
|
||||
File logFile = new File(plugin.getDataFolder(), "log.log");
|
||||
FileHandler handler = new FileHandler(logFile.getAbsolutePath(), true);
|
||||
handler.setFormatter(new LogFormatter());
|
||||
fileLogger.addHandler(handler);
|
||||
} catch (IOException ex) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Failed to obtain log file:", ex);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
manager.addPacketListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketSending(PacketEvent event) {
|
||||
log(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketReceiving(PacketEvent event) {
|
||||
log(event);
|
||||
}
|
||||
|
||||
// Here's where the magic happens
|
||||
|
||||
private static String hexDump(byte[] bytes) throws IOException {
|
||||
try (ByteArrayOutputStream output = new ByteArrayOutputStream()) {
|
||||
HexDump.dump(bytes, 0, output, 0);
|
||||
return new String(output.toByteArray(), Charsets.UTF_8);
|
||||
}
|
||||
}
|
||||
|
||||
private void log(PacketEvent event) {
|
||||
try {
|
||||
byte[] bytes = WirePacket.bytesFromPacket(event.getPacket());
|
||||
String hexDump = hexDump(bytes);
|
||||
|
||||
if (location == LogLocation.FILE) {
|
||||
fileLogger.log(Level.INFO, event.getPacketType() + ":");
|
||||
fileLogger.log(Level.INFO, hexDump);
|
||||
fileLogger.log(Level.INFO, "");
|
||||
} else {
|
||||
System.out.println(event.getPacketType() + ":");
|
||||
System.out.println(hexDump);
|
||||
System.out.println();
|
||||
}
|
||||
} catch (Throwable ex) {
|
||||
plugin.getLogger().log(Level.WARNING, "Failed to log packet " + event.getPacketType() + ":", ex);
|
||||
plugin.getLogger().log(Level.WARNING, "Clearing packet logger...");
|
||||
|
||||
sendingTypes.clear();
|
||||
receivingTypes.clear();
|
||||
startLogging();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListeningWhitelist getSendingWhitelist() {
|
||||
return sendingWhitelist;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListeningWhitelist getReceivingWhitelist() {
|
||||
return receivingWhitelist;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Plugin getPlugin() {
|
||||
return plugin;
|
||||
}
|
||||
|
||||
private enum LogLocation {
|
||||
CONSOLE, FILE
|
||||
}
|
||||
|
||||
private static class LogFormatter extends Formatter {
|
||||
private static final SimpleDateFormat DATE = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
private static final String LINE_SEPARATOR = System.getProperty("line.separator");
|
||||
private static final String FORMAT = "[{0}] {1}";
|
||||
|
||||
@Override
|
||||
public String format(LogRecord record) {
|
||||
String string = formatMessage(record);
|
||||
if (string.isEmpty()) {
|
||||
return LINE_SEPARATOR;
|
||||
}
|
||||
|
||||
StringBuilder message = new StringBuilder();
|
||||
message.append(MessageFormat.format(FORMAT, DATE.format(record.getMillis()), string));
|
||||
message.append(LINE_SEPARATOR);
|
||||
return message.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
+25
-8
@@ -2,16 +2,16 @@
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
@@ -24,6 +24,7 @@ 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.
|
||||
@@ -37,7 +38,7 @@ public interface PacketStream {
|
||||
* @param packet - packet to send.
|
||||
* @throws InvocationTargetException - if an error occured when sending the packet.
|
||||
*/
|
||||
public void sendServerPacket(Player receiver, PacketContainer packet)
|
||||
public void sendServerPacket(Player receiver, PacketContainer packet)
|
||||
throws InvocationTargetException;
|
||||
|
||||
/**
|
||||
@@ -61,6 +62,22 @@ public interface PacketStream {
|
||||
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.
|
||||
@@ -69,7 +86,7 @@ public interface PacketStream {
|
||||
* @throws InvocationTargetException If the reflection machinery failed.
|
||||
* @throws IllegalAccessException If the underlying method caused an error.
|
||||
*/
|
||||
public void recieveClientPacket(Player sender, PacketContainer packet)
|
||||
public void recieveClientPacket(Player sender, PacketContainer packet)
|
||||
throws IllegalAccessException, InvocationTargetException;
|
||||
|
||||
/**
|
||||
File diff suppressed because it is too large.
Load diff
+57
-56
@@ -2,20 +2,20 @@
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol.reflect;
|
||||
package com.comphenix.protocol;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
@@ -23,8 +23,10 @@ import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.google.common.collect.BiMap;
|
||||
import com.google.common.collect.HashBiMap;
|
||||
import com.google.common.collect.Sets;
|
||||
|
||||
/**
|
||||
* Represents a more modern object-based enum.
|
||||
@@ -33,39 +35,53 @@ import com.google.common.collect.HashBiMap;
|
||||
* want to prevent the creation of additional members dynamically.
|
||||
* @author Kristian
|
||||
*/
|
||||
public class ObjectEnum<T> implements Iterable<T> {
|
||||
public class PacketTypeEnum implements Iterable<PacketType> {
|
||||
// Used to convert between IDs and names
|
||||
protected BiMap<T, String> members = HashBiMap.create();
|
||||
|
||||
protected Set<PacketType> members = Sets.newHashSet();
|
||||
|
||||
/**
|
||||
* Registers every declared integer field.
|
||||
* Registers every declared PacketType field.
|
||||
*/
|
||||
public ObjectEnum(Class<T> fieldType) {
|
||||
registerAll(fieldType);
|
||||
public PacketTypeEnum() {
|
||||
registerAll();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Registers every public assignable static field as a member.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
protected void registerAll(Class<T> fieldType) {
|
||||
protected void registerAll() {
|
||||
try {
|
||||
// Register every int field
|
||||
// Register every non-deprecated field
|
||||
for (Field entry : this.getClass().getFields()) {
|
||||
if (Modifier.isStatic(entry.getModifiers()) && fieldType.isAssignableFrom(entry.getType())) {
|
||||
T value = (T) entry.get(null);
|
||||
|
||||
if (value == null)
|
||||
throw new IllegalArgumentException("Field " + entry + " was NULL. Remember to " +
|
||||
"construct the object after the field has been declared.");
|
||||
registerMember(value, entry.getName());
|
||||
if (Modifier.isStatic(entry.getModifiers()) && PacketType.class.isAssignableFrom(entry.getType())) {
|
||||
PacketType value = (PacketType) entry.get(null);
|
||||
if (value == null) {
|
||||
throw new IllegalArgumentException("Field " + entry.getName() + " was null!");
|
||||
}
|
||||
|
||||
value.setName(entry.getName());
|
||||
|
||||
if (entry.getAnnotation(PacketType.ForceAsync.class) != null) {
|
||||
value.forceAsync();
|
||||
}
|
||||
|
||||
boolean deprecated = entry.getAnnotation(Deprecated.class) != null;
|
||||
if (deprecated) value.setDeprecated();
|
||||
|
||||
if (members.contains(value)) {
|
||||
// Replace potentially deprecated packet types with non-deprecated ones
|
||||
if (!deprecated) {
|
||||
members.remove(value);
|
||||
members.add(value);
|
||||
}
|
||||
} else {
|
||||
members.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} catch (IllegalArgumentException e) {
|
||||
e.printStackTrace();
|
||||
} catch (IllegalAccessException e) {
|
||||
e.printStackTrace();
|
||||
} catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,11 +91,14 @@ public class ObjectEnum<T> implements Iterable<T> {
|
||||
* @param name - name of member.
|
||||
* @return TRUE if the member was registered, FALSE otherwise.
|
||||
*/
|
||||
public boolean registerMember(T instance, String name) {
|
||||
if (!members.containsKey(instance)) {
|
||||
members.put(instance, name);
|
||||
public boolean registerMember(PacketType instance, String name) {
|
||||
instance.setName(name);
|
||||
|
||||
if (!members.contains(instance)) {
|
||||
members.add(instance);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -88,38 +107,20 @@ public class ObjectEnum<T> implements Iterable<T> {
|
||||
* @param member - the member to check.
|
||||
* @return TRUE if the given member has been registered, FALSE otherwise.
|
||||
*/
|
||||
public boolean hasMember(T member) {
|
||||
return members.containsKey(member);
|
||||
public boolean hasMember(PacketType member) {
|
||||
return members.contains(member);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a member by name,
|
||||
* @param name - name of member to retrieve.
|
||||
* @return The member, or NULL if not found.
|
||||
*/
|
||||
public T valueOf(String name) {
|
||||
return members.inverse().get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the name of the given member.
|
||||
* @param member - the member to retrieve.
|
||||
* @return Declared name of the member, or NULL if not found.
|
||||
*/
|
||||
public String getDeclaredName(T member) {
|
||||
return members.get(member);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve every registered member.
|
||||
* @return Enumeration of every value.
|
||||
*/
|
||||
public Set<T> values() {
|
||||
return new HashSet<T>(members.keySet());
|
||||
public Set<PacketType> values() {
|
||||
return new HashSet<>(members);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<T> iterator() {
|
||||
return members.keySet().iterator();
|
||||
public Iterator<PacketType> iterator() {
|
||||
return members.iterator();
|
||||
}
|
||||
}
|
||||
+54
-16
@@ -2,6 +2,8 @@ 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;
|
||||
@@ -15,7 +17,7 @@ import com.google.common.collect.Multimap;
|
||||
* @author Kristian
|
||||
*/
|
||||
class PacketTypeLookup {
|
||||
private static class ProtocolSenderLookup {
|
||||
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();
|
||||
@@ -47,6 +49,39 @@ class PacketTypeLookup {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class ClassLookup {
|
||||
// Unroll lookup for performance reasons
|
||||
public final Map<String, PacketType> HANDSHAKE_CLIENT = new ConcurrentHashMap<String, PacketType>();
|
||||
public final Map<String, PacketType> HANDSHAKE_SERVER = new ConcurrentHashMap<String, PacketType>();
|
||||
public final Map<String, PacketType> GAME_CLIENT = new ConcurrentHashMap<String, PacketType>();
|
||||
public final Map<String, PacketType> GAME_SERVER = new ConcurrentHashMap<String, PacketType>();
|
||||
public final Map<String, PacketType> STATUS_CLIENT = new ConcurrentHashMap<String, PacketType>();
|
||||
public final Map<String, PacketType> STATUS_SERVER = new ConcurrentHashMap<String, PacketType>();
|
||||
public final Map<String, PacketType> LOGIN_CLIENT = new ConcurrentHashMap<String, PacketType>();
|
||||
public final Map<String, PacketType> LOGIN_SERVER = new ConcurrentHashMap<String, PacketType>();
|
||||
|
||||
/**
|
||||
* 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>();
|
||||
@@ -54,11 +89,14 @@ class PacketTypeLookup {
|
||||
private final IntegerMap<PacketType> clientLookup = new IntegerMap<PacketType>();
|
||||
|
||||
// Packets for 1.7.2
|
||||
private final ProtocolSenderLookup currentLookup = new ProtocolSenderLookup();
|
||||
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.
|
||||
@@ -67,21 +105,11 @@ class PacketTypeLookup {
|
||||
Preconditions.checkNotNull(types, "types cannot be NULL");
|
||||
|
||||
for (PacketType type : types) {
|
||||
int legacy = type.getLegacyId();
|
||||
|
||||
// Skip unknown legacy packets
|
||||
if (legacy != PacketType.UNKNOWN_PACKET) {
|
||||
if (type.isServer())
|
||||
serverLookup.put(type.getLegacyId(), type);
|
||||
if (type.isClient())
|
||||
clientLookup.put(type.getLegacyId(), type);
|
||||
legacyLookup.put(type.getLegacyId(), type);
|
||||
}
|
||||
// Skip unknown current packets
|
||||
if (type.getCurrentId() != PacketType.UNKNOWN_PACKET) {
|
||||
currentLookup.getMap(type.getProtocol(), type.getSender()).put(type.getCurrentId(), type);
|
||||
idLookup.getMap(type.getProtocol(), type.getSender()).put(type.getCurrentId(), type);
|
||||
classLookup.getMap(type.getProtocol(), type.getSender()).put(type.getClassNames().get(0), type);
|
||||
}
|
||||
// Save name
|
||||
nameLookup.put(type.name(), type);
|
||||
}
|
||||
return this;
|
||||
@@ -132,8 +160,18 @@ class PacketTypeLookup {
|
||||
* @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 currentLookup.getMap(protocol, sender).get(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;
|
||||
}
|
||||
}
|
||||
+7
-6
@@ -4,19 +4,20 @@ import java.util.Collection;
|
||||
import java.util.Deque;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
|
||||
import com.comphenix.protocol.PacketType.Protocol;
|
||||
import com.comphenix.protocol.PacketType.Sender;
|
||||
import com.comphenix.protocol.events.ConnectionSide;
|
||||
import com.google.common.collect.DiscreteDomains;
|
||||
import com.google.common.collect.ContiguousSet;
|
||||
import com.google.common.collect.DiscreteDomain;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Range;
|
||||
import com.google.common.collect.Ranges;
|
||||
import com.google.common.collect.Sets;
|
||||
|
||||
class PacketTypeParser {
|
||||
public final static Range<Integer> DEFAULT_MAX_RANGE = Ranges.closed(0, 255);
|
||||
public final static Range<Integer> DEFAULT_MAX_RANGE = Range.closed(0, 255);
|
||||
|
||||
private Sender side = null;
|
||||
private Protocol protocol = null;
|
||||
@@ -53,7 +54,7 @@ class PacketTypeParser {
|
||||
// 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();
|
||||
String name = it.next().toUpperCase(Locale.ENGLISH);
|
||||
Collection<PacketType> names = PacketType.fromName(name);
|
||||
|
||||
for (PacketType type : names) {
|
||||
@@ -72,7 +73,7 @@ class PacketTypeParser {
|
||||
}
|
||||
|
||||
for (Range<Integer> range : ranges) {
|
||||
for (Integer id : range.asSet(DiscreteDomains.integers())) {
|
||||
for (Integer id : ContiguousSet.create(range, DiscreteDomain.integers())) {
|
||||
// Deprecated packets
|
||||
if (protocol == null) {
|
||||
if (PacketType.hasLegacy(id)) {
|
||||
@@ -143,5 +144,5 @@ class PacketTypeParser {
|
||||
return Protocol.STATUS;
|
||||
else
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
+125
-79
@@ -1,4 +1,4 @@
|
||||
/*
|
||||
/**
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
@@ -14,18 +14,19 @@
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.bukkit.configuration.Configuration;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks;
|
||||
import com.comphenix.protocol.injector.PlayerInjectHooks;
|
||||
import com.google.common.base.Charsets;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Lists;
|
||||
@@ -38,53 +39,49 @@ import com.google.common.io.Files;
|
||||
*/
|
||||
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 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.getConfig());
|
||||
}
|
||||
|
||||
public ProtocolConfig(Plugin plugin, Configuration config) {
|
||||
this.plugin = plugin;
|
||||
reloadConfig();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reload configuration file.
|
||||
*/
|
||||
@@ -93,19 +90,20 @@ public class ProtocolConfig {
|
||||
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));
|
||||
@@ -118,37 +116,40 @@ public class ProtocolConfig {
|
||||
// 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) {
|
||||
@@ -157,25 +158,29 @@ public class ProtocolConfig {
|
||||
}
|
||||
if (global != null) {
|
||||
updater = global.getConfigurationSection(SECTION_AUTOUPDATER);
|
||||
if (updater.getValues(true).isEmpty()) {
|
||||
plugin.getLogger().warning("Updater section is missing, regenerate your config!");
|
||||
}
|
||||
}
|
||||
|
||||
// Automatically copy defaults
|
||||
if (copyDefaults && (!getFile().exists() || global == null || updater == null)) {
|
||||
loadingSections = true;
|
||||
|
||||
|
||||
if (config != null)
|
||||
config.options().copyDefaults(true);
|
||||
plugin.saveDefaultConfig();
|
||||
plugin.reloadConfig();
|
||||
loadingSections = false;
|
||||
|
||||
loadingSections = false;
|
||||
|
||||
// Inform the user
|
||||
System.out.println("[ProtocolLib] Created default configuration.");
|
||||
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.
|
||||
@@ -185,93 +190,122 @@ public class ProtocolConfig {
|
||||
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 global.getBoolean(DETAILED_ERROR, false);
|
||||
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 updater.getBoolean(UPDATER_NOTIFY, true);
|
||||
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 && updater.getBoolean(UPDATER_DOWNLAD, true);
|
||||
return updater != null && getUpdaterValue(UPDATER_DOWNLAD, false);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set whether or not ProtocolLib should automatically download the new version.
|
||||
*
|
||||
* @param value - TRUE if it should. FALSE otherwise.
|
||||
*/
|
||||
public void setAutoDownload(boolean value) {
|
||||
setConfig(updater, UPDATER_DOWNLAD, value);
|
||||
modCount++;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determine whether or not debug mode is enabled.
|
||||
* <p>
|
||||
* This grants access to the filter command.
|
||||
*
|
||||
* @return TRUE if it is, FALSE otherwise.
|
||||
*/
|
||||
public boolean isDebug() {
|
||||
return global.getBoolean(DEBUG_MODE_ENABLED, false);
|
||||
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(global.getStringList(SUPPRESSED_REPORTS));
|
||||
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) {
|
||||
@@ -281,17 +315,19 @@ public class ProtocolConfig {
|
||||
|
||||
/**
|
||||
* 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(updater.getInt(UPDATER_DELAY, 0), DEFAULT_UPDATER_DELAY);
|
||||
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) {
|
||||
@@ -301,20 +337,20 @@ public class ProtocolConfig {
|
||||
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 global.getString(IGNORE_VERSION_CHECK, "");
|
||||
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.
|
||||
* 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.
|
||||
*/
|
||||
@@ -322,15 +358,16 @@ public class ProtocolConfig {
|
||||
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 global.getBoolean(METRICS_ENABLED, true);
|
||||
return getGlobalValue(METRICS_ENABLED, true);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set whether or not metrics is enabled.
|
||||
* <p>
|
||||
@@ -342,28 +379,31 @@ public class ProtocolConfig {
|
||||
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 global.getBoolean(BACKGROUND_COMPILER_ENABLED, true);
|
||||
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() {
|
||||
@@ -373,75 +413,81 @@ public class ProtocolConfig {
|
||||
/**
|
||||
* Set the last time we updated, in seconds since 1970.01.01 00:00.
|
||||
* <p>
|
||||
* Note that this is not considered to modify the configuration, so the modification count
|
||||
* will not be incremented.
|
||||
* 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) {
|
||||
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 global.getString(SCRIPT_ENGINE_NAME, "JavaScript");
|
||||
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(" ", "_"));
|
||||
|
||||
if (text != null)
|
||||
hook = PlayerInjectHooks.valueOf(text.toUpperCase(Locale.ENGLISH).replace(" ", "_"));
|
||||
return hook;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the starting injection method to use.
|
||||
*
|
||||
* @return Injection method.
|
||||
*/
|
||||
public void setInjectionMethod(PlayerInjectHooks hook) {
|
||||
setConfig(global, INJECTION_METHOD, hook.name());
|
||||
modCount++;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve the number of modifications made to this configuration.
|
||||
*
|
||||
* @return The number of modifications.
|
||||
*/
|
||||
public int getModificationCount() {
|
||||
return modCount;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Save the current configuration file.
|
||||
*/
|
||||
@@ -450,7 +496,7 @@ public class ProtocolConfig {
|
||||
saveLastUpdate(lastUpdateTime);
|
||||
if (configChanged)
|
||||
plugin.saveConfig();
|
||||
|
||||
|
||||
// And we're done
|
||||
valuesChanged = false;
|
||||
configChanged = false;
|
||||
+143
-147
@@ -1,4 +1,4 @@
|
||||
/*
|
||||
/**
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
@@ -14,7 +14,6 @@
|
||||
* 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;
|
||||
@@ -27,40 +26,37 @@ 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.PluginManager;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import com.comphenix.executors.BukkitExecutors;
|
||||
import com.comphenix.protocol.async.AsyncFilterManager;
|
||||
import com.comphenix.protocol.error.BasicErrorReporter;
|
||||
import com.comphenix.protocol.error.DelegatedErrorReporter;
|
||||
import com.comphenix.protocol.error.DetailedErrorReporter;
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.error.Report;
|
||||
import com.comphenix.protocol.error.ReportType;
|
||||
import com.comphenix.protocol.error.*;
|
||||
import com.comphenix.protocol.injector.DelayedSingleTask;
|
||||
import com.comphenix.protocol.injector.InternalManager;
|
||||
import com.comphenix.protocol.injector.PacketFilterManager;
|
||||
import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks;
|
||||
import com.comphenix.protocol.injector.PlayerInjectHooks;
|
||||
import com.comphenix.protocol.metrics.Statistics;
|
||||
import com.comphenix.protocol.reflect.compiler.BackgroundCompiler;
|
||||
import com.comphenix.protocol.updater.Updater;
|
||||
import com.comphenix.protocol.updater.Updater.UpdateType;
|
||||
import com.comphenix.protocol.utility.ChatExtensions;
|
||||
import com.comphenix.protocol.utility.EnhancerFactory;
|
||||
import com.comphenix.protocol.utility.ByteBuddyFactory;
|
||||
import com.comphenix.protocol.utility.NettyVersion;
|
||||
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;
|
||||
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.PluginCommand;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.bukkit.plugin.PluginManager;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
/**
|
||||
* The main entry point for ProtocolLib.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public class ProtocolLibrary extends JavaPlugin {
|
||||
public class ProtocolLib extends JavaPlugin {
|
||||
// Every possible error or warning report type
|
||||
public static final ReportType REPORT_CANNOT_LOAD_CONFIG = new ReportType("Cannot load configuration");
|
||||
public static final ReportType REPORT_CANNOT_DELETE_CONFIG = new ReportType("Cannot delete old ProtocolLib configuration.");
|
||||
@@ -79,26 +75,16 @@ public class ProtocolLibrary extends JavaPlugin {
|
||||
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.0";
|
||||
|
||||
/**
|
||||
* The maximum version ProtocolLib has been tested with,
|
||||
*/
|
||||
public static final String MAXIMUM_MINECRAFT_VERSION = "1.7.10";
|
||||
|
||||
/**
|
||||
* The date (with ISO 8601 or YYYY-MM-DD) when the most recent version was released.
|
||||
*/
|
||||
public static final String MINECRAFT_LAST_RELEASE_DATE = "2013-12-10";
|
||||
// Update information
|
||||
static final String BUKKIT_DEV_SLUG = "protocollib";
|
||||
static final int BUKKIT_DEV_ID = 45564;
|
||||
|
||||
// Different commands
|
||||
private enum ProtocolCommand {
|
||||
FILTER,
|
||||
PACKET,
|
||||
PROTOCOL
|
||||
PROTOCOL,
|
||||
LOGGING;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -120,10 +106,6 @@ public class ProtocolLibrary extends JavaPlugin {
|
||||
// Metrics and statistics
|
||||
private Statistics statistics;
|
||||
|
||||
// Executors
|
||||
private static ListeningScheduledExecutorService executorAsync;
|
||||
private static ListeningScheduledExecutorService executorSync;
|
||||
|
||||
// Structure compiler
|
||||
private BackgroundCompiler backgroundCompiler;
|
||||
|
||||
@@ -139,35 +121,40 @@ public class ProtocolLibrary extends JavaPlugin {
|
||||
// Settings/options
|
||||
private int configExpectedMod = -1;
|
||||
|
||||
// Updater
|
||||
private Updater updater;
|
||||
public static boolean UPDATES_DISABLED;
|
||||
|
||||
// Logger
|
||||
private Logger logger;
|
||||
private static Logger logger;
|
||||
private Handler redirectHandler;
|
||||
|
||||
// Commands
|
||||
private CommandProtocol commandProtocol;
|
||||
private CommandPacket commandPacket;
|
||||
private CommandFilter commandFilter;
|
||||
private PacketLogging packetLogging;
|
||||
|
||||
// Whether or not disable is not needed
|
||||
private boolean skipDisable;
|
||||
|
||||
@Override
|
||||
public void onLoad() {
|
||||
// Load configuration
|
||||
logger = getLoggerSafely();
|
||||
Application.registerPrimaryThread();
|
||||
// Logging
|
||||
logger = getLogger();
|
||||
ProtocolLogger.init(this);
|
||||
|
||||
// Initialize enhancer factory
|
||||
EnhancerFactory.getInstance().setClassLoader(getClassLoader());
|
||||
|
||||
// Initialize executors
|
||||
executorAsync = BukkitExecutors.newAsynchronous(this);
|
||||
executorSync = BukkitExecutors.newSynchronous(this);
|
||||
ByteBuddyFactory.getInstance().setClassLoader(getClassLoader());
|
||||
|
||||
// Add global parameters
|
||||
DetailedErrorReporter detailedReporter = new DetailedErrorReporter(this);
|
||||
reporter = getFilteredReporter(detailedReporter);
|
||||
|
||||
// Configuration
|
||||
saveDefaultConfig();
|
||||
reloadConfig();
|
||||
|
||||
try {
|
||||
config = new ProtocolConfig(this);
|
||||
} catch (Exception e) {
|
||||
@@ -184,7 +171,11 @@ public class ProtocolLibrary extends JavaPlugin {
|
||||
// Print the state of the debug mode
|
||||
if (config.isDebug()) {
|
||||
logger.warning("Debug mode is enabled!");
|
||||
logger.info("Detected netty version: " + NettyVersion.getVersion());
|
||||
} else {
|
||||
NettyVersion.getVersion(); // this will cache the version
|
||||
}
|
||||
|
||||
// And the state of the error reporter
|
||||
if (config.isDetailedErrorReporting()) {
|
||||
detailedReporter.setDetailedReporting(true);
|
||||
@@ -198,8 +189,21 @@ public class ProtocolLibrary extends JavaPlugin {
|
||||
// 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();
|
||||
protocolManager = PacketFilterManager.newBuilder()
|
||||
.classLoader(getClassLoader())
|
||||
.server(getServer())
|
||||
.library(this)
|
||||
.minecraftVersion(version)
|
||||
.unhookTask(unhookTask)
|
||||
.reporter(reporter)
|
||||
.build();
|
||||
|
||||
// Initialize the API
|
||||
ProtocolLibrary.init(this, config, protocolManager, reporter);
|
||||
|
||||
// Setup error reporter
|
||||
detailedReporter.addGlobalParameter("manager", protocolManager);
|
||||
@@ -223,8 +227,6 @@ public class ProtocolLibrary extends JavaPlugin {
|
||||
|
||||
} catch (OutOfMemoryError e) {
|
||||
throw e;
|
||||
} catch (ThreadDeath e) {
|
||||
throw e;
|
||||
} catch (Throwable e) {
|
||||
reporter.reportDetailed(this, Report.newBuilder(REPORT_PLUGIN_LOAD_ERROR).error(e).callerParam(protocolManager));
|
||||
disablePlugin();
|
||||
@@ -240,7 +242,7 @@ public class ProtocolLibrary extends JavaPlugin {
|
||||
try {
|
||||
switch (command) {
|
||||
case PROTOCOL:
|
||||
commandProtocol = new CommandProtocol(reporter, this, config);
|
||||
commandProtocol = new CommandProtocol(reporter, this, updater, config);
|
||||
break;
|
||||
case FILTER:
|
||||
commandFilter = new CommandFilter(reporter, this, config);
|
||||
@@ -248,13 +250,17 @@ public class ProtocolLibrary extends JavaPlugin {
|
||||
case PACKET:
|
||||
commandPacket = new CommandPacket(reporter, this, logger, commandFilter, protocolManager);
|
||||
break;
|
||||
case LOGGING:
|
||||
packetLogging = new PacketLogging(this, protocolManager);
|
||||
break;
|
||||
}
|
||||
} catch (OutOfMemoryError e) {
|
||||
throw e;
|
||||
} catch (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));
|
||||
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_REGISTER_COMMAND)
|
||||
.messageParam(command.name(), e.getMessage()).error(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -346,19 +352,26 @@ public class ProtocolLibrary extends JavaPlugin {
|
||||
// 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. ", };
|
||||
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);
|
||||
@@ -373,6 +386,7 @@ public class ProtocolLibrary extends JavaPlugin {
|
||||
registerCommand(CommandProtocol.NAME, commandProtocol);
|
||||
registerCommand(CommandPacket.NAME, commandPacket);
|
||||
registerCommand(CommandFilter.NAME, commandFilter);
|
||||
registerCommand(PacketLogging.NAME, packetLogging);
|
||||
|
||||
// Player login and logout events
|
||||
protocolManager.registerEvents(manager, this);
|
||||
@@ -380,11 +394,8 @@ public class ProtocolLibrary extends JavaPlugin {
|
||||
// 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();
|
||||
@@ -398,8 +409,6 @@ public class ProtocolLibrary extends JavaPlugin {
|
||||
}
|
||||
} catch (OutOfMemoryError e) {
|
||||
throw e;
|
||||
} catch (ThreadDeath e) {
|
||||
throw e;
|
||||
} catch (IOException e) {
|
||||
reporter.reportDetailed(this, Report.newBuilder(REPORT_METRICS_IO_ERROR).error(e).callerParam(statistics));
|
||||
} catch (Throwable e) {
|
||||
@@ -407,10 +416,28 @@ public class ProtocolLibrary extends JavaPlugin {
|
||||
}
|
||||
}
|
||||
|
||||
// Plugin authors: Notify me to remove these
|
||||
|
||||
private void checkForIncompatibility(PluginManager manager) {
|
||||
for (String plugin : ProtocolLibrary.INCOMPATIBLE) {
|
||||
if (manager.getPlugin(plugin) != null) {
|
||||
// Special case for TagAPI and iTag
|
||||
if (plugin.equals("TagAPI")) {
|
||||
Plugin iTag = manager.getPlugin("iTag");
|
||||
if (iTag == null || iTag.getDescription().getVersion().startsWith("1.0")) {
|
||||
logger.severe("Detected incompatible plugin: TagAPI");
|
||||
}
|
||||
} else {
|
||||
logger.severe("Detected incompatible plugin: " + plugin);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Used to check Minecraft version
|
||||
private MinecraftVersion verifyMinecraftVersion() {
|
||||
MinecraftVersion minimum = new MinecraftVersion(MINIMUM_MINECRAFT_VERSION);
|
||||
MinecraftVersion maximum = new MinecraftVersion(MAXIMUM_MINECRAFT_VERSION);
|
||||
MinecraftVersion minimum = new MinecraftVersion(ProtocolLibrary.MINIMUM_MINECRAFT_VERSION);
|
||||
MinecraftVersion maximum = new MinecraftVersion(ProtocolLibrary.MAXIMUM_MINECRAFT_VERSION);
|
||||
|
||||
try {
|
||||
MinecraftVersion current = new MinecraftVersion(getServer());
|
||||
@@ -423,8 +450,8 @@ public class ProtocolLibrary extends JavaPlugin {
|
||||
if (current.compareTo(maximum) > 0)
|
||||
logger.warning("Version " + current + " has not yet been tested! Proceed with caution.");
|
||||
}
|
||||
return current;
|
||||
|
||||
return current;
|
||||
} catch (Exception e) {
|
||||
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_PARSE_MINECRAFT_VERSION).error(e).messageParam(maximum));
|
||||
|
||||
@@ -435,7 +462,7 @@ public class ProtocolLibrary extends JavaPlugin {
|
||||
|
||||
private void checkConflictingVersions() {
|
||||
Pattern ourPlugin = Pattern.compile("ProtocolLib-(.*)\\.jar");
|
||||
MinecraftVersion currentVersion = new MinecraftVersion(this.getDescription().getVersion());
|
||||
MinecraftVersion currentVersion = new MinecraftVersion(getDescription().getVersion());
|
||||
MinecraftVersion newestVersion = null;
|
||||
|
||||
// Skip the file that contains this current instance however
|
||||
@@ -443,26 +470,29 @@ public class ProtocolLibrary extends JavaPlugin {
|
||||
|
||||
try {
|
||||
// Scan the plugin folder for newer versions of ProtocolLib
|
||||
File pluginFolder = new File("plugins/");
|
||||
// The plugin folder isn't always plugins/
|
||||
File pluginFolder = getDataFolder().getParentFile();
|
||||
|
||||
for (File candidate : pluginFolder.listFiles()) {
|
||||
if (candidate.isFile() && !candidate.equals(loadedFile)) {
|
||||
Matcher match = ourPlugin.matcher(candidate.getName());
|
||||
File[] candidates = pluginFolder.listFiles();
|
||||
if (candidates != null) {
|
||||
for (File candidate : candidates) {
|
||||
if (candidate.isFile() && !candidate.equals(loadedFile)) {
|
||||
Matcher match = ourPlugin.matcher(candidate.getName());
|
||||
if (match.matches()) {
|
||||
MinecraftVersion version = new MinecraftVersion(match.group(1));
|
||||
|
||||
if (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;
|
||||
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));
|
||||
}
|
||||
|
||||
@@ -471,7 +501,9 @@ public class ProtocolLibrary extends JavaPlugin {
|
||||
// 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()));
|
||||
throw new IllegalStateException(String.format(
|
||||
"Detected a newer version of ProtocolLib (%s) in plugin folder than the current (%s). Disabling.",
|
||||
newestVersion.getVersion(), currentVersion.getVersion()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -484,11 +516,11 @@ public class ProtocolLibrary extends JavaPlugin {
|
||||
PluginCommand command = getCommand(name);
|
||||
|
||||
// Try to load the command
|
||||
if (command != null)
|
||||
if (command != null) {
|
||||
command.setExecutor(executor);
|
||||
else
|
||||
} 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));
|
||||
}
|
||||
@@ -517,12 +549,15 @@ public class ProtocolLibrary extends JavaPlugin {
|
||||
|
||||
// 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));
|
||||
@@ -539,6 +574,29 @@ public class ProtocolLibrary extends JavaPlugin {
|
||||
}
|
||||
}
|
||||
|
||||
private void checkUpdates() {
|
||||
// Ignore milliseconds - it's pointless
|
||||
long currentTime = System.currentTimeMillis() / MILLI_PER_SECOND;
|
||||
|
||||
try {
|
||||
long updateTime = config.getAutoLastTime() + config.getAutoDelay();
|
||||
|
||||
// Should we update?
|
||||
if (currentTime > updateTime && !updater.isChecking()) {
|
||||
// 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) {
|
||||
@@ -576,52 +634,6 @@ public class ProtocolLibrary extends JavaPlugin {
|
||||
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>
|
||||
@@ -633,23 +645,7 @@ public class ProtocolLibrary extends JavaPlugin {
|
||||
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;
|
||||
public ProtocolConfig getProtocolConfig() {
|
||||
return config;
|
||||
}
|
||||
}
|
||||
Loaded 100 of 389 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user