Compare commits
1370
Commits
1.5.0
..
update/1193
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d9c623a254 | ||
|
|
fe0abbf9b3 | ||
|
|
92b604506f | ||
|
|
574c34ad5c | ||
|
|
115cc83040 | ||
|
|
a18c418f42 | ||
|
|
6aaf0ec26b | ||
|
|
86e586da26 | ||
|
|
069783a353 | ||
|
|
cc95e19ba4 | ||
|
|
5ef24a7471 | ||
|
|
475f89ab89 | ||
|
|
e3d4339659 | ||
|
|
e7d54cdabd | ||
|
|
c31133c20f | ||
|
|
69ae3656b5 | ||
|
|
43145bd478 | ||
|
|
b7c1e096c4 | ||
|
|
6707c4811e | ||
|
|
2092b8f48e | ||
|
|
20e73369fa | ||
|
|
abc0db8281 | ||
|
|
1beb95115f | ||
|
|
7fcfcdc365 | ||
|
|
2be216899a | ||
|
|
8876ce323b | ||
|
|
575174580e | ||
|
|
7fd4ec3172 | ||
|
|
a75d383001 | ||
|
|
7ddfd4f347 | ||
|
|
7e137cbfc5 | ||
|
|
d40762e69d | ||
|
|
ae19478007 | ||
|
|
11a8184c3e | ||
|
|
c3dc00de05 | ||
|
|
84cb541866 | ||
|
|
624f6aaca6 | ||
|
|
c5f0550953 | ||
|
|
7f0bc7fd24 | ||
|
|
96155b1065 | ||
|
|
23dac3287b | ||
|
|
0bbbd961aa | ||
|
|
dd19e1040a | ||
|
|
e45c16d490 | ||
|
|
a19959cea4 | ||
|
|
4f18d37832 | ||
|
|
a2bf242097 | ||
|
|
4e105c59ed | ||
|
|
59ca841ed5 | ||
|
|
5e8f044a18 | ||
|
|
4db1e39ac7 | ||
|
|
e202503c09 | ||
|
|
aed98abac6 | ||
|
|
250f94e9cd | ||
|
|
d7bf43001f | ||
|
|
868b357527 | ||
|
|
9a609c2053 | ||
|
|
f3acce99d8 | ||
|
|
374e6cd5ee | ||
|
|
84a0b5ffdd | ||
|
|
4cc3957723 | ||
|
|
a0a5469988 | ||
|
|
240920d642 | ||
|
|
764195bd55 | ||
|
|
7bfee67a29 | ||
|
|
c87604cf0c | ||
|
|
4096952c16 | ||
|
|
b4eff32213 | ||
|
|
d361526371 | ||
|
|
0d4e4c818f | ||
|
|
073bfa2b86 | ||
|
|
f0059f39f6 | ||
|
|
41bb4bacb2 | ||
|
|
9487c42985 | ||
|
|
55f7b67f9d | ||
|
|
e44d1e6051 | ||
|
|
baecaf4ca4 | ||
|
|
151d4a289f | ||
|
|
1b4d79b302 | ||
|
|
74833f8680 | ||
|
|
e77f8ced4c | ||
|
|
1cd83e493d | ||
|
|
40b6c66491 | ||
|
|
723fc2c7ec | ||
|
|
dd687ce175 | ||
|
|
8774d87d59 | ||
|
|
8361cf078f | ||
|
|
dbedab0c14 | ||
|
|
5dda8c8ab1 | ||
|
|
4c0c18d7c6 | ||
|
|
9c20455bf6 | ||
|
|
8175443da0 | ||
|
|
95a884974a | ||
|
|
153dd61994 | ||
|
|
263ec8a36e | ||
|
|
af46ba4d1a | ||
|
|
dd85904642 | ||
|
|
7a8fce224e | ||
|
|
9ca7c91a76 | ||
|
|
466354cd2c | ||
|
|
99504dab8f | ||
|
|
9b6603e2eb | ||
|
|
4fc476a125 | ||
|
|
583ed4b58a | ||
|
|
638e81b9ce | ||
|
|
9a0703d05d | ||
|
|
9de096f783 | ||
|
|
c54a99945d | ||
|
|
90a38cc15c | ||
|
|
76930ae6e8 | ||
|
|
d745cfb184 | ||
|
|
4be2bf38ff | ||
|
|
0a32f24f08 | ||
|
|
1c2bc274dd | ||
|
|
9b54794f6b | ||
|
|
42bec5a858 | ||
|
|
fa317c1167 | ||
|
|
cc17b9ee6e | ||
|
|
190ca1ff6a | ||
|
|
c51930121f | ||
|
|
c7a8d734d4 | ||
|
|
b446cf2183 | ||
|
|
f11c246276 | ||
|
|
6f91bd23de | ||
|
|
45c293df7d | ||
|
|
5acdb2b3c5 | ||
|
|
72c1f3e26c | ||
|
|
b3ccf82597 | ||
|
|
0c01a11755 | ||
|
|
7ce3f471bf | ||
|
|
97972acee8 | ||
|
|
a0bb11e1bd | ||
|
|
ba74fceed6 | ||
|
|
ab0faab396 | ||
|
|
765fd9e987 | ||
|
|
8f7b530613 | ||
|
|
30fe81d366 | ||
|
|
b54dd49426 | ||
|
|
26274fed52 | ||
|
|
aa555f792e | ||
|
|
0d50905754 | ||
|
|
b7132196fb | ||
|
|
eb54b99856 | ||
|
|
13b3d8679d | ||
|
|
1545a88e06 | ||
|
|
2b22999b94 | ||
|
|
bbb053aa4e | ||
|
|
7bac4ec634 | ||
|
|
4bc9e8b7b7 | ||
|
|
f381f0a2f7 | ||
|
|
553e4b6813 | ||
|
|
bdaa843f2d | ||
|
|
13f5c14599 | ||
|
|
b871eb3d54 | ||
|
|
12e3a895b3 | ||
|
|
8c5fbe3298 | ||
|
|
0512215007 | ||
|
|
80f4c7b9a7 | ||
|
|
8c51b175c4 | ||
|
|
8d991ad5a7 | ||
|
|
f19bfc613e | ||
|
|
a7e702899a | ||
|
|
deb192b04d | ||
|
|
fbe46f7bac | ||
|
|
fd93c1c553 | ||
|
|
b2f6a56843 | ||
|
|
658da31d46 | ||
|
|
77feaa857e | ||
|
|
5f204d798c | ||
|
|
5183bd53b5 | ||
|
|
fdd30a7b87 | ||
|
|
7ac4ac696f | ||
|
|
c203fda391 | ||
|
|
9a65ddbc43 | ||
|
|
54c252a354 | ||
|
|
e92abda187 | ||
|
|
3f7b7f4bb3 | ||
|
|
944b3f8280 | ||
|
|
b04fca8324 | ||
|
|
bfa0eee91e | ||
|
|
308e3d3417 | ||
|
|
42e48aa9b8 | ||
|
|
18d0193288 | ||
|
|
0ab552a6ef | ||
|
|
d9fef94bc6 | ||
|
|
ab5fb40f8f | ||
|
|
4baa4aa724 | ||
|
|
7a4a285935 | ||
|
|
f38c393d6f | ||
|
|
22c2a4abcc | ||
|
|
1a434e9ea2 | ||
|
|
25a5f1d07f | ||
|
|
e2f949c3a7 | ||
|
|
c3c59337ff | ||
|
|
fdf315ba7d | ||
|
|
f46bd56e8f | ||
|
|
b1efed0c0c | ||
|
|
524fb1ba68 | ||
|
|
3ff2ccf1b3 | ||
|
|
e915fd0f9a | ||
|
|
6f8b2377b1 | ||
|
|
c893a3f11e | ||
|
|
73c71e0198 | ||
|
|
bb305fdaad | ||
|
|
b4d4eb29af | ||
|
|
6d9fe45fb4 | ||
|
|
b292c8485b | ||
|
|
1e4da2c93b | ||
|
|
84e31d032b | ||
|
|
59c8c8c9db | ||
|
|
a76ceb94cc | ||
|
|
9a108af219 | ||
|
|
2f09dec1c5 | ||
|
|
4c43f125f9 | ||
|
|
b5cff49358 | ||
|
|
446a1c8326 | ||
|
|
8c20d19339 | ||
|
|
9dfffe5366 | ||
|
|
19dd81ce94 | ||
|
|
dd9eac3d6d | ||
|
|
916434251d | ||
|
|
62e8d82e16 | ||
|
|
d7be712959 | ||
|
|
490b1dadcb | ||
|
|
502c5960e3 | ||
|
|
d297e373b4 | ||
|
|
9f5d95f617 | ||
|
|
b14b4cc345 | ||
|
|
e41bb8753b | ||
|
|
565f169e94 | ||
|
|
7b7449ee15 | ||
|
|
95bb026fa9 | ||
|
|
4713d320f8 | ||
|
|
083c8e1481 | ||
|
|
6ff6976e85 | ||
|
|
7e87c90b8d | ||
|
|
6fdf0f8255 | ||
|
|
7e12f57eda | ||
|
|
776ec56a2d | ||
|
|
8ec31d83db | ||
|
|
1373911ca2 | ||
|
|
cf5d9dfc7d | ||
|
|
9df0dd48e4 | ||
|
|
d112e9b1dc | ||
|
|
206dc30e17 | ||
|
|
4c3a0afd7e | ||
|
|
8d9bf7d91f | ||
|
|
3ef537242f | ||
|
|
976e4b4217 | ||
|
|
e827a3dd1f | ||
|
|
778031c105 | ||
|
|
6c3654ef40 | ||
|
|
5048f6f34b | ||
|
|
c9dd6e2ba7 | ||
|
|
fc76af9d56 | ||
|
|
aed2285bcb | ||
|
|
1d11908af1 | ||
|
|
3ccc56fef9 | ||
|
|
39e0c974ee | ||
|
|
e52c69ca22 | ||
|
|
d7d53bd905 | ||
|
|
fb2d5016e5 | ||
|
|
015099b92e | ||
|
|
7e8356f0bb | ||
|
|
72b8c4ced8 | ||
|
|
2d22df404a | ||
|
|
0f13a32925 | ||
|
|
1ddb7a0014 | ||
|
|
f9376432c4 | ||
|
|
d195fb6990 | ||
|
|
e4be102a77 | ||
|
|
5f14ccdc34 | ||
|
|
7573459a48 | ||
|
|
01b68cf8e5 | ||
|
|
f3b1a07189 | ||
|
|
c70eb27653 | ||
|
|
4483ce0d79 | ||
|
|
1b23e9ec22 | ||
|
|
1f5692a0c7 | ||
|
|
0ac68dc179 | ||
|
|
d891a057c0 | ||
|
|
9e5bdf4124 | ||
|
|
e1ea295600 | ||
|
|
59171c5443 | ||
|
|
501244cb3c | ||
|
|
390c21f6d5 | ||
|
|
20d78832b0 | ||
|
|
adb3c5392c | ||
|
|
cd2f56d7c7 | ||
|
|
7c9bbc3f6c | ||
|
|
52e396b25d | ||
|
|
aeb2cfb65e | ||
|
|
7f5ea6e493 | ||
|
|
1be94aad78 | ||
|
|
6bb169ac01 | ||
|
|
ea7900d64f | ||
|
|
123abdff30 | ||
|
|
11247f1c01 | ||
|
|
f093c9196d | ||
|
|
81daa62071 | ||
|
|
f99b977888 | ||
|
|
b00ae2ff15 | ||
|
|
b4f9c501b3 | ||
|
|
1738c6f243 | ||
|
|
abea7441a4 | ||
|
|
3887ba27d2 | ||
|
|
76a3af84a1 | ||
|
|
be5d42ee19 | ||
|
|
790711c16e | ||
|
|
8c3127dee1 | ||
|
|
5ec87c9d76 | ||
|
|
2205ce639e | ||
|
|
2844d75607 | ||
|
|
a6810ea874 | ||
|
|
aaf1af8e41 | ||
|
|
b8dd43aeed | ||
|
|
e959efc5af | ||
|
|
73ce01bbde | ||
|
|
b30628f6db | ||
|
|
9289825d87 | ||
|
|
57cfbc4f81 | ||
|
|
3ab2026d62 | ||
|
|
e439c778a6 | ||
|
|
98413c917e | ||
|
|
540a5e53c4 | ||
|
|
27047f83a5 | ||
|
|
222b535c15 | ||
|
|
3f31af6b70 | ||
|
|
b6a054b766 | ||
|
|
3b7b692e80 | ||
|
|
c6788addeb | ||
|
|
5b38bc7f38 | ||
|
|
33bb12713d | ||
|
|
d175a7655e | ||
|
|
4afb38aae1 | ||
|
|
36b5670b3d | ||
|
|
7b61796506 | ||
|
|
684b687e42 | ||
|
|
7a9b9afe2e | ||
|
|
7d382279ad | ||
|
|
b4f5f18283 | ||
|
|
17e150393a | ||
|
|
b5ecfb9c5f | ||
|
|
31a0c9b10c | ||
|
|
e4d95e09cf | ||
|
|
d173be0db5 | ||
|
|
051989a396 | ||
|
|
795c77e943 | ||
|
|
1de6bbc9dc | ||
|
|
474c74b1f9 | ||
|
|
caaa57c915 | ||
|
|
588f736348 | ||
|
|
828302150a | ||
|
|
144723af18 | ||
|
|
087913ab3a | ||
|
|
64942cbb5e | ||
|
|
97430a8e42 | ||
|
|
1bdd200794 | ||
|
|
679035c3bf | ||
|
|
bd4352f041 | ||
|
|
05ffeb8e7f | ||
|
|
6c982a83f0 | ||
|
|
9860fbaec6 | ||
|
|
26c29d9c85 | ||
|
|
242b25eb1e | ||
|
|
c13b8d4fc6 | ||
|
|
12e9c08a1a | ||
|
|
98a0528806 | ||
|
|
015a6067fd | ||
|
|
e34105e754 | ||
|
|
a995866d5b | ||
|
|
a88347dfe6 | ||
|
|
4f404a41c6 | ||
|
|
88444dcf74 | ||
|
|
4f5ab9eb8c | ||
|
|
f51427b63c | ||
|
|
cdcb6a6007 | ||
|
|
ac7e17d0a6 | ||
|
|
402390866a | ||
|
|
cc362a1b7f | ||
|
|
1ca7973b77 | ||
|
|
9ecbea68ca | ||
|
|
e5bc602af3 | ||
|
|
a9aa406d29 | ||
|
|
c1ae6f14fc | ||
|
|
411b7a2446 | ||
|
|
869b457810 | ||
|
|
4330bae47f | ||
|
|
1c36c41050 | ||
|
|
bea4356aca | ||
|
|
32f51c612a | ||
|
|
355d00d951 | ||
|
|
bf56f3919f | ||
|
|
d876580ddf | ||
|
|
90f36198d2 | ||
|
|
e542f3c9d3 | ||
|
|
87c56d0248 | ||
|
|
8c928cb237 | ||
|
|
6030992af4 | ||
|
|
688da1a1bb | ||
|
|
ceecd51982 | ||
|
|
c77a700442 | ||
|
|
10ded26d60 | ||
|
|
74adaba8ac | ||
|
|
19c57b0e5e | ||
|
|
f561057311 | ||
|
|
0e6a7a39a0 | ||
|
|
13b905e762 | ||
|
|
b4c0cc5a27 | ||
|
|
a042c8bb3c | ||
|
|
286a642739 | ||
|
|
513a83c4b0 | ||
|
|
9b2c8826b1 | ||
|
|
413a3c27a4 | ||
|
|
c9328d98d7 | ||
|
|
f6c0751ce3 | ||
|
|
16c7ebef73 | ||
|
|
ec38ac7292 | ||
|
|
b8497a91a6 | ||
|
|
5115bcaf98 | ||
|
|
7077037bb7 | ||
|
|
11ba322b5f | ||
|
|
a39a37ec51 | ||
|
|
ec8fa0d1fe | ||
|
|
5efc4ffee3 | ||
|
|
c2d028df4f | ||
|
|
9b838df17d | ||
|
|
a20ff4645d | ||
|
|
850e3431da | ||
|
|
072a60516e | ||
|
|
9b3feb995a | ||
|
|
d359f7455f | ||
|
|
4987cd188a | ||
|
|
019e45572d | ||
|
|
d87e877a27 | ||
|
|
05c7da17b9 | ||
|
|
8c21f75e6d | ||
|
|
615a4e6e37 | ||
|
|
11ca1bf51e | ||
|
|
7fabfad65d | ||
|
|
ce4779f49d | ||
|
|
a88b919e60 | ||
|
|
cc4d035b63 | ||
|
|
12b78eb1d7 | ||
|
|
8a5267ab76 | ||
|
|
a189947617 | ||
|
|
f01b3999ff | ||
|
|
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 | ||
|
|
d7e46215b8 | ||
|
|
7e168e2655 | ||
|
|
e69832a82a | ||
|
|
e1e0c4a05a | ||
|
|
7a3b9c9e2b | ||
|
|
6044822014 | ||
|
|
d76788b092 | ||
|
|
03260969ff | ||
|
|
5804f0520f | ||
|
|
ca2bc3ecc5 | ||
|
|
592874fd5e | ||
|
|
bd74d0df1d | ||
|
|
4043f67610 | ||
|
|
eebcdf3ea1 | ||
|
|
726b7eb8ab | ||
|
|
c61de2766d | ||
|
|
eb27b03721 | ||
|
|
ffa85b80ee | ||
|
|
ac05781111 | ||
|
|
cfb5944990 | ||
|
|
d8181b7bf3 | ||
|
|
25cb648cfa | ||
|
|
78b6a651dc | ||
|
|
f0fd904396 | ||
|
|
b901f029d2 | ||
|
|
b68579bcb7 | ||
|
|
c443fc3da6 | ||
|
|
b3cda21fe5 | ||
|
|
4bd9a1f01e | ||
|
|
ba88d292c0 | ||
|
|
f378b7bbd9 | ||
|
|
d717ff1586 | ||
|
|
4ccd8853c4 | ||
|
|
09e45977f2 | ||
|
|
7e5abecfe2 | ||
|
|
aaecb08892 | ||
|
|
0375ad5391 | ||
|
|
0c940711ac | ||
|
|
c50bc55f77 | ||
|
|
ce216d0371 | ||
|
|
5b21f4d61c | ||
|
|
1141c0ba71 | ||
|
|
8067799306 | ||
|
|
1ee68d4e09 | ||
|
|
82be6bfecc | ||
|
|
0f5be7f1f1 | ||
|
|
b272322105 | ||
|
|
bbaea894d4 | ||
|
|
d3e37df343 | ||
|
|
b2425dcc6b | ||
|
|
95087a5b9f | ||
|
|
e0449b2db6 | ||
|
|
324e6de284 | ||
|
|
ccecdf216f | ||
|
|
818ac5cbde | ||
|
|
d415bd7643 | ||
|
|
180caed6b9 | ||
|
|
a6dfa60d79 | ||
|
|
8a2e696363 | ||
|
|
0c9f5e8992 | ||
|
|
71ce362c8e | ||
|
|
f9cd2b3db4 | ||
|
|
2244f986bb | ||
|
|
d8e8a88076 | ||
|
|
1ef602416d | ||
|
|
f7c4fd4ec9 | ||
|
|
5185442f35 | ||
|
|
ce8d115b5a | ||
|
|
58e66b80da | ||
|
|
17dda7f032 | ||
|
|
19ea5dfc33 | ||
|
|
3bd1361c9a | ||
|
|
486ac2653d | ||
|
|
c1890cb256 | ||
|
|
a65eac6471 | ||
|
|
0a4b1b3201 | ||
|
|
2476ab67a6 | ||
|
|
74947c02af | ||
|
|
e0b0e51342 | ||
|
|
c84a5d7fa2 | ||
|
|
4dea26b847 | ||
|
|
be238f6ac6 | ||
|
|
8cb9e050a5 | ||
|
|
a800a505f5 | ||
|
|
24de835440 | ||
|
|
4f871c64d7 | ||
|
|
839c186609 | ||
|
|
300d3c2475 | ||
|
|
485c3856c1 | ||
|
|
3b26940385 | ||
|
|
3257d6069a | ||
|
|
45e236b54a | ||
|
|
7adb6548aa | ||
|
|
bd3a60b7eb | ||
|
|
d76d4247b0 | ||
|
|
5e0000d139 | ||
|
|
ad060b10af | ||
|
|
75a5efcaa1 | ||
|
|
f4f8817fd5 | ||
|
|
38c62c4cfd | ||
|
|
4259a86740 | ||
|
|
b84aace585 | ||
|
|
e8c77af92b | ||
|
|
659f01cc63 | ||
|
|
6e0a44f9df | ||
|
|
5f66dc6b4a | ||
|
|
eb7760236f | ||
|
|
eb421dd31e | ||
|
|
7503470cb1 | ||
|
|
6443de6257 | ||
|
|
a8378d4e6f | ||
|
|
4e650ec7bf | ||
|
|
f71ed90b04 | ||
|
|
345cc74b3c | ||
|
|
9ef9475b61 | ||
|
|
ea7b550bda | ||
|
|
8bd177daf5 | ||
|
|
12286ebaa7 | ||
|
|
b59f55e2e5 | ||
|
|
752807fa5f | ||
|
|
96cc518fb0 | ||
|
|
6e3583b300 | ||
|
|
cfde9ecce8 | ||
|
|
ef4bd2ddcd | ||
|
|
bf8c16e22d | ||
|
|
fb07a91fb0 | ||
|
|
5d7895d741 | ||
|
|
385bae8bda | ||
|
|
7ae0399721 | ||
|
|
5cffccd8ca | ||
|
|
c87c0a9299 | ||
|
|
d70e9d28d4 | ||
|
|
90548f1092 | ||
|
|
eea0d131bb | ||
|
|
0c718f9484 | ||
|
|
e23129540c | ||
|
|
4f95b515ae | ||
|
|
a902c1f186 | ||
|
|
75fe2c6db9 | ||
|
|
7ed24e72f6 | ||
|
|
2a729cfe2d | ||
|
|
3e7fe52681 | ||
|
|
31b26fd0c5 | ||
|
|
7e9860f4c0 | ||
|
|
a7cac0be7f | ||
|
|
c06aeb2836 | ||
|
|
5c92a46cfa | ||
|
|
a0280a76d4 | ||
|
|
14c69e294a | ||
|
|
a9f1ee48a9 | ||
|
|
3c5482f79b | ||
|
|
e0fed34fe4 | ||
|
|
58c027f162 | ||
|
|
32758061a9 | ||
|
|
ce064643ff | ||
|
|
e56c0fec00 | ||
|
|
16dd2d5d1b | ||
|
|
db8d08f602 | ||
|
|
d71bea9f8a | ||
|
|
5267e73311 | ||
|
|
d62955dc71 | ||
|
|
af58dd8d03 | ||
|
|
acea92ef5c | ||
|
|
6bd8bd2ca2 | ||
|
|
5805150d8c | ||
|
|
9d972b90ac | ||
|
|
936e0f0e82 | ||
|
|
aedd97dfef | ||
|
|
96f24167bb | ||
|
|
88a2385b1e | ||
|
|
f41efdec60 | ||
|
|
a6f4aaa09a | ||
|
|
b52ea72903 | ||
|
|
85d415de7c | ||
|
|
9b349299a0 | ||
|
|
c568481da3 | ||
|
|
74d83b3ed6 | ||
|
|
e7273385cf | ||
|
|
517eeb9f3f | ||
|
|
bec5706e33 | ||
|
|
d166baf721 | ||
|
|
922fb94804 | ||
|
|
ee53fc0d5f | ||
|
|
17a5b06577 | ||
|
|
745a0846ea | ||
|
|
726374e7f5 | ||
|
|
9a07979733 | ||
|
|
c23e5c98f8 | ||
|
|
ab0ef6d37c | ||
|
|
9b0d4294cb | ||
|
|
c3ae43c75f | ||
|
|
bdc739317b | ||
|
|
e8759d0b72 | ||
|
|
143ed2ff02 | ||
|
|
b70c7fa775 | ||
|
|
154d73ae51 | ||
|
|
1aaf272878 | ||
|
|
20524c1c3c | ||
|
|
feae8dd400 | ||
|
|
be95fbc430 | ||
|
|
b947ed1193 | ||
|
|
a74d2ca8bc | ||
|
|
fa55e2cb27 | ||
|
|
029b19d94c | ||
|
|
4d11cfa8e8 | ||
|
|
54ef43fae8 | ||
|
|
b6625e6e39 | ||
|
|
8be221ff2e | ||
|
|
e44f02e1fa | ||
|
|
d83655f2d3 | ||
|
|
aa3600a337 | ||
|
|
23ebcb47ab | ||
|
|
ba064f649f | ||
|
|
2a0e782725 | ||
|
|
e064c1feb4 | ||
|
|
99eda4beca | ||
|
|
d8df5a7667 | ||
|
|
6159507bb4 | ||
|
|
253a0cf2a6 | ||
|
|
92c8f21d5e | ||
|
|
3509702204 | ||
|
|
be2d6b205b | ||
|
|
8eeb8645e7 | ||
|
|
745c4054a3 | ||
|
|
7f4f2bb724 | ||
|
|
84ce20f202 | ||
|
|
8e9c9951a9 | ||
|
|
d746fcbf6c | ||
|
|
e39310fbcc | ||
|
|
6c3a3e0ba2 | ||
|
|
29d71e05e4 | ||
|
|
7d8d8ead08 | ||
|
|
d42b4de6e9 | ||
|
|
a9dbca0bfb | ||
|
|
be6d4fb720 | ||
|
|
348019c1e1 | ||
|
|
9a658065e1 | ||
|
|
79c1e81c02 | ||
|
|
8f408c2109 | ||
|
|
f5e519eada | ||
|
|
7afc930592 | ||
|
|
9052de5316 | ||
|
|
6842166b94 | ||
|
|
7b813fa4e6 | ||
|
|
f94b060591 | ||
|
|
7055cadaef | ||
|
|
398b1bc3be | ||
|
|
65e665aa59 | ||
|
|
c137640fee | ||
|
|
28b4874c60 | ||
|
|
c9fd6b4b93 | ||
|
|
125bfbad30 | ||
|
|
00b297475c | ||
|
|
86f04f53b5 | ||
|
|
ba6c6e5abf | ||
|
|
3d6cfbf1e1 | ||
|
|
4be582ef87 | ||
|
|
8c8ca3746b | ||
|
|
b6809f6ce6 | ||
|
|
c416877f35 | ||
|
|
47134b43cc | ||
|
|
68c0a3c1dc | ||
|
|
f3b2b0bf3b | ||
|
|
3ea5b5d62f | ||
|
|
6a0600cd7f | ||
|
|
42395abe75 | ||
|
|
73804d45df | ||
|
|
8a4982e169 | ||
|
|
c058498267 | ||
|
|
09cc024a3f | ||
|
|
47ffa7f62d | ||
|
|
c2abc4277b | ||
|
|
e4c193440e | ||
|
|
b1b4bc7fb3 | ||
|
|
a63e03bf16 | ||
|
|
b53908ef0f | ||
|
|
75f6cff5a5 | ||
|
|
1b1f36c5d6 | ||
|
|
2001c15132 | ||
|
|
4e2af45428 | ||
|
|
bed74f6ab6 | ||
|
|
57ad8d8aaa | ||
|
|
dc8b23d48b | ||
|
|
4392eb9ea0 | ||
|
|
9442e2c36f | ||
|
|
db2efbe089 | ||
|
|
d810d17abb | ||
|
|
37766d3ecf | ||
|
|
1b5463651f | ||
|
|
fba2b7a6b3 | ||
|
|
8295b951d9 | ||
|
|
fc01a61290 | ||
|
|
3f8a48732e | ||
|
|
be9bbc924e | ||
|
|
3923e05178 | ||
|
|
ccc123b26f | ||
|
|
ba692d5b7d | ||
|
|
b27922dd3d | ||
|
|
18f5998f8a | ||
|
|
65f1371cf4 | ||
|
|
50c2931484 | ||
|
|
6d152707cf | ||
|
|
8e136579b3 | ||
|
|
6fba5cb020 | ||
|
|
988026611c | ||
|
|
7170bfcadc | ||
|
|
5c2fc8684e | ||
|
|
34f5278605 | ||
|
|
5584357fee | ||
|
|
260f108749 | ||
|
|
3b76fe3d99 | ||
|
|
d116be6fd4 | ||
|
|
51879afe07 | ||
|
|
d70f102655 | ||
|
|
5f54b95998 | ||
|
|
6054d559e1 | ||
|
|
0b56df20d5 | ||
|
|
458a92c34a | ||
|
|
51c645928f | ||
|
|
7b9a5e9db4 | ||
|
|
2756b80ac0 | ||
|
|
62094492a9 | ||
|
|
92781be618 | ||
|
|
ce2079b0bc | ||
|
|
1e4a03b056 | ||
|
|
7f69c41bf6 | ||
|
|
a31dc6fdcb | ||
|
|
9448e06ad8 | ||
|
|
aa9d84c639 | ||
|
|
b0cec61d66 | ||
|
|
9f0d3a5054 | ||
|
|
fefad5d806 | ||
|
|
35d5912275 | ||
|
|
e7aa1ca98c | ||
|
|
6554a34752 | ||
|
|
6527c0a7f5 | ||
|
|
8f30199a22 | ||
|
|
e25ab31316 | ||
|
|
051a4eda87 | ||
|
|
45f36f9ad6 | ||
|
|
468d013032 | ||
|
|
cd0f8a6fa5 | ||
|
|
f2125623f6 | ||
|
|
1000378b78 | ||
|
|
c590e4a825 | ||
|
|
a4eb219a9a | ||
|
|
6fe7fe46f3 | ||
|
|
fb7f80b646 | ||
|
|
00ff832e07 | ||
|
|
47a5382709 | ||
|
|
a4f81e5e9f | ||
|
|
6847283fb3 | ||
|
|
5e35f46b96 | ||
|
|
81e158d74a | ||
|
|
1159b4541e | ||
|
|
49eb39e45f | ||
|
|
0ec2a705da | ||
|
|
5e5243e3fb | ||
|
|
0b3fe5470a | ||
|
|
ef4476a72e | ||
|
|
8d0e8139de | ||
|
|
256581d4ed | ||
|
|
56b81281a8 | ||
|
|
f7aa146a94 | ||
|
|
a797dba594 | ||
|
|
2cce1bca0a | ||
|
|
8f2935e241 | ||
|
|
e7954a0f79 | ||
|
|
169842f265 | ||
|
|
627c500de1 | ||
|
|
aa5e1beb7f | ||
|
|
77346f8438 | ||
|
|
bbe766cf23 | ||
|
|
542105601b | ||
|
|
7e20abdd37 | ||
|
|
9b8d61b2b0 | ||
|
|
b613e8f01e | ||
|
|
02b5dec304 | ||
|
|
6af440789c | ||
|
|
a2b04e055a | ||
|
|
8d814d2d9c | ||
|
|
0fc6396974 | ||
|
|
c813811721 | ||
|
|
8970cd915a | ||
|
|
8964246e22 | ||
|
|
bec05967d3 | ||
|
|
cecf80250c | ||
|
|
93da193428 | ||
|
|
40a3abf5b9 | ||
|
|
7e18207a2b | ||
|
|
b451a5a672 | ||
|
|
5ca29ef5ce | ||
|
|
37dd46432a | ||
|
|
cd78e311d5 | ||
|
|
8a5ebb88e0 | ||
|
|
2411d29822 | ||
|
|
8e11a40662 | ||
|
|
bda4474d62 | ||
|
|
95603d3fa2 | ||
|
|
e7be3be17f | ||
|
|
72172805ba | ||
|
|
da7a58fb43 | ||
|
|
82bb7a7c43 | ||
|
|
505226f8ad | ||
|
|
867afe29f7 | ||
|
|
3ee38d7b6d | ||
|
|
15980d70fb | ||
|
|
5720994a31 | ||
|
|
22beae23e0 | ||
|
|
fb441b4910 | ||
|
|
9a34036d14 | ||
|
|
27da638a91 | ||
|
|
b3322b35c1 | ||
|
|
a43428c2c4 | ||
|
|
d643690eef | ||
|
|
310fd18e89 | ||
|
|
da0bcaa482 | ||
|
|
ed9b61fd11 | ||
|
|
e3cfa45607 | ||
|
|
1bd0db20ce | ||
|
|
3c97cffc09 | ||
|
|
a798147e71 | ||
|
|
e8f5bce3d5 | ||
|
|
ef334aff50 | ||
|
|
9b3893b807 | ||
|
|
4406cdb571 | ||
|
|
9a16143c89 | ||
|
|
9f6b4b60e3 | ||
|
|
b1b6e9ec20 | ||
|
|
6019ab177c | ||
|
|
d387b2d792 | ||
|
|
2985dc9cf8 | ||
|
|
8388a77f0b | ||
|
|
2e0acd3a78 | ||
|
|
d60ab3e953 | ||
|
|
fbfbd28bea | ||
|
|
c7737ca96e | ||
|
|
c32d225ef3 | ||
|
|
56807cbd3a | ||
|
|
3357fd6c9c | ||
|
|
7968f1ce4f | ||
|
|
575248063d | ||
|
|
2cf265f8e8 | ||
|
|
9195e677ab | ||
|
|
8c2f6bddd8 | ||
|
|
89d2604ce2 | ||
|
|
df4542017a | ||
|
|
ffd920e5b2 | ||
|
|
9b0fe540c2 | ||
|
|
bf443be0d3 | ||
|
|
61ae40b936 | ||
|
|
e919056f9b | ||
|
|
8c0a671078 | ||
|
|
3ae10d9123 | ||
|
|
b8b39b4785 | ||
|
|
0a3c1b13ed | ||
|
|
02d0a9afd5 | ||
|
|
dd3cd6c768 | ||
|
|
bc1955bff3 | ||
|
|
32282bbe9f | ||
|
|
351dc7f3df | ||
|
|
224b5d7f3e | ||
|
|
4d17a6a5bb | ||
|
|
52adb8b9fd | ||
|
|
75eac1c9aa | ||
|
|
28b213a419 | ||
|
|
fc44ff9fba | ||
|
|
cf52d6ddb4 | ||
|
|
f3ad0d86fe | ||
|
|
bdcd7f1f54 | ||
|
|
901ab1fdda | ||
|
|
e8112dba0e | ||
|
|
4685a06d27 | ||
|
|
7abfd2148e | ||
|
|
6cf3307a3b | ||
|
|
3219deed79 | ||
|
|
eaf7ea9618 | ||
|
|
75f05732bb | ||
|
|
58969b26f6 | ||
|
|
d583f7ccc3 | ||
|
|
ab5bc1fd25 | ||
|
|
c7caf16a94 | ||
|
|
b26c9c45c8 | ||
|
|
ea08696d10 | ||
|
|
e8c615b203 | ||
|
|
8b12907dfb | ||
|
|
2f8912a8ae | ||
|
|
58017960c9 | ||
|
|
d109506cf2 | ||
|
|
eb996e73d8 | ||
|
|
4f061e3464 | ||
|
|
74ecb14a33 | ||
|
|
6ed5d4687d | ||
|
|
290b791eb3 | ||
|
|
c6d896d717 | ||
|
|
2a8dc4ad22 | ||
|
|
14f667e2b9 | ||
|
|
b138332608 | ||
|
|
5c098339a9 | ||
|
|
093a6f12bf | ||
|
|
95438c1861 | ||
|
|
d23a45081a | ||
|
|
1880add7dc | ||
|
|
429ca5d447 | ||
|
|
651581d677 | ||
|
|
637d6cda18 | ||
|
|
70cb9c5275 | ||
|
|
37a9fc6a98 | ||
|
|
f416d1ba3d | ||
|
|
845f339a30 | ||
|
|
17a5f59c0d | ||
|
|
29709c4159 | ||
|
|
898eb405f0 | ||
|
|
a6755d0ddd | ||
|
|
9778ed6fc5 | ||
|
|
9c86a36afb | ||
|
|
12c15b23ab | ||
|
|
f2c955985c | ||
|
|
10d5163ef5 | ||
|
|
221ce71b97 | ||
|
|
0597084c3d | ||
|
|
f791e2726f | ||
|
|
dbc28c0035 | ||
|
|
3426306805 | ||
|
|
c2ea92ab37 | ||
|
|
749f006621 | ||
|
|
4756de8066 | ||
|
|
79daec662c | ||
|
|
9fb5f7f2dd | ||
|
|
70589a6263 | ||
|
|
38137cea2c | ||
|
|
6b3d85af62 | ||
|
|
8abb93114f | ||
|
|
671654aaaf | ||
|
|
73e71ff954 | ||
|
|
07ae10bcd6 | ||
|
|
6996db9e63 | ||
|
|
3723e73e81 | ||
|
|
711ffb1db7 | ||
|
|
f995accd0c | ||
|
|
6bd8ec5791 | ||
|
|
599e3b8828 | ||
|
|
044a58f657 | ||
|
|
fd69f08a1d | ||
|
|
82ec3dc5f1 | ||
|
|
0d34b82d72 | ||
|
|
20ab2a71ac | ||
|
|
d16e0bf1c6 | ||
|
|
f0e7b9aec7 | ||
|
|
01b9e15930 | ||
|
|
0f30a11510 | ||
|
|
447f01a3dd | ||
|
|
695a4f3b0e | ||
|
|
dea725e1e3 | ||
|
|
8b91e3034d | ||
|
|
fdbd3d6495 | ||
|
|
655fbf4799 | ||
|
|
425c5fd682 | ||
|
|
cc3441dfeb | ||
|
|
450fbd8e8d | ||
|
|
d328111b38 | ||
|
|
5d06b9e84c | ||
|
|
555b5d0795 | ||
|
|
35083f87fe | ||
|
|
c71bf61511 | ||
|
|
5a96a63ff8 | ||
|
|
220c0e4bc5 | ||
|
|
3b142db569 | ||
|
|
1509f5cd8b | ||
|
|
3b0790f136 | ||
|
|
b0cc1440dd | ||
|
|
d7d6e24506 | ||
|
|
b1bd7e9fcd | ||
|
|
79afb0bcdc | ||
|
|
86f15c8674 | ||
|
|
4440e6fcaa | ||
|
|
15961f6cce | ||
|
|
7117545509 | ||
|
|
e855be64ad | ||
|
|
781a945ef4 | ||
|
|
4d0aa5e1c1 | ||
|
|
3d166ae353 | ||
|
|
afdb0d0324 | ||
|
|
40345f25ff | ||
|
|
f6f8c9f80f | ||
|
|
b135bf86c3 | ||
|
|
7845a844d4 | ||
|
|
83f5a7e5b7 | ||
|
|
7ca3e916a5 | ||
|
|
6b5ecd7309 | ||
|
|
af546e7f1d | ||
|
|
6530db23b7 | ||
|
|
0745fba881 | ||
|
|
45c28d47c8 | ||
|
|
88009292bb | ||
|
|
25d41ed51c | ||
|
|
ede60970b9 | ||
|
|
86d3461700 | ||
|
|
733ecdd59b | ||
|
|
e0455b76b8 | ||
|
|
cc88232c8e | ||
|
|
0129a0575c | ||
|
|
98edcb1772 | ||
|
|
7062b9327c | ||
|
|
b9abf0b683 | ||
|
|
98a9377384 | ||
|
|
300111b850 | ||
|
|
f4accbfe2d | ||
|
|
2bd06922e0 | ||
|
|
f81e3262d0 | ||
|
|
beb4bba7fa | ||
|
|
03ad9be078 | ||
|
|
d53b6dbee1 | ||
|
|
c49b1ddc02 | ||
|
|
f5cb7ddc7b | ||
|
|
a2b61ea880 | ||
|
|
1d1f96cda4 | ||
|
|
a4f79ccb3f | ||
|
|
78387a033b | ||
|
|
21a1dfcbac | ||
|
|
4b2f69c3c8 | ||
|
|
4b19f8498b | ||
|
|
95fe40aef3 | ||
|
|
8c4b4fcaa4 | ||
|
|
524ef2e6c9 | ||
|
|
858f9ee02d | ||
|
|
0b200472f1 | ||
|
|
9482818751 | ||
|
|
36f867cafa | ||
|
|
dd9cb30d25 | ||
|
|
53fe3e5b61 | ||
|
|
a849c38ce6 | ||
|
|
d4d763af94 | ||
|
|
dc186df695 | ||
|
|
95dbddf9bb | ||
|
|
023c3908ae | ||
|
|
ac993896cc | ||
|
|
456764468a | ||
|
|
9170e48992 | ||
|
|
7b9d971238 | ||
|
|
f8af92eb5b | ||
|
|
d5aa1cde51 | ||
|
|
cdc5740f85 | ||
|
|
c2209138fe | ||
|
|
45f5d55b6a | ||
|
|
4298ac609d | ||
|
|
15ca240bac | ||
|
|
a40df621d0 | ||
|
|
2a86a4cbc5 | ||
|
|
b6478924bb | ||
|
|
b84161fde2 | ||
|
|
e28c2331e2 | ||
|
|
5c2e692a90 | ||
|
|
c2d79c142f | ||
|
|
189497fffa | ||
|
|
235e6eeed2 | ||
|
|
80b99fccd9 | ||
|
|
7f5288dea6 | ||
|
|
528468a342 | ||
|
|
fec2734fe2 | ||
|
|
ad69b0caac | ||
|
|
a567721114 | ||
|
|
5bb6f7649a | ||
|
|
0b292af3b1 | ||
|
|
76d27017de | ||
|
|
addc62457a | ||
|
|
79786b8192 | ||
|
|
da7cd0b10d | ||
|
|
003bc927a4 | ||
|
|
5e6a6f6a95 | ||
|
|
0108c3390e | ||
|
|
5b279660e0 | ||
|
|
77c707923f | ||
|
|
23fb21d234 | ||
|
|
2a5d240df9 | ||
|
|
a5dbbc6170 | ||
|
|
abafb7a6c5 | ||
|
|
f5dce019df | ||
|
|
64e3ba7f14 | ||
|
|
2f2eb148fa | ||
|
|
6c8bda24fd | ||
|
|
a9fe6afa01 | ||
|
|
626dea07fa | ||
|
|
188f03b39e | ||
|
|
da0d55fcea | ||
|
|
f2e078ce6a | ||
|
|
72bfa3da9b | ||
|
|
27104c7350 | ||
|
|
263d085590 | ||
|
|
f16581cdf4 | ||
|
|
413eb283a4 | ||
|
|
7952da99c9 | ||
|
|
3d0dce7e8d | ||
|
|
4328072f49 | ||
|
|
22aab024c2 | ||
|
|
18ec9bc973 | ||
|
|
2c00b570aa | ||
|
|
5ac15f11dc | ||
|
|
cda235af7e | ||
|
|
51184dd209 | ||
|
|
aa9616d6b0 | ||
|
|
ae08abe821 | ||
|
|
d54cc35445 | ||
|
|
f8a8a645eb | ||
|
|
32ed35d409 | ||
|
|
242ddd5f40 | ||
|
|
b9ed36641f | ||
|
|
0622322ef4 | ||
|
|
3f19105ce3 | ||
|
|
37a77d2295 | ||
|
|
352e8d70c4 | ||
|
|
b54681d7c2 | ||
|
|
f56cf3290e | ||
|
|
6a5794d0c0 | ||
|
|
0a67cf3cd1 | ||
|
|
22cb77d78e | ||
|
|
10e5508c8a | ||
|
|
f232823820 | ||
|
|
a76e5835fd | ||
|
|
cdcb7ce656 | ||
|
|
99b9dc90ae |
No files matched your search
@@ -0,0 +1 @@
|
||||
* text=auto eol=lf
|
||||
@@ -0,0 +1,38 @@
|
||||
Contributing to ProtocolLib
|
||||
===========================
|
||||
This page will detail specific things that must be done if you intend to contribute to this project.
|
||||
|
||||
## Updating ProtocolLib to a New Protocol Version
|
||||
#### Before we get started:
|
||||
1. **When do we need to update the protocol version?**
|
||||
_A Minecraft protocol version is a number for each specification of the Minecraft protocol.
|
||||
This means it is incremented when there is a change to the [Protocol Specification](http://wiki.vg/Protocol),
|
||||
but not necessarily for each Minecraft version update. If the latest Minecraft version contains a protocol change,
|
||||
that means ProtocolLib needs to be updated._
|
||||
2. **When do we need to update other parts of ProtocolLib?**
|
||||
_When the package version guard changes. The package version guard is the version string inserted
|
||||
into the package identifier for CraftBukkit and net.minecraft.server (e.g. v1_9_R2). It is incremented
|
||||
by Spigot if there is a major breaking change in the internal server code. This is to encourage plugins
|
||||
that interact directly with CraftBukkit/nms code to update more specifically. However, since most of
|
||||
ProtocolLib uses reflection, and the structure of packet classes is generally static, one will have
|
||||
to go through the code and change imports from nms/CraftBukkit._
|
||||
|
||||
#### Ready? Let's get started!
|
||||
1. Read the [Protocol Changes](http://wiki.vg/Protocol_History). Always make sure the list is both
|
||||
complete and correct. If you're unsure, don't hesitate to ask in #mcdevs (the people who maintain wiki.vg) on [libera.chat](http://libera.chat)
|
||||
or #spigot on [irc.spi.gt](http://irc.spi.gt) ([webchat](https://irc.spi.gt/iris/?channels=spigot)).
|
||||
2. Search for usages of the now-defunct NMS package guard and change them.
|
||||
3. The class `com.comphenix.protocol.PacketType` contains a list of all the packets. If any packets were added or removed
|
||||
(or had their ID changed), make sure to update this list. If a packet was removed
|
||||
in favor of usage of another packet, instead of removing it, move it to the bottom of the list
|
||||
in its section, add a deprecation warning to it, and redirect it to the packet that replaced it.
|
||||
4. `mvn` in the root directory to build the project.
|
||||
5. If tests fail in the maven build, go through the tests to make sure you removed references to any removed packets
|
||||
and changed the NMS version guards.
|
||||
6. Increment the package version in `com.comphenix.protocol.utility.Constants`.
|
||||
7. `com.comphenix.protocol.ProtocolLibrary` contains several constants that must be updated, including the Minecraft version
|
||||
and the release date.
|
||||
8. `com.comphenix.protocol.utility.MinecraftProtocolVersion` contains a map of all the protocol version integers.
|
||||
If the protocol version has been incremented, add a new line to the map.
|
||||
9. `mvn` in root directory again. If it builds successfully, test on the appropriate version of a Spigot server. If
|
||||
the build fails, debug!
|
||||
@@ -0,0 +1,2 @@
|
||||
github: [dmulloy2]
|
||||
custom: ["https://paypal.me/dmulloy2"]
|
||||
@@ -0,0 +1,12 @@
|
||||
_Follow this template except for feature requests. Use pastebin when providing /protocol dump and any relevant errors._
|
||||
|
||||
Make sure you've done the following:
|
||||
- [ ] You're using the latest build for your server version
|
||||
- [ ] This isn't fixed in a recent development build
|
||||
- [ ] This isn't an issue caused by another plugin
|
||||
- [ ] You've checked for duplicate issues
|
||||
- [ ] You didn't use `/reload`
|
||||
|
||||
Debug paste link:
|
||||
|
||||
Description and relevant errors:
|
||||
@@ -0,0 +1,29 @@
|
||||
---
|
||||
name: API question
|
||||
about: Questions related to using ProtocolLib in your plugins
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Make sure you're doing the following**
|
||||
- [ ] You're using the latest build for your server version
|
||||
- [ ] This isn't an issue caused by another plugin
|
||||
- [ ] You've checked for duplicate issues
|
||||
- [ ] You didn't use `/reload`
|
||||
|
||||
**Describe the question**
|
||||
A clear and concise description of what your question is.
|
||||
|
||||
**API method(s) used**
|
||||
List what API method(s) you're using
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Code**
|
||||
If applicable, add relevant code from your project
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
@@ -0,0 +1,32 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
- [ ] This issue is not solved in a development build
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Version Info**
|
||||
Provide your ProtocolLib install info with `/protocol dump` through pastebin.
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
@@ -0,0 +1,22 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
- [ ] This feature is not currently present in a development build
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
@@ -0,0 +1,28 @@
|
||||
name: ProtocolLib full build lifecycle
|
||||
|
||||
on:
|
||||
- push
|
||||
- pull_request
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup java
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '17'
|
||||
cache: 'maven'
|
||||
|
||||
- name: Run maven build lifecycle
|
||||
run: mvn --batch-mode clean test package
|
||||
|
||||
- name: Upload plugin file
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: ProtocolLib
|
||||
path: target/ProtocolLib.jar
|
||||
@@ -0,0 +1,41 @@
|
||||
name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "master" ]
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [ "master" ]
|
||||
schedule:
|
||||
- cron: '28 9 * * 1'
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
security-events: write
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup java
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '17'
|
||||
cache: 'maven'
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
with:
|
||||
languages: 'java'
|
||||
|
||||
- name: Run maven build lifecycle
|
||||
run: mvn --batch-mode clean test package
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v2
|
||||
+183
@@ -0,0 +1,183 @@
|
||||
#################
|
||||
## Eclipse
|
||||
#################
|
||||
|
||||
# Generic project files
|
||||
.project
|
||||
.classpath
|
||||
.settings/
|
||||
bin/
|
||||
.idea/
|
||||
*.iml
|
||||
.ipr
|
||||
.iws
|
||||
|
||||
*.pydevproject
|
||||
.metadata
|
||||
tmp/
|
||||
*.tmp
|
||||
*.bak
|
||||
*.swp
|
||||
*~.nib
|
||||
local.properties
|
||||
.loadpath
|
||||
|
||||
# External tool builders
|
||||
.externalToolBuilders/
|
||||
|
||||
# Locally stored "Eclipse launch configurations"
|
||||
*.launch
|
||||
|
||||
# CDT-specific
|
||||
.cproject
|
||||
|
||||
# PDT-specific
|
||||
.buildpath
|
||||
|
||||
#################
|
||||
## Maven
|
||||
#################
|
||||
|
||||
target/
|
||||
dependency-reduced-pom.xml
|
||||
|
||||
#################
|
||||
## 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
|
||||
|
||||
# Log4J files
|
||||
logs/
|
||||
|
||||
.gradle/
|
||||
build/
|
||||
logs/
|
||||
.java-version
|
||||
@@ -1,27 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry including="**/*.java" kind="src" output="target/classes" path="src/main/java">
|
||||
<attributes>
|
||||
<attribute name="optional" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry combineaccessrules="false" kind="src" path="/ProtocolLib"/>
|
||||
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
|
||||
<attributes>
|
||||
<attribute name="optional" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="output" path="target/classes"/>
|
||||
</classpath>
|
||||
@@ -1 +0,0 @@
|
||||
target/
|
||||
@@ -1,23 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>ItemDisguise</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.m2e.core.maven2Builder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
||||
@@ -1,12 +0,0 @@
|
||||
eclipse.preferences.version=1
|
||||
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
|
||||
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
|
||||
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
|
||||
org.eclipse.jdt.core.compiler.compliance=1.6
|
||||
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
|
||||
org.eclipse.jdt.core.compiler.debug.localVariable=generate
|
||||
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
|
||||
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
|
||||
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
|
||||
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
|
||||
org.eclipse.jdt.core.compiler.source=1.6
|
||||
@@ -1,4 +0,0 @@
|
||||
activeProfiles=
|
||||
eclipse.preferences.version=1
|
||||
resolveWorkspaceProjects=true
|
||||
version=1
|
||||
@@ -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>
|
||||
@@ -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,27 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry including="**/*.java" kind="src" output="target/classes" path="src/main/java">
|
||||
<attributes>
|
||||
<attribute name="optional" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
|
||||
<attributes>
|
||||
<attribute name="optional" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="output" path="target/classes"/>
|
||||
</classpath>
|
||||
@@ -1 +0,0 @@
|
||||
target/
|
||||
@@ -1,23 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>ProtocolLib</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.m2e.core.maven2Builder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
||||
@@ -1,4 +0,0 @@
|
||||
eclipse.preferences.version=1
|
||||
encoding//src/main/java=cp1252
|
||||
encoding//src/test/java=cp1252
|
||||
encoding/<project>=cp1252
|
||||
@@ -1,12 +0,0 @@
|
||||
eclipse.preferences.version=1
|
||||
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
|
||||
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
|
||||
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
|
||||
org.eclipse.jdt.core.compiler.compliance=1.6
|
||||
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
|
||||
org.eclipse.jdt.core.compiler.debug.localVariable=generate
|
||||
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
|
||||
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
|
||||
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
|
||||
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
|
||||
org.eclipse.jdt.core.compiler.source=1.6
|
||||
@@ -1,4 +0,0 @@
|
||||
activeProfiles=
|
||||
eclipse.preferences.version=1
|
||||
resolveWorkspaceProjects=true
|
||||
version=1
|
||||
@@ -1,187 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<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/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>com.comphenix.protocol</groupId>
|
||||
<artifactId>ProtocolLib</artifactId>
|
||||
<name>ProtocolLib</name>
|
||||
<version>1.5.0</version>
|
||||
<description>Provides read/write access to the Minecraft protocol.</description>
|
||||
<url>http://dev.bukkit.org/server-mods/protocollib/</url>
|
||||
<developers>
|
||||
<developer>
|
||||
<id>aadnk</id>
|
||||
<name>Kristian S. Stangeland</name>
|
||||
<email>kr_stang@hotmail.com</email>
|
||||
<url>http://comphenix.net/</url>
|
||||
<roles>
|
||||
<role>developer</role>
|
||||
<role>maintainer</role>
|
||||
</roles>
|
||||
<timezone>1</timezone>
|
||||
</developer>
|
||||
</developers>
|
||||
<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>
|
||||
<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>
|
||||
<build>
|
||||
<sourceDirectory>src/main/java</sourceDirectory>
|
||||
<testSourceDirectory>src/test/java</testSourceDirectory>
|
||||
<defaultGoal>clean install</defaultGoal>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>src/main/java</directory>
|
||||
<excludes>
|
||||
<exclude>**/*.java</exclude>
|
||||
</excludes>
|
||||
</resource>
|
||||
</resources>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>2.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<shadedArtifactAttached>false</shadedArtifactAttached>
|
||||
<createDependencyReducedPom>true</createDependencyReducedPom>
|
||||
<relocations>
|
||||
<relocation>
|
||||
<pattern>net.sf</pattern>
|
||||
<shadedPattern>com.comphenix.net.sf</shadedPattern>
|
||||
</relocation>
|
||||
</relocations>
|
||||
<artifactSet>
|
||||
<excludes>
|
||||
<exclude>org.bukkit:craftbukkit</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>
|
||||
</plugins>
|
||||
</build>
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>release-sign-artifacts</id>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-source-plugin</artifactId>
|
||||
<version>2.2</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-sources</id>
|
||||
<goals>
|
||||
<goal>jar</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<version>2.9</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-javadocs</id>
|
||||
<goals>
|
||||
<goal>jar</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<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>
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>bukkit-rep</id>
|
||||
<url>http://repo.bukkit.org/content/groups/public</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.bukkit</groupId>
|
||||
<artifactId>craftbukkit</artifactId>
|
||||
<version>1.3.2-R1.0</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>4.10</version>
|
||||
<scope>test</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<artifactId>hamcrest-core</artifactId>
|
||||
<groupId>org.hamcrest</groupId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<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>
|
||||
<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>cp1252</project.build.sourceEncoding>
|
||||
</properties>
|
||||
</project>
|
||||
|
||||
@@ -1,215 +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</artifactId>
|
||||
<version>1.5.0</version>
|
||||
<packaging>jar</packaging>
|
||||
<description>Provides read/write access to the Minecraft protocol.</description>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>cp1252</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<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>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>bukkit-rep</id>
|
||||
<url>http://repo.bukkit.org/content/groups/public</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<build>
|
||||
<defaultGoal>clean install</defaultGoal>
|
||||
<sourceDirectory>src/main/java</sourceDirectory>
|
||||
<testSourceDirectory>src/test/java</testSourceDirectory>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>src/main/java</directory>
|
||||
<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>true</createDependencyReducedPom>
|
||||
|
||||
<relocations>
|
||||
<relocation>
|
||||
<pattern>net.sf</pattern>
|
||||
<shadedPattern>com.comphenix.net.sf</shadedPattern>
|
||||
</relocation>
|
||||
</relocations>
|
||||
|
||||
<artifactSet>
|
||||
<excludes>
|
||||
<exclude>org.bukkit:craftbukkit</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>
|
||||
|
||||
</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>
|
||||
|
||||
<url>http://dev.bukkit.org/server-mods/protocollib/</url>
|
||||
<name>ProtocolLib</name>
|
||||
|
||||
<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>aadnk</id>
|
||||
<name>Kristian S. Stangeland</name>
|
||||
<email>kr_stang@hotmail.com</email>
|
||||
<url>http://comphenix.net/</url>
|
||||
<roles>
|
||||
<role>developer</role>
|
||||
<role>maintainer</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>org.bukkit</groupId>
|
||||
<artifactId>craftbukkit</artifactId>
|
||||
<version>1.3.2-R1.0</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>4.10</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -1,134 +0,0 @@
|
||||
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.events.ListeningWhitelist;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.injector.BukkitUnwrapper;
|
||||
import com.comphenix.protocol.reflect.FieldUtils;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.comphenix.protocol.reflect.MethodUtils;
|
||||
import com.comphenix.protocol.reflect.ObjectCloner;
|
||||
import com.comphenix.protocol.reflect.PrimitiveUtils;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Used to fix ClassLoader leaks that may lead to filling up the permanent generation.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
class CleanupStaticMembers {
|
||||
|
||||
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
|
||||
Class<?>[] publicClasses = {
|
||||
AsyncListenerHandler.class, ListeningWhitelist.class, PacketContainer.class,
|
||||
BukkitUnwrapper.class, DefaultInstances.class, CollectionGenerator.class,
|
||||
PrimitiveGenerator.class, FuzzyReflection.class, MethodUtils.class,
|
||||
BackgroundCompiler.class, StructureCompiler.class,
|
||||
ObjectCloner.class, PrimitiveUtils.class, Packets.Server.class,
|
||||
Packets.Client.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.player.TemporaryPlayerFactory",
|
||||
"com.comphenix.protocol.injector.EntityUtilities",
|
||||
"com.comphenix.protocol.injector.MinecraftRegistry",
|
||||
"com.comphenix.protocol.injector.PacketInjector",
|
||||
"com.comphenix.protocol.injector.ReadPacketModifier",
|
||||
"com.comphenix.protocol.injector.StructureCache",
|
||||
"com.comphenix.protocol.reflect.compiler.BoxingHelper",
|
||||
"com.comphenix.protocol.reflect.compiler.MethodDescriptor"
|
||||
};
|
||||
|
||||
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)) {
|
||||
|
||||
try {
|
||||
setFinalStatic(field, null);
|
||||
} catch (IllegalAccessException e) {
|
||||
// Just inform the player
|
||||
reporter.reportWarning(this, "Unable to reset field " + field.getName() + ": " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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, "Unable to unload class " + name, e);
|
||||
}
|
||||
}
|
||||
|
||||
return output.toArray(new Class<?>[0]);
|
||||
}
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
|
||||
/**
|
||||
* Represents a object capable of sending or receiving packets.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public interface PacketStream {
|
||||
|
||||
/**
|
||||
* Send a packet to the given player.
|
||||
* @param reciever - the reciever.
|
||||
* @param packet - packet to send.
|
||||
* @throws InvocationTargetException - if an error occured when sending the packet.
|
||||
*/
|
||||
public void sendServerPacket(Player reciever, PacketContainer packet)
|
||||
throws InvocationTargetException;
|
||||
|
||||
/**
|
||||
* Send a packet to the given player.
|
||||
* @param reciever - the reciever.
|
||||
* @param packet - packet to send.
|
||||
* @param filters - whether or not to invoke any packet filters.
|
||||
* @throws InvocationTargetException - if an error occured when sending the packet.
|
||||
*/
|
||||
public void sendServerPacket(Player reciever, PacketContainer packet, boolean filters)
|
||||
throws InvocationTargetException;
|
||||
|
||||
/**
|
||||
* Simulate recieving a certain packet from a given player.
|
||||
* @param sender - the sender.
|
||||
* @param packet - the packet that was sent.
|
||||
* @throws InvocationTargetException If the reflection machinery failed.
|
||||
* @throws IllegalAccessException If the underlying method caused an error.
|
||||
*/
|
||||
public void recieveClientPacket(Player sender, PacketContainer packet)
|
||||
throws IllegalAccessException, InvocationTargetException;
|
||||
|
||||
/**
|
||||
* Simulate recieving a certain packet from a given player.
|
||||
* @param sender - the sender.
|
||||
* @param packet - the packet that was sent.
|
||||
* @param filters - whether or not to invoke any packet filters.
|
||||
* @throws InvocationTargetException If the reflection machinery failed.
|
||||
* @throws IllegalAccessException If the underlying method caused an error.
|
||||
*/
|
||||
public void recieveClientPacket(Player sender, PacketContainer packet, boolean filters)
|
||||
throws IllegalAccessException, InvocationTargetException;
|
||||
}
|
||||
@@ -1,257 +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.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public final class Packets {
|
||||
|
||||
/**
|
||||
* List of packets sent only by the server.
|
||||
* @author Kristian
|
||||
*/
|
||||
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;
|
||||
public static final int ENTITY_LOCATION_ACTION = 17;
|
||||
public static final int ARM_ANIMATION = 18;
|
||||
public static final int NAMED_ENTITY_SPAWN = 20;
|
||||
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;
|
||||
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 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 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;
|
||||
public static final int TILE_ENTITY_DATA = 132;
|
||||
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 CUSTOM_PAYLOAD = 250;
|
||||
public static final int KEY_RESPONSE = 252;
|
||||
public static final int KEY_REQUEST = 253;
|
||||
public static final int KICK_DISCONNECT = 255;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @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.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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.
|
||||
* @author Kristian
|
||||
*/
|
||||
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;
|
||||
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 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;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @return The current client registry.
|
||||
*/
|
||||
public static Server getServerRegistry() {
|
||||
return Server.getRegistry();
|
||||
}
|
||||
|
||||
/**
|
||||
* A registry that parses between names and packet IDs.
|
||||
* @return The current server registry.
|
||||
*/
|
||||
public static Client getClientRegistry() {
|
||||
return Client.INSTANCE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a packet by name. Must be capitalized and use underscores.
|
||||
* @param name - name of packet to find.
|
||||
* @return The packet ID found.
|
||||
*/
|
||||
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.
|
||||
* @param packetID - packet to retrieve name.
|
||||
* @return The name, or NULL if unable to find such a packet.
|
||||
*/
|
||||
public static String getDeclaredName(int packetID) {
|
||||
String serverAttempt = Server.INSTANCE.getDeclaredName(packetID);
|
||||
|
||||
if (serverAttempt != null)
|
||||
return serverAttempt;
|
||||
else
|
||||
return Client.INSTANCE.getDeclaredName(packetID);
|
||||
}
|
||||
}
|
||||
@@ -1,259 +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.IOException;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.plugin.PluginManager;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import com.comphenix.protocol.async.AsyncFilterManager;
|
||||
import com.comphenix.protocol.error.DetailedErrorReporter;
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.events.ConnectionSide;
|
||||
import com.comphenix.protocol.events.MonitorAdapter;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.injector.DelayedSingleTask;
|
||||
import com.comphenix.protocol.injector.PacketFilterManager;
|
||||
import com.comphenix.protocol.metrics.Statistics;
|
||||
import com.comphenix.protocol.reflect.compiler.BackgroundCompiler;
|
||||
|
||||
/**
|
||||
* The main entry point for ProtocolLib.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public class ProtocolLibrary extends JavaPlugin {
|
||||
|
||||
// There should only be one protocol manager, so we'll make it static
|
||||
private static PacketFilterManager protocolManager;
|
||||
|
||||
// Information logger
|
||||
private Logger logger;
|
||||
|
||||
// Error reporter
|
||||
private ErrorReporter reporter;
|
||||
|
||||
// Metrics and statistisc
|
||||
private Statistics statistisc;
|
||||
|
||||
// Structure compiler
|
||||
private BackgroundCompiler backgroundCompiler;
|
||||
|
||||
// Used to clean up server packets that have expired.
|
||||
// But mostly required to simulate recieving client packets.
|
||||
private int asyncPacketTask = -1;
|
||||
private int tickCounter = 0;
|
||||
private static final int ASYNC_PACKET_DELAY = 1;
|
||||
|
||||
// Used to unhook players after a delay
|
||||
private DelayedSingleTask unhookTask;
|
||||
|
||||
// Used for debugging
|
||||
private boolean debugListener;
|
||||
|
||||
@Override
|
||||
public void onLoad() {
|
||||
// Add global parameters
|
||||
DetailedErrorReporter reporter = new DetailedErrorReporter();
|
||||
|
||||
try {
|
||||
logger = getLoggerSafely();
|
||||
unhookTask = new DelayedSingleTask(this);
|
||||
protocolManager = new PacketFilterManager(getClassLoader(), getServer(), unhookTask, reporter);
|
||||
reporter.addGlobalParameter("manager", protocolManager);
|
||||
|
||||
} catch (Throwable e) {
|
||||
reporter.reportDetailed(this, "Cannot load ProtocolLib.", e, protocolManager);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
try {
|
||||
Server server = getServer();
|
||||
PluginManager manager = server.getPluginManager();
|
||||
|
||||
// Initialize background compiler
|
||||
if (backgroundCompiler == null) {
|
||||
backgroundCompiler = new BackgroundCompiler(getClassLoader());
|
||||
BackgroundCompiler.setInstance(backgroundCompiler);
|
||||
}
|
||||
|
||||
// Notify server managers of incompatible plugins
|
||||
checkForIncompatibility(manager);
|
||||
|
||||
// Player login and logout events
|
||||
protocolManager.registerEvents(manager, this);
|
||||
|
||||
// Worker that ensures that async packets are eventually sent
|
||||
createAsyncTask(server);
|
||||
//toggleDebugListener();
|
||||
|
||||
} catch (Throwable e) {
|
||||
reporter.reportDetailed(this, "Cannot enable ProtocolLib.", e);
|
||||
disablePlugin();
|
||||
return;
|
||||
}
|
||||
|
||||
// Try to enable statistics
|
||||
try {
|
||||
statistisc = new Statistics(this);
|
||||
} catch (IOException e) {
|
||||
reporter.reportDetailed(this, "Unable to enable metrics.", e, statistisc);
|
||||
} catch (Throwable e) {
|
||||
reporter.reportDetailed(this, "Metrics cannot be enabled. Incompatible Bukkit version.", e, statistisc);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle a listener that prints every sent and received packet.
|
||||
*/
|
||||
void toggleDebugListener() {
|
||||
|
||||
if (debugListener) {
|
||||
protocolManager.removePacketListeners(this);
|
||||
} else {
|
||||
// DEBUG DEBUG
|
||||
protocolManager.addPacketListener(new MonitorAdapter(this, ConnectionSide.BOTH, logger) {
|
||||
@Override
|
||||
public void onPacketReceiving(PacketEvent event) {
|
||||
Object handle = event.getPacket().getHandle();
|
||||
|
||||
logger.info(String.format(
|
||||
"RECEIVING %s@%s from %s.",
|
||||
handle.getClass().getSimpleName(), handle.hashCode(), event.getPlayer().getName()
|
||||
));
|
||||
};
|
||||
@Override
|
||||
public void onPacketSending(PacketEvent event) {
|
||||
Object handle = event.getPacket().getHandle();
|
||||
|
||||
logger.info(String.format(
|
||||
"SENDING %s@%s from %s.",
|
||||
handle.getClass().getSimpleName(), handle.hashCode(), event.getPlayer().getName()
|
||||
));
|
||||
}
|
||||
});
|
||||
}
|
||||
debugListener = !debugListener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable the current plugin.
|
||||
*/
|
||||
private void disablePlugin() {
|
||||
getServer().getPluginManager().disablePlugin(this);
|
||||
}
|
||||
|
||||
private void createAsyncTask(Server server) {
|
||||
try {
|
||||
if (asyncPacketTask >= 0)
|
||||
throw new IllegalStateException("Async task has already been created");
|
||||
|
||||
// Attempt to create task
|
||||
asyncPacketTask = server.getScheduler().scheduleSyncRepeatingTask(this, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
AsyncFilterManager manager = (AsyncFilterManager) protocolManager.getAsynchronousManager();
|
||||
|
||||
// We KNOW we're on the main thread at the moment
|
||||
manager.sendProcessedPackets(tickCounter++, true);
|
||||
}
|
||||
}, ASYNC_PACKET_DELAY, ASYNC_PACKET_DELAY);
|
||||
|
||||
} catch (Throwable e) {
|
||||
if (asyncPacketTask == -1) {
|
||||
reporter.reportDetailed(this, "Unable to create packet timeout task.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void checkForIncompatibility(PluginManager manager) {
|
||||
// Plugin authors: Notify me to remove these
|
||||
String[] incompatiblePlugins = {};
|
||||
|
||||
for (String plugin : incompatiblePlugins) {
|
||||
if (manager.getPlugin(plugin) != null) {
|
||||
// Check for versions, ect.
|
||||
reporter.reportWarning(this, "Detected incompatible plugin: " + plugin);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
// Disable compiler
|
||||
if (backgroundCompiler != null) {
|
||||
backgroundCompiler.shutdownAll();
|
||||
backgroundCompiler = null;
|
||||
BackgroundCompiler.setInstance(null);
|
||||
}
|
||||
|
||||
// Clean up
|
||||
if (asyncPacketTask >= 0) {
|
||||
getServer().getScheduler().cancelTask(asyncPacketTask);
|
||||
asyncPacketTask = -1;
|
||||
}
|
||||
|
||||
unhookTask.close();
|
||||
protocolManager.close();
|
||||
protocolManager = null;
|
||||
statistisc = null;
|
||||
|
||||
// Leaky ClassLoader begone!
|
||||
CleanupStaticMembers cleanup = new CleanupStaticMembers(getClassLoader(), reporter);
|
||||
cleanup.resetAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the packet protocol manager.
|
||||
* @return Packet protocol manager, or NULL if it has been disabled.
|
||||
*/
|
||||
public static ProtocolManager getProtocolManager() {
|
||||
return protocolManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the metrics instance used to measure users of this library.
|
||||
* <p>
|
||||
* Note that this method may return NULL when the server is reloading or shutting down.
|
||||
* @return Metrics instance container.
|
||||
*/
|
||||
public Statistics getStatistics() {
|
||||
return statistisc;
|
||||
}
|
||||
|
||||
// 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,131 +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.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.events.PacketListener;
|
||||
import com.comphenix.protocol.injector.PacketConstructor;
|
||||
import com.comphenix.protocol.reflect.FieldAccessException;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
|
||||
/**
|
||||
* Represents an API for accessing the Minecraft protocol.
|
||||
* @author Kristian
|
||||
*/
|
||||
public interface ProtocolManager extends PacketStream {
|
||||
|
||||
/**
|
||||
* Retrieves a list of every registered packet listener.
|
||||
* @return Every registered packet listener.
|
||||
*/
|
||||
public ImmutableSet<PacketListener> getPacketListeners();
|
||||
|
||||
/**
|
||||
* Adds a packet listener.
|
||||
* <p>
|
||||
* Adding an already registered listener has no effect. If you need to change the packets
|
||||
* the current listener is observing, you must first remove the packet listener before you
|
||||
* can register it again.
|
||||
* @param listener - new packet listener.
|
||||
*/
|
||||
public void addPacketListener(PacketListener listener);
|
||||
|
||||
/**
|
||||
* Removes a given packet listener.
|
||||
* <p>
|
||||
* Attempting to remove a listener that doesn't exist has no effect.
|
||||
* @param listener - the packet listener to remove.
|
||||
*/
|
||||
public void removePacketListener(PacketListener listener);
|
||||
|
||||
/**
|
||||
* Removes every listener associated with the given plugin.
|
||||
* @param plugin - the plugin to unload.
|
||||
*/
|
||||
public void removePacketListeners(Plugin plugin);
|
||||
|
||||
/**
|
||||
* Constructs a new encapsulated Minecraft packet with the given ID.
|
||||
* @param id - packet ID.
|
||||
* @return New encapsulated Minecraft packet.
|
||||
*/
|
||||
public PacketContainer createPacket(int id);
|
||||
|
||||
/**
|
||||
* Constructs a new encapsulated Minecraft packet with the given ID.
|
||||
* <p>
|
||||
* If set to true, the <i>forceDefaults</i> option will force the system to automatically
|
||||
* give non-primitive fields in the packet sensible default values. For instance, certain
|
||||
* packets - like Packet60Explosion - require a List or Set to be non-null. If the
|
||||
* forceDefaults option is true, the List or Set will be automatically created.
|
||||
*
|
||||
* @param id - packet ID.
|
||||
* @param forceDefaults - TRUE to use sensible defaults in most fields, FALSE otherwise.
|
||||
* @return New encapsulated Minecraft packet.
|
||||
*/
|
||||
public PacketContainer createPacket(int id, boolean forceDefaults);
|
||||
|
||||
/**
|
||||
* Construct a packet using the special builtin Minecraft constructors.
|
||||
* @param id - the packet ID.
|
||||
* @param arguments - arguments that will be passed to the constructor.
|
||||
* @return The packet constructor.
|
||||
*/
|
||||
public PacketConstructor createPacketConstructor(int id, Object... arguments);
|
||||
|
||||
/**
|
||||
* Completely refresh all clients about an entity.
|
||||
* <p>
|
||||
* Note that this method is NOT thread safe. If you call this method from anything
|
||||
* but the main thread, it will throw an exception.
|
||||
* @param entity - entity to refresh.
|
||||
* @param observers - the clients to update.
|
||||
*/
|
||||
public void updateEntity(Entity entity, List<Player> observers) throws FieldAccessException;
|
||||
|
||||
/**
|
||||
* Retrieves a immutable set containing the ID of the sent server packets that will be observed by listeners.
|
||||
* @return Every filtered server packet.
|
||||
*/
|
||||
public Set<Integer> getSendingFilters();
|
||||
|
||||
/**
|
||||
* Retrieves a immutable set containing the ID of the recieved client packets that will be observed by listeners.
|
||||
* @return Every filtered client packet.
|
||||
*/
|
||||
public Set<Integer> getReceivingFilters();
|
||||
|
||||
/**
|
||||
* Determines whether or not this protocol mananger has been disabled.
|
||||
* @return TRUE if it has, FALSE otherwise.
|
||||
*/
|
||||
public boolean isClosed();
|
||||
|
||||
/**
|
||||
* Retrieve the current asyncronous packet manager.
|
||||
* @return Asyncronous packet manager.
|
||||
*/
|
||||
public AsynchronousManager getAsynchronousManager();
|
||||
}
|
||||
@@ -1,225 +0,0 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol.async;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.PriorityBlockingQueue;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.injector.PlayerLoggedOutException;
|
||||
import com.comphenix.protocol.reflect.FieldAccessException;
|
||||
|
||||
/**
|
||||
* Represents packets ready to be transmitted to a client.
|
||||
* @author Kristian
|
||||
*/
|
||||
class PacketSendingQueue {
|
||||
|
||||
public static final int INITIAL_CAPACITY = 64;
|
||||
|
||||
private PriorityBlockingQueue<PacketEventHolder> sendingQueue;
|
||||
|
||||
// Whether or not packet transmission can only occur on the main thread
|
||||
private final boolean synchronizeMain;
|
||||
|
||||
/**
|
||||
* Number of packet events in the queue.
|
||||
* @return The number of packet events in the queue.
|
||||
*/
|
||||
public int size() {
|
||||
return sendingQueue.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a packet sending queue.
|
||||
* @param synchronizeMain - whether or not to synchronize with the main thread.
|
||||
*/
|
||||
public PacketSendingQueue(boolean synchronizeMain) {
|
||||
this.sendingQueue = new PriorityBlockingQueue<PacketEventHolder>(INITIAL_CAPACITY);
|
||||
this.synchronizeMain = synchronizeMain;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue a packet for sending.
|
||||
* @param packet - packet to queue.
|
||||
*/
|
||||
public void enqueue(PacketEvent packet) {
|
||||
sendingQueue.add(new PacketEventHolder(packet));
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when one of the packets have finished processing.
|
||||
* @param packetUpdated - the packet that has now been updated.
|
||||
* @param onMainThread - whether or not this is occuring on the main thread.
|
||||
*/
|
||||
public synchronized void signalPacketUpdate(PacketEvent packetUpdated, boolean onMainThread) {
|
||||
|
||||
AsyncMarker marker = packetUpdated.getAsyncMarker();
|
||||
|
||||
// Should we reorder the event?
|
||||
if (marker.getQueuedSendingIndex() != marker.getNewSendingIndex()) {
|
||||
PacketEvent copy = PacketEvent.fromSynchronous(packetUpdated, marker);
|
||||
|
||||
// "Cancel" the original event
|
||||
packetUpdated.setCancelled(true);
|
||||
|
||||
// Enqueue the copy with the new sending index
|
||||
enqueue(copy);
|
||||
}
|
||||
|
||||
// Mark this packet as finished
|
||||
marker.setProcessed(true);
|
||||
trySendPackets(onMainThread);
|
||||
}
|
||||
|
||||
/***
|
||||
* Invoked when a list of packet IDs are no longer associated with any listeners.
|
||||
* @param packetsRemoved - packets that no longer have any listeners.
|
||||
* @param onMainThread - whether or not this is occuring on the main thread.
|
||||
*/
|
||||
public synchronized void signalPacketUpdate(List<Integer> packetsRemoved, boolean onMainThread) {
|
||||
|
||||
Set<Integer> lookup = new HashSet<Integer>(packetsRemoved);
|
||||
|
||||
// Note that this is O(n), so it might be expensive
|
||||
for (PacketEventHolder holder : sendingQueue) {
|
||||
PacketEvent event = holder.getEvent();
|
||||
|
||||
if (lookup.contains(event.getPacketID())) {
|
||||
event.getAsyncMarker().setProcessed(true);
|
||||
}
|
||||
}
|
||||
|
||||
// This is likely to have changed the situation a bit
|
||||
trySendPackets(onMainThread);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to send any remaining packets.
|
||||
* @param onMainThread - whether or not this is occuring on the main thread.
|
||||
*/
|
||||
public void trySendPackets(boolean onMainThread) {
|
||||
|
||||
// Transmit as many packets as we can
|
||||
while (true) {
|
||||
PacketEventHolder holder = sendingQueue.peek();
|
||||
|
||||
if (holder != null) {
|
||||
PacketEvent current = holder.getEvent();
|
||||
AsyncMarker marker = current.getAsyncMarker();
|
||||
|
||||
// Abort if we're not on the main thread
|
||||
if (synchronizeMain) {
|
||||
try {
|
||||
boolean wantAsync = marker.isMinecraftAsync(current);
|
||||
boolean wantSync = !wantAsync;
|
||||
|
||||
// Quit if we haven't fulfilled our promise
|
||||
if ((onMainThread && wantAsync) || (!onMainThread && wantSync))
|
||||
return;
|
||||
|
||||
} catch (FieldAccessException e) {
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (marker.isProcessed() || marker.hasExpired()) {
|
||||
if (marker.isProcessed() && !current.isCancelled()) {
|
||||
// Silently skip players that have logged out
|
||||
if (isOnline(current.getPlayer())) {
|
||||
sendPacket(current);
|
||||
}
|
||||
}
|
||||
|
||||
sendingQueue.poll();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Only repeat when packets are removed
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isOnline(Player player) {
|
||||
return player != null && player.isOnline();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send every packet, regardless of the processing state.
|
||||
*/
|
||||
private void forceSend() {
|
||||
while (true) {
|
||||
PacketEventHolder holder = sendingQueue.poll();
|
||||
|
||||
if (holder != null) {
|
||||
sendPacket(holder.getEvent());
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not the packet transmission must synchronize with the main thread.
|
||||
* @return TRUE if it must, FALSE otherwise.
|
||||
*/
|
||||
public boolean isSynchronizeMain() {
|
||||
return synchronizeMain;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transmit a packet, if it hasn't already.
|
||||
* @param event - the packet to transmit.
|
||||
*/
|
||||
private void sendPacket(PacketEvent event) {
|
||||
|
||||
AsyncMarker marker = event.getAsyncMarker();
|
||||
|
||||
try {
|
||||
// Don't send a packet twice
|
||||
if (marker != null && !marker.isTransmitted()) {
|
||||
marker.sendPacket(event);
|
||||
}
|
||||
|
||||
} catch (PlayerLoggedOutException e) {
|
||||
System.out.println(String.format(
|
||||
"[ProtocolLib] Warning: Dropped packet index %s of ID %s",
|
||||
marker.getOriginalSendingIndex(), event.getPacketID()
|
||||
));
|
||||
|
||||
} catch (IOException e) {
|
||||
// Just print the error
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatically transmits every delayed packet.
|
||||
*/
|
||||
public void cleanupAll() {
|
||||
// Note that the cleanup itself will always occur on the main thread
|
||||
forceSend();
|
||||
}
|
||||
}
|
||||
-240
@@ -1,240 +0,0 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol.concurrency;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import com.google.common.base.Objects;
|
||||
import com.google.common.collect.Iterables;
|
||||
|
||||
/**
|
||||
* An implicitly sorted array list that preserves insertion order and maintains duplicates.
|
||||
* @param <T> - type of the elements in the list.
|
||||
*/
|
||||
public class SortedCopyOnWriteArray<T extends Comparable<T>> implements Iterable<T>, Collection<T> {
|
||||
// Prevent reordering
|
||||
private volatile List<T> list;
|
||||
|
||||
/**
|
||||
* Construct an empty sorted array.
|
||||
*/
|
||||
public SortedCopyOnWriteArray() {
|
||||
list = new ArrayList<T>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a sorted array from the given list. The elements will be automatically sorted.
|
||||
* @param wrapped - the collection whose elements are to be placed into the list.
|
||||
*/
|
||||
public SortedCopyOnWriteArray(Collection<T> wrapped) {
|
||||
this.list = new ArrayList<T>(wrapped);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a sorted array from the given list.
|
||||
* @param wrapped - the collection whose elements are to be placed into the list.
|
||||
* @param sort - TRUE to automatically sort the collection, FALSE if it is already sorted.
|
||||
*/
|
||||
public SortedCopyOnWriteArray(Collection<T> wrapped, boolean sort) {
|
||||
this.list = new ArrayList<T>(wrapped);
|
||||
|
||||
if (sort) {
|
||||
Collections.sort(list);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts the given element in the proper location.
|
||||
* @param value - element to insert.
|
||||
*/
|
||||
@Override
|
||||
public synchronized boolean add(T value) {
|
||||
|
||||
// We use NULL as a special marker, so we don't allow it
|
||||
if (value == null)
|
||||
throw new IllegalArgumentException("value cannot be NULL");
|
||||
|
||||
List<T> copy = new ArrayList<T>();
|
||||
|
||||
for (T element : list) {
|
||||
// If the value is now greater than the current element, it should be placed right before it
|
||||
if (value != null && value.compareTo(element) < 0) {
|
||||
copy.add(value);
|
||||
value = null;
|
||||
}
|
||||
copy.add(element);
|
||||
}
|
||||
|
||||
// Don't forget to add it
|
||||
if (value != null)
|
||||
copy.add(value);
|
||||
|
||||
list = copy;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized boolean addAll(Collection<? extends T> values) {
|
||||
|
||||
if (values == null)
|
||||
throw new IllegalArgumentException("values cannot be NULL");
|
||||
if (values.size() == 0)
|
||||
return false;
|
||||
|
||||
List<T> copy = new ArrayList<T>();
|
||||
|
||||
// Insert the new content and sort it
|
||||
copy.addAll(list);
|
||||
copy.addAll(values);
|
||||
Collections.sort(copy);
|
||||
|
||||
list = copy;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes from the list by making a new list with every element except the one given.
|
||||
* <p>
|
||||
* Objects will be compared using the given objects equals() method.
|
||||
* @param value - value to remove.
|
||||
*/
|
||||
@Override
|
||||
public synchronized boolean remove(Object value) {
|
||||
List<T> copy = new ArrayList<T>();
|
||||
|
||||
// Note that there's not much to be gained from using BinarySearch, as we
|
||||
// have to copy (and thus read) the entire list regardless.
|
||||
|
||||
// Copy every element except the one given to us.
|
||||
for (T element : list) {
|
||||
if (value != null && !Objects.equal(value, element)) {
|
||||
copy.add(element);
|
||||
value = null;
|
||||
}
|
||||
}
|
||||
|
||||
list = copy;
|
||||
return value == null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeAll(Collection<?> values) {
|
||||
// Special cases
|
||||
if (values == null)
|
||||
throw new IllegalArgumentException("values cannot be NULL");
|
||||
if (values.size() == 0)
|
||||
return false;
|
||||
|
||||
List<T> copy = new ArrayList<T>();
|
||||
|
||||
copy.addAll(list);
|
||||
copy.removeAll(values);
|
||||
|
||||
list = copy;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean retainAll(Collection<?> values) {
|
||||
// Special cases
|
||||
if (values == null)
|
||||
throw new IllegalArgumentException("values cannot be NULL");
|
||||
if (values.size() == 0)
|
||||
return false;
|
||||
|
||||
List<T> copy = new ArrayList<T>();
|
||||
|
||||
copy.addAll(list);
|
||||
copy.removeAll(values);
|
||||
|
||||
list = copy;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes from the list by making a copy of every element except the one with the given index.
|
||||
* @param index - index of the element to remove.
|
||||
*/
|
||||
public synchronized void remove(int index) {
|
||||
|
||||
List<T> copy = new ArrayList<T>(list);
|
||||
|
||||
copy.remove(index);
|
||||
list = copy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves an element by index.
|
||||
* @param index - index of element to retrieve.
|
||||
* @return The element at the given location.
|
||||
*/
|
||||
public T get(int index) {
|
||||
return list.get(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the size of the list.
|
||||
* @return Size of the list.
|
||||
*/
|
||||
public int size() {
|
||||
return list.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves an iterator over the elements in the given list.
|
||||
* Warning: No not attempt to remove elements using the iterator.
|
||||
*/
|
||||
public Iterator<T> iterator() {
|
||||
return Iterables.unmodifiableIterable(list).iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
list = new ArrayList<T>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(Object value) {
|
||||
return list.contains(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsAll(Collection<?> values) {
|
||||
return list.containsAll(values);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return list.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] toArray() {
|
||||
return list.toArray();
|
||||
}
|
||||
|
||||
@SuppressWarnings("hiding")
|
||||
@Override
|
||||
public <T> T[] toArray(T[] a) {
|
||||
return list.toArray(a);
|
||||
}
|
||||
}
|
||||
@@ -1,268 +0,0 @@
|
||||
package com.comphenix.protocol.error;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.apache.commons.lang.builder.ToStringBuilder;
|
||||
import org.apache.commons.lang.builder.ToStringStyle;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import com.comphenix.protocol.events.PacketAdapter;
|
||||
import com.google.common.primitives.Primitives;
|
||||
|
||||
/**
|
||||
* Internal class used to handle exceptions.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public class DetailedErrorReporter implements ErrorReporter {
|
||||
|
||||
public static final String SECOND_LEVEL_PREFIX = " ";
|
||||
public static final String DEFAULT_PREFIX = " ";
|
||||
public static final String DEFAULT_SUPPORT_URL = "http://dev.bukkit.org/server-mods/protocollib/";
|
||||
public static final String PLUGIN_NAME = "ProtocolLib";
|
||||
|
||||
// We don't want to spam the server
|
||||
public static final int DEFAULT_MAX_ERROR_COUNT = 20;
|
||||
|
||||
protected String prefix;
|
||||
protected String supportURL;
|
||||
|
||||
protected int errorCount;
|
||||
protected int maxErrorCount;
|
||||
protected Logger logger;
|
||||
|
||||
// Whether or not Apache Commons is not present
|
||||
protected boolean apacheCommonsMissing;
|
||||
|
||||
// Map of global objects
|
||||
protected Map<String, Object> globalParameters = new HashMap<String, Object>();
|
||||
|
||||
/**
|
||||
* Create a default error reporting system.
|
||||
*/
|
||||
public DetailedErrorReporter() {
|
||||
this(DEFAULT_PREFIX, DEFAULT_SUPPORT_URL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a central error reporting system.
|
||||
* @param prefix - default line prefix.
|
||||
* @param supportURL - URL to report the error.
|
||||
*/
|
||||
public DetailedErrorReporter(String prefix, String supportURL) {
|
||||
this(prefix, supportURL, DEFAULT_MAX_ERROR_COUNT, getBukkitLogger());
|
||||
}
|
||||
|
||||
// Attempt to get the logger.
|
||||
private static Logger getBukkitLogger() {
|
||||
try {
|
||||
return Bukkit.getLogger();
|
||||
} catch (Throwable e) {
|
||||
return Logger.getLogger("Minecraft");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a central error reporting system.
|
||||
* @param prefix - default line prefix.
|
||||
* @param supportURL - URL to report the error.
|
||||
* @param maxErrorCount - number of errors to print before giving up.
|
||||
* @param logger - current logger.
|
||||
*/
|
||||
public DetailedErrorReporter(String prefix, String supportURL, int maxErrorCount, Logger logger) {
|
||||
this.prefix = prefix;
|
||||
this.supportURL = supportURL;
|
||||
this.maxErrorCount = maxErrorCount;
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportMinimal(Plugin sender, String methodName, Throwable error) {
|
||||
logger.log(Level.SEVERE, "[" + PLUGIN_NAME + "] Unhandled exception occured in " + methodName + " for " +
|
||||
PacketAdapter.getPluginName(sender), error);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportWarning(Object sender, String message) {
|
||||
logger.log(Level.WARNING, "[" + PLUGIN_NAME + "] [" + getSenderName(sender) + "] " + message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportWarning(Object sender, String message, Throwable error) {
|
||||
logger.log(Level.WARNING, "[" + PLUGIN_NAME + "] [" + getSenderName(sender) + "] " + message, error);
|
||||
}
|
||||
|
||||
private String getSenderName(Object sender) {
|
||||
if (sender != null)
|
||||
return sender.getClass().getSimpleName();
|
||||
else
|
||||
return "NULL";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportDetailed(Object sender, String message, Throwable error, Object... parameters) {
|
||||
|
||||
// Do not overtly spam the server!
|
||||
if (++errorCount > maxErrorCount) {
|
||||
String maxReached = String.format("Reached maxmimum error count. Cannot pass error %s from %s.", error, sender);
|
||||
logger.severe(maxReached);
|
||||
return;
|
||||
}
|
||||
|
||||
StringWriter text = new StringWriter();
|
||||
PrintWriter writer = new PrintWriter(text);
|
||||
|
||||
// Helpful message
|
||||
writer.println("[ProtocolLib] INTERNAL ERROR: " + message);
|
||||
writer.println("If this problem hasn't already been reported, please open a ticket");
|
||||
writer.println("at " + supportURL + " with the following data:");
|
||||
|
||||
// Now, let us print important exception information
|
||||
writer.println(" ===== STACK TRACE =====");
|
||||
|
||||
if (error != null)
|
||||
error.printStackTrace(writer);
|
||||
|
||||
// Data dump!
|
||||
writer.println(" ===== DUMP =====");
|
||||
|
||||
// Relevant parameters
|
||||
if (parameters != null && parameters.length > 0) {
|
||||
writer.println("Parameters:");
|
||||
|
||||
// We *really* want to get as much information as possible
|
||||
for (Object param : parameters) {
|
||||
writer.println(addPrefix(getStringDescription(param), SECOND_LEVEL_PREFIX));
|
||||
}
|
||||
}
|
||||
|
||||
// Global parameters
|
||||
for (String param : globalParameters()) {
|
||||
writer.println(SECOND_LEVEL_PREFIX + param + ":");
|
||||
writer.println(addPrefix(getStringDescription(getGlobalParameter(param)),
|
||||
SECOND_LEVEL_PREFIX + SECOND_LEVEL_PREFIX));
|
||||
}
|
||||
|
||||
// Now, for the sender itself
|
||||
writer.println("Sender:");
|
||||
writer.println(addPrefix(getStringDescription(sender), SECOND_LEVEL_PREFIX));
|
||||
|
||||
// Add the server version too
|
||||
if (Bukkit.getServer() != null) {
|
||||
writer.println("Server:");
|
||||
writer.println(addPrefix(Bukkit.getServer().getVersion(), SECOND_LEVEL_PREFIX));
|
||||
}
|
||||
|
||||
// Make sure it is reported
|
||||
logger.severe(addPrefix(text.toString(), prefix));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given prefix to every line in the text.
|
||||
* @param text - text to modify.
|
||||
* @param prefix - prefix added to every line in the text.
|
||||
* @return The modified text.
|
||||
*/
|
||||
protected String addPrefix(String text, String prefix) {
|
||||
return text.replaceAll("(?m)^", prefix);
|
||||
}
|
||||
|
||||
protected String getStringDescription(Object value) {
|
||||
|
||||
// We can't only rely on toString.
|
||||
if (value == null) {
|
||||
return "[NULL]";
|
||||
} if (isSimpleType(value)) {
|
||||
return value.toString();
|
||||
} else {
|
||||
try {
|
||||
if (!apacheCommonsMissing)
|
||||
return (ToStringBuilder.reflectionToString(value, ToStringStyle.MULTI_LINE_STYLE, false, null));
|
||||
} catch (Throwable ex) {
|
||||
// Apache is probably missing
|
||||
logger.warning("Cannot find Apache Commons. Object introspection disabled.");
|
||||
apacheCommonsMissing = true;
|
||||
}
|
||||
|
||||
// Just use toString()
|
||||
return String.format("%s", value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the given object is a wrapper for a primitive/simple type or not.
|
||||
* @param test - the object to test.
|
||||
* @return TRUE if this object is simple enough to simply be printed, FALSE othewise.
|
||||
*/
|
||||
protected boolean isSimpleType(Object test) {
|
||||
return test instanceof String || Primitives.isWrapperType(test.getClass());
|
||||
}
|
||||
|
||||
public int getErrorCount() {
|
||||
return errorCount;
|
||||
}
|
||||
|
||||
public void setErrorCount(int errorCount) {
|
||||
this.errorCount = errorCount;
|
||||
}
|
||||
|
||||
public int getMaxErrorCount() {
|
||||
return maxErrorCount;
|
||||
}
|
||||
|
||||
public void setMaxErrorCount(int maxErrorCount) {
|
||||
this.maxErrorCount = maxErrorCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given global parameter. It will be included in every error report.
|
||||
* @param key - name of parameter.
|
||||
* @param value - the global parameter itself.
|
||||
*/
|
||||
public void addGlobalParameter(String key, Object value) {
|
||||
globalParameters.put(key, value);
|
||||
}
|
||||
|
||||
public Object getGlobalParameter(String key) {
|
||||
return globalParameters.get(key);
|
||||
}
|
||||
|
||||
public void clearGlobalParameters() {
|
||||
globalParameters.clear();
|
||||
}
|
||||
|
||||
public Set<String> globalParameters() {
|
||||
return globalParameters.keySet();
|
||||
}
|
||||
|
||||
public String getSupportURL() {
|
||||
return supportURL;
|
||||
}
|
||||
|
||||
public void setSupportURL(String supportURL) {
|
||||
this.supportURL = supportURL;
|
||||
}
|
||||
|
||||
public String getPrefix() {
|
||||
return prefix;
|
||||
}
|
||||
|
||||
public void setPrefix(String prefix) {
|
||||
this.prefix = prefix;
|
||||
}
|
||||
|
||||
public Logger getLogger() {
|
||||
return logger;
|
||||
}
|
||||
|
||||
public void setLogger(Logger logger) {
|
||||
this.logger = logger;
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
package com.comphenix.protocol.error;
|
||||
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
public interface ErrorReporter {
|
||||
|
||||
/**
|
||||
* Prints a small minimal error report about an exception from another plugin.
|
||||
* @param sender - the other plugin.
|
||||
* @param methodName - name of the caller method.
|
||||
* @param error - the exception itself.
|
||||
*/
|
||||
public abstract void reportMinimal(Plugin sender, String methodName, Throwable error);
|
||||
|
||||
/**
|
||||
* Prints a warning message from the current plugin.
|
||||
* @param sender - the object containing the caller method.
|
||||
* @param message - error message.
|
||||
*/
|
||||
public abstract void reportWarning(Object sender, String message);
|
||||
|
||||
/**
|
||||
* Prints a warning message from the current plugin.
|
||||
* @param sender - the object containing the caller method.
|
||||
* @param message - error message.
|
||||
* @param error - the exception that was thrown.
|
||||
*/
|
||||
public abstract void reportWarning(Object sender, String message, Throwable error);
|
||||
|
||||
/**
|
||||
* Prints a detailed error report about an unhandled exception.
|
||||
* @param sender - the object containing the caller method.
|
||||
* @param message - an error message to include.
|
||||
* @param error - the exception that was thrown in the caller method.
|
||||
* @param parameters - parameters from the caller method.
|
||||
*/
|
||||
public abstract void reportDetailed(Object sender, String message, Throwable error, Object... parameters);
|
||||
|
||||
}
|
||||
@@ -1,161 +0,0 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol.events;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import com.comphenix.protocol.injector.GamePhase;
|
||||
import com.google.common.base.Objects;
|
||||
import com.google.common.collect.Sets;
|
||||
|
||||
/**
|
||||
* Determines which packets will be observed by a listener, and with what priority.
|
||||
|
||||
* @author Kristian
|
||||
*/
|
||||
public class ListeningWhitelist {
|
||||
|
||||
/**
|
||||
* A whitelist with no packets - indicates that the listener shouldn't observe any packets.
|
||||
*/
|
||||
public static final ListeningWhitelist EMPTY_WHITELIST = new ListeningWhitelist(ListenerPriority.LOW);
|
||||
|
||||
private ListenerPriority priority;
|
||||
private Set<Integer> whitelist;
|
||||
private GamePhase gamePhase;
|
||||
|
||||
/**
|
||||
* Creates a packet whitelist for a given priority with a set of packet IDs.
|
||||
* @param priority - the listener priority.
|
||||
* @param whitelist - set of IDs to observe/enable.
|
||||
*/
|
||||
public ListeningWhitelist(ListenerPriority priority, Set<Integer> whitelist) {
|
||||
this(priority, whitelist, GamePhase.PLAYING);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a packet whitelist for a given priority with a set of packet IDs.
|
||||
* @param priority - the listener priority.
|
||||
* @param whitelist - set of IDs to observe/enable.
|
||||
* @param gamePhase - which game phase to receieve notifications on.
|
||||
*/
|
||||
public ListeningWhitelist(ListenerPriority priority, Set<Integer> whitelist, GamePhase gamePhase) {
|
||||
this.priority = priority;
|
||||
this.whitelist = whitelist;
|
||||
this.gamePhase = gamePhase;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a packet whitelist of a given priority for a list of packets.
|
||||
* @param priority - the listener priority.
|
||||
* @param whitelist - list of packet IDs to observe/enable.
|
||||
*/
|
||||
public ListeningWhitelist(ListenerPriority priority, Integer... whitelist) {
|
||||
this.priority = priority;
|
||||
this.whitelist = Sets.newHashSet(whitelist);
|
||||
this.gamePhase = GamePhase.PLAYING;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a packet whitelist for a given priority with a set of packet IDs.
|
||||
* @param priority - the listener priority.
|
||||
* @param whitelist - list of packet IDs to observe/enable.
|
||||
* @param gamePhase - which game phase to receieve notifications on.
|
||||
*/
|
||||
public ListeningWhitelist(ListenerPriority priority, Integer[] whitelist, GamePhase gamePhase) {
|
||||
this.priority = priority;
|
||||
this.whitelist = Sets.newHashSet(whitelist);
|
||||
this.gamePhase = gamePhase;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not this whitelist has any enabled packets.
|
||||
* @return TRUE if there are any packets, FALSE otherwise.
|
||||
*/
|
||||
public boolean isEnabled() {
|
||||
return whitelist != null && whitelist.size() > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the priority in the execution order of the packet listener. Highest priority will be executed last.
|
||||
* @return Execution order in terms of priority.
|
||||
*/
|
||||
public ListenerPriority getPriority() {
|
||||
return priority;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the list of packets that will be observed by the listeners.
|
||||
* @return Packet whitelist.
|
||||
*/
|
||||
public Set<Integer> getWhitelist() {
|
||||
return whitelist;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve which game phase this listener is active under.
|
||||
* @return The active game phase.
|
||||
*/
|
||||
public GamePhase getGamePhase() {
|
||||
return gamePhase;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode(){
|
||||
return Objects.hashCode(priority, whitelist, gamePhase);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if any of the given IDs can be found in the whitelist.
|
||||
* @param whitelist - whitelist to test.
|
||||
* @param idList - list of packet IDs to find.
|
||||
* @return TRUE if any of the packets in the list can be found in the whitelist, FALSE otherwise.
|
||||
*/
|
||||
public static boolean containsAny(ListeningWhitelist whitelist, int... idList) {
|
||||
if (whitelist != null) {
|
||||
for (int i = 0; i < idList.length; i++) {
|
||||
if (whitelist.getWhitelist().contains(idList[i]))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object obj){
|
||||
if(obj instanceof ListeningWhitelist){
|
||||
final ListeningWhitelist other = (ListeningWhitelist) obj;
|
||||
return Objects.equal(priority, other.priority)
|
||||
&& Objects.equal(whitelist, other.whitelist);
|
||||
} else{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (this == EMPTY_WHITELIST)
|
||||
return "EMPTY_WHITELIST";
|
||||
else
|
||||
return Objects.toStringHelper(this)
|
||||
.add("priority", priority)
|
||||
.add("packets", whitelist).toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
package com.comphenix.protocol.events;
|
||||
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import com.comphenix.protocol.Packets;
|
||||
import com.comphenix.protocol.events.ConnectionSide;
|
||||
import com.comphenix.protocol.events.ListenerPriority;
|
||||
import com.comphenix.protocol.events.ListeningWhitelist;
|
||||
import com.comphenix.protocol.events.PacketListener;
|
||||
import com.comphenix.protocol.injector.GamePhase;
|
||||
import com.comphenix.protocol.reflect.FieldAccessException;
|
||||
|
||||
/**
|
||||
* Represents a listener that is notified of every sent and recieved packet.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public abstract class MonitorAdapter implements PacketListener {
|
||||
|
||||
private Plugin plugin;
|
||||
private ListeningWhitelist sending = ListeningWhitelist.EMPTY_WHITELIST;
|
||||
private ListeningWhitelist receiving = ListeningWhitelist.EMPTY_WHITELIST;
|
||||
|
||||
public MonitorAdapter(Plugin plugin, ConnectionSide side) {
|
||||
initialize(plugin, side, getLogger(plugin));
|
||||
}
|
||||
|
||||
public MonitorAdapter(Plugin plugin, ConnectionSide side, Logger logger) {
|
||||
initialize(plugin, side, logger);
|
||||
}
|
||||
|
||||
private void initialize(Plugin plugin, ConnectionSide side, Logger logger) {
|
||||
this.plugin = plugin;
|
||||
|
||||
// Recover in case something goes wrong
|
||||
try {
|
||||
if (side.isForServer())
|
||||
this.sending = new ListeningWhitelist(ListenerPriority.MONITOR, Packets.Server.getSupported(), GamePhase.BOTH);
|
||||
if (side.isForClient())
|
||||
this.receiving = new ListeningWhitelist(ListenerPriority.MONITOR, Packets.Client.getSupported(), GamePhase.BOTH);
|
||||
} catch (FieldAccessException e) {
|
||||
if (side.isForServer())
|
||||
this.sending = new ListeningWhitelist(ListenerPriority.MONITOR, Packets.Server.getRegistry().values(), GamePhase.BOTH);
|
||||
if (side.isForClient())
|
||||
this.receiving = new ListeningWhitelist(ListenerPriority.MONITOR, Packets.Client.getRegistry().values(), GamePhase.BOTH);
|
||||
logger.log(Level.WARNING, "Defaulting to 1.3 packets.", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a logger, even if we're running in a CraftBukkit version that doesn't support it.
|
||||
* @param plugin - the plugin to retrieve.
|
||||
* @return The logger.
|
||||
*/
|
||||
private Logger getLogger(Plugin plugin) {
|
||||
try {
|
||||
return plugin.getLogger();
|
||||
} catch (NoSuchMethodError e) {
|
||||
return Logger.getLogger("Minecraft");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListeningWhitelist getSendingWhitelist() {
|
||||
return sending;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListeningWhitelist getReceivingWhitelist() {
|
||||
return receiving;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Plugin getPlugin() {
|
||||
return plugin;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,205 +0,0 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol.events;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import com.comphenix.protocol.injector.GamePhase;
|
||||
|
||||
/**
|
||||
* Represents a packet listener with useful constructors.
|
||||
* <p>
|
||||
* Remember to override onPacketReceiving() and onPacketSending(), depending on the ConnectionSide.
|
||||
* @author Kristian
|
||||
*/
|
||||
public abstract class PacketAdapter implements PacketListener {
|
||||
|
||||
protected Plugin plugin;
|
||||
protected ConnectionSide connectionSide;
|
||||
protected ListeningWhitelist receivingWhitelist = ListeningWhitelist.EMPTY_WHITELIST;
|
||||
protected ListeningWhitelist sendingWhitelist = ListeningWhitelist.EMPTY_WHITELIST;
|
||||
|
||||
/**
|
||||
* Initialize a packet listener with default priority.
|
||||
* @param plugin - the plugin that spawned this listener.
|
||||
* @param connectionSide - the packet type the listener is looking for.
|
||||
* @param packets - the packet IDs the listener is looking for.
|
||||
*/
|
||||
public PacketAdapter(Plugin plugin, ConnectionSide connectionSide, Integer... packets) {
|
||||
this(plugin, connectionSide, ListenerPriority.NORMAL, packets);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a packet listener for a single connection side.
|
||||
* @param plugin - the plugin that spawned this listener.
|
||||
* @param connectionSide - the packet type the listener is looking for.
|
||||
* @param listenerPriority - the event priority.
|
||||
* @param packets - the packet IDs the listener is looking for.
|
||||
*/
|
||||
public PacketAdapter(Plugin plugin, ConnectionSide connectionSide, ListenerPriority listenerPriority, Set<Integer> packets) {
|
||||
this(plugin, connectionSide, listenerPriority, GamePhase.PLAYING, packets.toArray(new Integer[0]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a packet listener for a single connection side.
|
||||
* <p>
|
||||
* The game phase is used to optmize performance. A listener should only choose BOTH or LOGIN if it's absolutely necessary.
|
||||
* @param plugin - the plugin that spawned this listener.
|
||||
* @param connectionSide - the packet type the listener is looking for.
|
||||
* @param gamePhase - which game phase this listener is active under.
|
||||
* @param packets - the packet IDs the listener is looking for.
|
||||
*/
|
||||
public PacketAdapter(Plugin plugin, ConnectionSide connectionSide, GamePhase gamePhase, Set<Integer> packets) {
|
||||
this(plugin, connectionSide, ListenerPriority.NORMAL, gamePhase, packets.toArray(new Integer[0]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a packet listener for a single connection side.
|
||||
* <p>
|
||||
* The game phase is used to optmize performance. A listener should only choose BOTH or LOGIN if it's absolutely necessary.
|
||||
* @param plugin - the plugin that spawned this listener.
|
||||
* @param connectionSide - the packet type the listener is looking for.
|
||||
* @param listenerPriority - the event priority.
|
||||
* @param gamePhase - which game phase this listener is active under.
|
||||
* @param packets - the packet IDs the listener is looking for.
|
||||
*/
|
||||
public PacketAdapter(Plugin plugin, ConnectionSide connectionSide, ListenerPriority listenerPriority, GamePhase gamePhase, Set<Integer> packets) {
|
||||
this(plugin, connectionSide, listenerPriority, gamePhase, packets.toArray(new Integer[0]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a packet listener for a single connection side.
|
||||
* @param plugin - the plugin that spawned this listener.
|
||||
* @param connectionSide - the packet type the listener is looking for.
|
||||
* @param listenerPriority - the event priority.
|
||||
* @param packets - the packet IDs the listener is looking for.
|
||||
*/
|
||||
public PacketAdapter(Plugin plugin, ConnectionSide connectionSide, ListenerPriority listenerPriority, Integer... packets) {
|
||||
this(plugin, connectionSide, listenerPriority, GamePhase.PLAYING, packets);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a packet listener for a single connection side.
|
||||
* @param plugin - the plugin that spawned this listener.
|
||||
* @param connectionSide - the packet type the listener is looking for.
|
||||
* @param listenerPriority - the event priority.
|
||||
* @param gamePhase - which game phase this listener is active under.
|
||||
* @param packets - the packet IDs the listener is looking for.
|
||||
*/
|
||||
public PacketAdapter(Plugin plugin, ConnectionSide connectionSide, GamePhase gamePhase, Integer... packets) {
|
||||
this(plugin, connectionSide, ListenerPriority.NORMAL, GamePhase.PLAYING, packets);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a packet listener for a single connection side.
|
||||
* <p>
|
||||
* The game phase is used to optmize performance. A listener should only choose BOTH or LOGIN if it's absolutely necessary.
|
||||
* @param plugin - the plugin that spawned this listener.
|
||||
* @param connectionSide - the packet type the listener is looking for.
|
||||
* @param listenerPriority - the event priority.
|
||||
* @param gamePhase - which game phase this listener is active under.
|
||||
* @param packets - the packet IDs the listener is looking for.
|
||||
*/
|
||||
public PacketAdapter(Plugin plugin, ConnectionSide connectionSide, ListenerPriority listenerPriority, GamePhase gamePhase, Integer... packets) {
|
||||
if (plugin == null)
|
||||
throw new IllegalArgumentException("plugin cannot be null");
|
||||
if (connectionSide == null)
|
||||
throw new IllegalArgumentException("connectionSide cannot be null");
|
||||
if (listenerPriority == null)
|
||||
throw new IllegalArgumentException("listenerPriority cannot be null");
|
||||
if (gamePhase == null)
|
||||
throw new IllegalArgumentException("gamePhase cannot be NULL");
|
||||
if (packets == null)
|
||||
throw new IllegalArgumentException("packets cannot be null");
|
||||
|
||||
// Add whitelists
|
||||
if (connectionSide.isForServer())
|
||||
sendingWhitelist = new ListeningWhitelist(listenerPriority, packets, gamePhase);
|
||||
if (connectionSide.isForClient())
|
||||
receivingWhitelist = new ListeningWhitelist(listenerPriority, packets, gamePhase);
|
||||
|
||||
this.plugin = plugin;
|
||||
this.connectionSide = connectionSide;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketReceiving(PacketEvent event) {
|
||||
// Lets prevent some bugs
|
||||
throw new IllegalStateException("Override onPacketReceiving to get notifcations of received packets!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketSending(PacketEvent event) {
|
||||
// Lets prevent some bugs
|
||||
throw new IllegalStateException("Override onPacketSending to get notifcations of sent packets!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListeningWhitelist getReceivingWhitelist() {
|
||||
return receivingWhitelist;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListeningWhitelist getSendingWhitelist() {
|
||||
return sendingWhitelist;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Plugin getPlugin() {
|
||||
return plugin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the name of the plugin that has been associated with the listener.
|
||||
* @param listener - the listener.
|
||||
* @return Name of the associated plugin.
|
||||
*/
|
||||
public static String getPluginName(PacketListener listener) {
|
||||
return getPluginName(listener.getPlugin());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the name of the given plugin.
|
||||
* @param plugin - the plugin.
|
||||
* @return Name of the given plugin.
|
||||
*/
|
||||
public static String getPluginName(Plugin plugin) {
|
||||
|
||||
// Try to get the plugin name
|
||||
try {
|
||||
if (plugin == null)
|
||||
return "UNKNOWN";
|
||||
else
|
||||
return plugin.getName();
|
||||
|
||||
} catch (NoSuchMethodError e) {
|
||||
return plugin.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
// This is used by the error reporter
|
||||
return String.format("PacketAdapter[plugin=%s, sending=%s, receiving=%s]",
|
||||
getPluginName(this),
|
||||
sendingWhitelist,
|
||||
receivingWhitelist);
|
||||
}
|
||||
}
|
||||
@@ -1,413 +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.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.WorldType;
|
||||
import org.bukkit.craftbukkit.CraftWorld;
|
||||
import org.bukkit.craftbukkit.inventory.CraftItemStack;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import com.comphenix.protocol.injector.StructureCache;
|
||||
import com.comphenix.protocol.reflect.EquivalentConverter;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.comphenix.protocol.reflect.StructureModifier;
|
||||
|
||||
import net.minecraft.server.Packet;
|
||||
|
||||
/**
|
||||
* Represents a Minecraft packet indirectly.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public class PacketContainer implements Serializable {
|
||||
|
||||
/**
|
||||
* Generated by Eclipse.
|
||||
*/
|
||||
private static final long serialVersionUID = 2074805748222377230L;
|
||||
|
||||
protected int id;
|
||||
protected transient Packet handle;
|
||||
|
||||
// Current structure modifier
|
||||
protected transient StructureModifier<Object> structureModifier;
|
||||
|
||||
// Check whether or not certain classes exists
|
||||
private static boolean hasWorldType = false;
|
||||
|
||||
// The getEntity method
|
||||
private static Method getEntity;
|
||||
|
||||
// Support for serialization
|
||||
private static Method writeMethod;
|
||||
private static Method readMethod;
|
||||
|
||||
static {
|
||||
try {
|
||||
Class.forName("net.minecraft.server.WorldType");
|
||||
hasWorldType = true;
|
||||
} catch (ClassNotFoundException e) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a packet container for a new packet.
|
||||
* @param id - ID of the packet to create.
|
||||
*/
|
||||
public PacketContainer(int id) {
|
||||
this(id, StructureCache.newPacket(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a packet container for an existing packet.
|
||||
* @param id - ID of the given packet.
|
||||
* @param handle - contained packet.
|
||||
*/
|
||||
public PacketContainer(int id, Packet handle) {
|
||||
this(id, handle, StructureCache.getStructure(id).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(int id, Packet handle, StructureModifier<Object> structure) {
|
||||
if (handle == null)
|
||||
throw new IllegalArgumentException("handle cannot be null.");
|
||||
|
||||
this.id = id;
|
||||
this.handle = handle;
|
||||
this.structureModifier = structure;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the underlying Minecraft packet.
|
||||
* @return Underlying Minecraft packet.
|
||||
*/
|
||||
public Packet 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 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(net.minecraft.server.ItemStack.class,
|
||||
getIgnoreNull(new EquivalentConverter<ItemStack>() {
|
||||
public Object getGeneric(ItemStack specific) {
|
||||
return toStackNMS(specific);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack getSpecific(Object generic) {
|
||||
return new CraftItemStack((net.minecraft.server.ItemStack) generic);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<ItemStack> getSpecificType() {
|
||||
return ItemStack.class;
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(
|
||||
net.minecraft.server.ItemStack[].class,
|
||||
getIgnoreNull(new EquivalentConverter<ItemStack[]>() {
|
||||
|
||||
public Object getGeneric(ItemStack[] specific) {
|
||||
net.minecraft.server.ItemStack[] result = new net.minecraft.server.ItemStack[specific.length];
|
||||
|
||||
// Unwrap every item
|
||||
for (int i = 0; i < result.length; i++) {
|
||||
result[i] = toStackNMS(specific[i]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack[] getSpecific(Object generic) {
|
||||
net.minecraft.server.ItemStack[] input = (net.minecraft.server.ItemStack[]) generic;
|
||||
ItemStack[] result = new ItemStack[input.length];
|
||||
|
||||
// Add the wrapper
|
||||
for (int i = 0; i < result.length; i++) {
|
||||
result[i] = new CraftItemStack(input[i]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<ItemStack[]> getSpecificType() {
|
||||
return ItemStack[].class;
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an item stack to the NMS equivalent.
|
||||
* @param stack - Bukkit stack to convert.
|
||||
* @return A bukkit stack.
|
||||
*/
|
||||
private net.minecraft.server.ItemStack toStackNMS(ItemStack stack) {
|
||||
// We must be prepared for an object that simply implements ItemStcak
|
||||
if (stack instanceof CraftItemStack) {
|
||||
return ((CraftItemStack) stack).getHandle();
|
||||
} else {
|
||||
return (new CraftItemStack(stack)).getHandle();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
|
||||
if (!hasWorldType) {
|
||||
// We couldn't find the Minecraft equivalent
|
||||
return structureModifier.withType(null);
|
||||
}
|
||||
|
||||
// Convert to and from the Bukkit wrapper
|
||||
return structureModifier.<WorldType>withType(
|
||||
net.minecraft.server.WorldType.class,
|
||||
getIgnoreNull(new EquivalentConverter<WorldType>() {
|
||||
|
||||
@Override
|
||||
public Object getGeneric(WorldType specific) {
|
||||
return net.minecraft.server.WorldType.getType(specific.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public WorldType getSpecific(Object generic) {
|
||||
net.minecraft.server.WorldType type = (net.minecraft.server.WorldType) generic;
|
||||
return WorldType.getByName(type.name());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<WorldType> getSpecificType() {
|
||||
return WorldType.class;
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @return A modifier entity types.
|
||||
*/
|
||||
public StructureModifier<Entity> getEntityModifier(World world) {
|
||||
|
||||
final Object worldServer = ((CraftWorld) world).getHandle();
|
||||
final Class<?> nmsEntityClass = net.minecraft.server.Entity.class;
|
||||
final World worldCopy = world;
|
||||
|
||||
if (getEntity == null)
|
||||
getEntity = FuzzyReflection.fromObject(worldServer).getMethodByParameters(
|
||||
"getEntity", nmsEntityClass, new Class[] { int.class });
|
||||
|
||||
// Convert to and from the Bukkit wrapper
|
||||
return structureModifier.<Entity>withType(
|
||||
int.class,
|
||||
getIgnoreNull(new EquivalentConverter<Entity>() {
|
||||
|
||||
@Override
|
||||
public Object getGeneric(Entity specific) {
|
||||
// Simple enough
|
||||
return specific.getEntityId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Entity getSpecific(Object generic) {
|
||||
try {
|
||||
net.minecraft.server.Entity nmsEntity = (net.minecraft.server.Entity)
|
||||
getEntity.invoke(worldServer, generic);
|
||||
Integer id = (Integer) generic;
|
||||
|
||||
// Attempt to get the Bukkit entity
|
||||
if (nmsEntity != null) {
|
||||
return nmsEntity.getBukkitEntity();
|
||||
} else {
|
||||
// Maybe it's a player that's just logged in? Try a search
|
||||
for (Player player : worldCopy.getPlayers()) {
|
||||
if (player.getEntityId() == id) {
|
||||
return player;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new RuntimeException("Incorrect arguments detected.", e);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException("Cannot read field due to a security limitation.", e);
|
||||
} catch (InvocationTargetException e) {
|
||||
throw new RuntimeException("Error occured in Minecraft method.", e.getCause());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<Entity> getSpecificType() {
|
||||
return Entity.class;
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private <TType> EquivalentConverter<TType> getIgnoreNull(final EquivalentConverter<TType> delegate) {
|
||||
// Automatically wrap all parameters to the delegate with a NULL check
|
||||
return new EquivalentConverter<TType>() {
|
||||
public Object getGeneric(TType specific) {
|
||||
if (specific != null)
|
||||
return delegate.getGeneric(specific);
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TType getSpecific(Object generic) {
|
||||
if (generic != null)
|
||||
return delegate.getSpecific(generic);
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<TType> getSpecificType() {
|
||||
return delegate.getSpecificType();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the ID of this packet.
|
||||
* @return Packet ID.
|
||||
*/
|
||||
public int getID() {
|
||||
return id;
|
||||
}
|
||||
|
||||
private void writeObject(ObjectOutputStream output) throws IOException {
|
||||
// Default serialization
|
||||
output.defaultWriteObject();
|
||||
|
||||
// We'll take care of NULL packets as well
|
||||
output.writeBoolean(handle != null);
|
||||
|
||||
// Retrieve the write method by reflection
|
||||
if (writeMethod == null)
|
||||
writeMethod = FuzzyReflection.fromObject(handle).getMethodByParameters("write", DataOutputStream.class);
|
||||
|
||||
try {
|
||||
// Call the write-method
|
||||
writeMethod.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(id);
|
||||
|
||||
// Don't read NULL packets
|
||||
if (input.readBoolean()) {
|
||||
|
||||
// Create a default instance of the packet
|
||||
handle = StructureCache.newPacket(id);
|
||||
|
||||
// Retrieve the read method by reflection
|
||||
if (readMethod == null)
|
||||
readMethod = FuzzyReflection.fromObject(handle).getMethodByParameters("read", DataInputStream.class);
|
||||
|
||||
// Call the read method
|
||||
try {
|
||||
readMethod.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,214 +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.util.EventObject;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.Cancellable;
|
||||
|
||||
import com.comphenix.protocol.async.AsyncMarker;
|
||||
|
||||
public class PacketEvent extends EventObject implements Cancellable {
|
||||
/**
|
||||
* Automatically generated by Eclipse.
|
||||
*/
|
||||
private static final long serialVersionUID = -5360289379097430620L;
|
||||
|
||||
private transient Player player;
|
||||
private PacketContainer packet;
|
||||
private boolean serverPacket;
|
||||
private boolean cancel;
|
||||
|
||||
private AsyncMarker asyncMarker;
|
||||
private boolean asynchronous;
|
||||
|
||||
/**
|
||||
* Use the static constructors to create instances of this event.
|
||||
* @param source - the event source.
|
||||
*/
|
||||
public PacketEvent(Object source) {
|
||||
super(source);
|
||||
}
|
||||
|
||||
private PacketEvent(Object source, PacketContainer packet, Player player, boolean serverPacket) {
|
||||
super(source);
|
||||
this.packet = packet;
|
||||
this.player = player;
|
||||
this.serverPacket = serverPacket;
|
||||
}
|
||||
|
||||
private PacketEvent(PacketEvent origial, AsyncMarker asyncMarker) {
|
||||
super(origial.source);
|
||||
this.packet = origial.packet;
|
||||
this.player = origial.player;
|
||||
this.cancel = origial.cancel;
|
||||
this.serverPacket = origial.serverPacket;
|
||||
this.asyncMarker = asyncMarker;
|
||||
this.asynchronous = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an event representing a client packet transmission.
|
||||
* @param source - the event source.
|
||||
* @param packet - the packet.
|
||||
* @param client - the client that sent the packet.
|
||||
* @return The event.
|
||||
*/
|
||||
public static PacketEvent fromClient(Object source, PacketContainer packet, Player client) {
|
||||
return new PacketEvent(source, packet, client, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an event representing a server packet transmission.
|
||||
* @param source - the event source.
|
||||
* @param packet - the packet.
|
||||
* @param recipient - the client that will receieve the packet.
|
||||
* @return The event.
|
||||
*/
|
||||
public static PacketEvent fromServer(Object source, PacketContainer packet, Player recipient) {
|
||||
return new PacketEvent(source, packet, recipient, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an asynchronous packet event from a synchronous event and a async marker.
|
||||
* @param event - the original synchronous event.
|
||||
* @param marker - the asynchronous marker.
|
||||
* @return The new packet event.
|
||||
*/
|
||||
public static PacketEvent fromSynchronous(PacketEvent event, AsyncMarker marker) {
|
||||
return new PacketEvent(event, marker);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the packet that will be sent to the player.
|
||||
* @return Packet to send to the player.
|
||||
*/
|
||||
public PacketContainer getPacket() {
|
||||
return packet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace the packet that will be sent to the player.
|
||||
* @param packet - the packet that will be sent instead.
|
||||
*/
|
||||
public void setPacket(PacketContainer packet) {
|
||||
this.packet = packet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the packet ID.
|
||||
* @return The current packet ID.
|
||||
*/
|
||||
public int getPacketID() {
|
||||
return packet.getID();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves whether or not the packet should be cancelled.
|
||||
* @return TRUE if it should be cancelled, FALSE otherwise.
|
||||
*/
|
||||
public boolean isCancelled() {
|
||||
return cancel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether or not the packet should be cancelled.
|
||||
* @param cancel - TRUE if it should be cancelled, FALSE otherwise.
|
||||
*/
|
||||
public void setCancelled(boolean cancel) {
|
||||
this.cancel = cancel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the player that has sent the packet or is recieving it.
|
||||
* @return The player associated with this event.
|
||||
*/
|
||||
public Player getPlayer() {
|
||||
return player;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not this packet was created by the server.
|
||||
* <p>
|
||||
* Most listeners can deduce this by noting which listener method was invoked.
|
||||
* @return TRUE if the packet was created by the server, FALSE if it was created by a client.
|
||||
*/
|
||||
public boolean isServerPacket() {
|
||||
return serverPacket;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the asynchronous marker.
|
||||
* <p>
|
||||
* If the packet is synchronous, this marker will be used to schedule an asynchronous event. In the following
|
||||
* asynchronous event, the marker is used to correctly pass the packet around to the different threads.
|
||||
* <p>
|
||||
* Note that if there are no asynchronous events that can receive this packet, the marker is NULL.
|
||||
* @return The current asynchronous marker, or NULL.
|
||||
*/
|
||||
public AsyncMarker getAsyncMarker() {
|
||||
return asyncMarker;
|
||||
}
|
||||
/**
|
||||
* Set the asynchronous marker.
|
||||
* <p>
|
||||
* If the marker is non-null at the end of an synchronous event processing, the packet will be scheduled
|
||||
* to be processed asynchronously with the given settings.
|
||||
* <p>
|
||||
* Note that if there are no asynchronous events that can receive this packet, the marker should be NULL.
|
||||
* @param asyncMarker - the new asynchronous marker, or NULL.
|
||||
* @throws IllegalStateException If the current event is asynchronous.
|
||||
*/
|
||||
public void setAsyncMarker(AsyncMarker asyncMarker) {
|
||||
if (isAsynchronous())
|
||||
throw new IllegalStateException("The marker is immutable for asynchronous events");
|
||||
this.asyncMarker = asyncMarker;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the packet event has been executed asynchronously or not.
|
||||
* @return TRUE if this packet event is asynchronous, FALSE otherwise.
|
||||
*/
|
||||
public boolean isAsynchronous() {
|
||||
return asynchronous;
|
||||
}
|
||||
|
||||
private void writeObject(ObjectOutputStream output) throws IOException {
|
||||
// Default serialization
|
||||
output.defaultWriteObject();
|
||||
|
||||
// Write the name of the player (or NULL if it's not set)
|
||||
output.writeObject(player != null ? new SerializedOfflinePlayer(player) : null);
|
||||
}
|
||||
|
||||
private void readObject(ObjectInputStream input) throws ClassNotFoundException, IOException {
|
||||
// Default deserialization
|
||||
input.defaultReadObject();
|
||||
|
||||
final SerializedOfflinePlayer offlinePlayer = (SerializedOfflinePlayer) input.readObject();
|
||||
|
||||
if (offlinePlayer != null) {
|
||||
// Better than nothing
|
||||
player = offlinePlayer.getPlayer();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,233 +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.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;
|
||||
|
||||
/**
|
||||
* 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 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.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 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 = new Enhancer();
|
||||
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,115 +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.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import com.comphenix.protocol.injector.PacketConstructor.Unwrapper;
|
||||
import com.comphenix.protocol.reflect.instances.DefaultInstances;
|
||||
|
||||
/**
|
||||
* Represents an object capable of converting wrapped Bukkit objects into NMS objects.
|
||||
* <p>
|
||||
* Typical conversions include:
|
||||
* <ul>
|
||||
* <li>org.bukkit.entity.Player -> net.minecraft.server.EntityPlayer</li>
|
||||
* <li>org.bukkit.World -> net.minecraft.server.WorldServer</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public class BukkitUnwrapper implements Unwrapper {
|
||||
|
||||
private static Map<Class<?>, Method> cache = new ConcurrentHashMap<Class<?>, Method>();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Object unwrapItem(Object wrappedObject) {
|
||||
|
||||
// Special cases
|
||||
if (wrappedObject == null) {
|
||||
return null;
|
||||
} else if (wrappedObject instanceof Collection) {
|
||||
return handleCollection((Collection<Object>) wrappedObject);
|
||||
}
|
||||
|
||||
Class<?> currentClass = wrappedObject.getClass();
|
||||
Method cachedMethod = initializeCache(currentClass);
|
||||
|
||||
try {
|
||||
// Retrieve the handle
|
||||
if (cachedMethod != null)
|
||||
return cachedMethod.invoke(wrappedObject);
|
||||
else
|
||||
return null;
|
||||
|
||||
} catch (IllegalArgumentException e) {
|
||||
// Impossible
|
||||
return null;
|
||||
} catch (IllegalAccessException e) {
|
||||
return null;
|
||||
} catch (InvocationTargetException e) {
|
||||
// This is REALLY bad
|
||||
throw new RuntimeException("Minecraft error.", e);
|
||||
}
|
||||
}
|
||||
|
||||
private Object handleCollection(Collection<Object> wrappedObject) {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Collection<Object> copy = DefaultInstances.DEFAULT.getDefault(wrappedObject.getClass());
|
||||
|
||||
if (copy != null) {
|
||||
// Unwrap every element
|
||||
for (Object element : wrappedObject) {
|
||||
copy.add(unwrapItem(element));
|
||||
}
|
||||
return copy;
|
||||
|
||||
} else {
|
||||
// Impossible
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private Method initializeCache(Class<?> type) {
|
||||
|
||||
// See if we're already determined this
|
||||
if (cache.containsKey(type)) {
|
||||
// We will never remove from the cache, so this ought to be thread safe
|
||||
return cache.get(type);
|
||||
}
|
||||
|
||||
try {
|
||||
Method find = type.getMethod("getHandle");
|
||||
|
||||
// It's thread safe, as getMethod should return the same handle
|
||||
cache.put(type, find);
|
||||
return find;
|
||||
|
||||
} catch (SecurityException e) {
|
||||
return null;
|
||||
} catch (NoSuchMethodException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,132 +0,0 @@
|
||||
package com.comphenix.protocol.injector;
|
||||
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.bukkit.scheduler.BukkitScheduler;
|
||||
|
||||
/**
|
||||
* Represents a single delayed task.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public class DelayedSingleTask {
|
||||
|
||||
protected int taskID = -1;
|
||||
protected Plugin plugin;
|
||||
protected BukkitScheduler scheduler;
|
||||
protected boolean closed;
|
||||
|
||||
/**
|
||||
* Create a single task scheduler.
|
||||
* @param plugin - owner plugin.
|
||||
*/
|
||||
public DelayedSingleTask(Plugin plugin) {
|
||||
this.plugin = plugin;
|
||||
this.scheduler = plugin.getServer().getScheduler();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a single task scheduler.
|
||||
* @param plugin - owner plugin.
|
||||
* @param scheduler - specialized scheduler.
|
||||
*/
|
||||
public DelayedSingleTask(Plugin plugin, BukkitScheduler scheduler) {
|
||||
this.plugin = plugin;
|
||||
this.scheduler = scheduler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule a single task for execution.
|
||||
* <p>
|
||||
* Any previously scheduled task will be automatically cancelled.
|
||||
* <p>
|
||||
* Note that a tick delay of zero will execute the task immediately.
|
||||
*
|
||||
* @param ticksDelay - number of ticks before the task is executed.
|
||||
* @param task - the task to schedule.
|
||||
* @return TRUE if the task was successfully scheduled or executed, FALSE otherwise.
|
||||
*/
|
||||
public boolean schedule(long ticksDelay, Runnable task) {
|
||||
if (ticksDelay < 0)
|
||||
throw new IllegalArgumentException("Tick delay cannot be negative.");
|
||||
if (task == null)
|
||||
throw new IllegalArgumentException("task cannot be NULL");
|
||||
if (closed)
|
||||
return false;
|
||||
|
||||
// Special case
|
||||
if (ticksDelay == 0) {
|
||||
task.run();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Boilerplate, boilerplate
|
||||
final Runnable dispatch = task;
|
||||
|
||||
// Don't run multiple tasks!
|
||||
cancel();
|
||||
taskID = scheduler.scheduleSyncDelayedTask(plugin, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
dispatch.run();
|
||||
taskID = -1;
|
||||
}
|
||||
}, ticksDelay);
|
||||
|
||||
return isRunning();
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not a future task is scheduled to be executed.
|
||||
* @return TRUE if a current task has been scheduled for execution, FALSE otherwise.
|
||||
*/
|
||||
public boolean isRunning() {
|
||||
return taskID >= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel a future task from being executed.
|
||||
* @return TRUE if a task was cancelled, FALSE otherwise.
|
||||
*/
|
||||
public boolean cancel() {
|
||||
if (isRunning()) {
|
||||
scheduler.cancelTask(taskID);
|
||||
taskID = -1;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the raw task ID.
|
||||
* @return Raw task ID, or negative one if no task has been scheduled.
|
||||
*/
|
||||
public int getTaskID() {
|
||||
return taskID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the plugin this task belongs to.
|
||||
* @return The plugin scheduling the current taks.
|
||||
*/
|
||||
public Plugin getPlugin() {
|
||||
return plugin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the current task and all future tasks scheduled by this instance.
|
||||
*/
|
||||
public synchronized void close() {
|
||||
if (!closed) {
|
||||
cancel();
|
||||
plugin = null;
|
||||
scheduler = null;
|
||||
closed = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void finalize() throws Throwable {
|
||||
close();
|
||||
}
|
||||
}
|
||||
@@ -1,206 +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.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.craftbukkit.CraftWorld;
|
||||
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.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 Method hashGetMethod;
|
||||
private static Method scanPlayersMethod;
|
||||
|
||||
/*
|
||||
* While this function may look pretty bad, it's essentially just a reflection-warped
|
||||
* version of the following:
|
||||
*
|
||||
* @SuppressWarnings("unchecked")
|
||||
* public static void updateEntity2(Entity entity, List<Player> observers) {
|
||||
*
|
||||
* World world = entity.getWorld();
|
||||
* WorldServer worldServer = ((CraftWorld) world).getHandle();
|
||||
*
|
||||
* EntityTracker tracker = worldServer.tracker;
|
||||
* EntityTrackerEntry entry = (EntityTrackerEntry) tracker.trackedEntities.get(entity.getEntityId());
|
||||
*
|
||||
* List<EntityPlayer> nmsPlayers = getNmsPlayers(observers);
|
||||
*
|
||||
* entry.trackedPlayers.removeAll(nmsPlayers);
|
||||
* entry.scanPlayers(nmsPlayers);
|
||||
* }
|
||||
*
|
||||
* private static List<EntityPlayer> getNmsPlayers(List<Player> players) {
|
||||
* List<EntityPlayer> nsmPlayers = new ArrayList<EntityPlayer>();
|
||||
*
|
||||
* for (Player bukkitPlayer : players) {
|
||||
* CraftPlayer craftPlayer = (CraftPlayer) bukkitPlayer;
|
||||
* nsmPlayers.add(craftPlayer.getHandle());
|
||||
* }
|
||||
*
|
||||
* return nsmPlayers;
|
||||
* }
|
||||
*
|
||||
*/
|
||||
public static void updateEntity(Entity entity, List<Player> observers) throws FieldAccessException {
|
||||
|
||||
World world = entity.getWorld();
|
||||
Object worldServer = ((CraftWorld) world).getHandle();
|
||||
|
||||
// We have to rely on the class naming here.
|
||||
if (entityTrackerField == null)
|
||||
entityTrackerField = FuzzyReflection.fromObject(worldServer).getFieldByType(".*Tracker");
|
||||
|
||||
// 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(FuzzyReflection.MINECRAFT_OBJECT, 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);
|
||||
}
|
||||
|
||||
// Getting the "get" method is pretty hard, but first - try to just get it by name
|
||||
if (hashGetMethod == null) {
|
||||
|
||||
Class<?> type = trackedEntities.getClass();
|
||||
|
||||
try {
|
||||
hashGetMethod = type.getMethod("get", int.class);
|
||||
} catch (NoSuchMethodException e) {
|
||||
|
||||
Class<?>[] params = { int.class };
|
||||
|
||||
// Then it's probably the lowest named method that takes an int-parameter
|
||||
for (Method method : type.getMethods()) {
|
||||
if (Arrays.equals(params, method.getParameterTypes())) {
|
||||
if (hashGetMethod == null ||
|
||||
method.getName().compareTo(hashGetMethod.getName()) < 0) {
|
||||
hashGetMethod = method;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
//EntityTrackerEntry trackEntity = (EntityTrackerEntry) tracker.trackedEntities.get(entity.getEntityId());
|
||||
Object trackerEntry = hashGetMethod.invoke(trackedEntities, entity.getEntityId());
|
||||
|
||||
if (trackedPlayersField == null) {
|
||||
// This one is fairly easy
|
||||
trackedPlayersField = FuzzyReflection.fromObject(trackerEntry).getFieldByType("java\\.util\\..*");
|
||||
}
|
||||
|
||||
// Phew, finally there.
|
||||
Collection<?> trackedPlayers = (Collection<?>) FieldUtils.readField(trackedPlayersField, trackerEntry, false);
|
||||
List<Object> nmsPlayers = unwrapBukkit(observers);
|
||||
|
||||
// trackEntity.trackedPlayers.clear();
|
||||
trackedPlayers.removeAll(nmsPlayers);
|
||||
|
||||
// We have to rely on a NAME once again. Damn it.
|
||||
if (scanPlayersMethod == null) {
|
||||
scanPlayersMethod = trackerEntry.getClass().getMethod("scanPlayers", List.class);
|
||||
}
|
||||
|
||||
//trackEntity.scanPlayers(server.players);
|
||||
scanPlayersMethod.invoke(trackerEntry, nmsPlayers);
|
||||
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw e;
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new FieldAccessException("Security limitation prevents access to 'get' method in IntHashMap", e);
|
||||
} catch (InvocationTargetException e) {
|
||||
throw new RuntimeException("Exception occurred in Minecraft.", e);
|
||||
} catch (SecurityException e) {
|
||||
throw new FieldAccessException("Security limitation prevents access to 'scanPlayers' method in trackerEntry.", e);
|
||||
} catch (NoSuchMethodException e) {
|
||||
throw new FieldAccessException("Canot find 'scanPlayers' method. Is ProtocolLib up to date?", 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,39 +0,0 @@
|
||||
package com.comphenix.protocol.injector;
|
||||
|
||||
/**
|
||||
* The current player phase. This is used to limit the number of different injections.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public enum GamePhase {
|
||||
/**
|
||||
* Only listen for packets sent or received before a player has logged in.
|
||||
*/
|
||||
LOGIN,
|
||||
|
||||
/**
|
||||
* Only listen for packets sent or received after a player has logged in.
|
||||
*/
|
||||
PLAYING,
|
||||
|
||||
/**
|
||||
* Listen for every sent and received packet.
|
||||
*/
|
||||
BOTH;
|
||||
|
||||
/**
|
||||
* Determine if the current value represents the login phase.
|
||||
* @return TRUE if it does, FALSE otherwise.
|
||||
*/
|
||||
public boolean hasLogin() {
|
||||
return this == LOGIN || this == BOTH;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the current value represents the playing phase.
|
||||
* @return TRUE if it does, FALSE otherwise.
|
||||
*/
|
||||
public boolean hasPlaying() {
|
||||
return this == PLAYING || this == BOTH;
|
||||
}
|
||||
}
|
||||
@@ -1,70 +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 net.minecraft.server.Packet;
|
||||
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public abstract int getPacketID(Packet packet);
|
||||
|
||||
/**
|
||||
* Associate a given class with the given packet ID. Internal method.
|
||||
* @param clazz - class to associate.
|
||||
* @param packetID - the packet ID.
|
||||
*/
|
||||
public abstract void unregisterPacketClass(Class<?> clazz);
|
||||
|
||||
/**
|
||||
* Remove a given class from the packet registry. Internal method.
|
||||
* @param clazz - class to remove.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
public abstract Class<?> getPacketClassFromID(int packetID, boolean forceVanilla);
|
||||
}
|
||||
@@ -1,183 +0,0 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol.injector;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import net.minecraft.server.Packet;
|
||||
|
||||
import com.comphenix.protocol.reflect.FieldAccessException;
|
||||
import com.comphenix.protocol.reflect.FieldUtils;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.google.common.base.Objects;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
|
||||
/**
|
||||
* Static registries in Minecraft.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
class MinecraftRegistry {
|
||||
|
||||
// Fuzzy reflection
|
||||
private static FuzzyReflection packetRegistry;
|
||||
|
||||
// The packet class to packet ID translator
|
||||
private static Map<Class, Integer> packetToID;
|
||||
|
||||
// Whether or not certain packets are sent by the client or the server
|
||||
private static Set<Integer> serverPackets;
|
||||
private static Set<Integer> clientPackets;
|
||||
|
||||
// New proxy values
|
||||
private static Map<Integer, Class> overwrittenPackets = new HashMap<Integer, Class>();
|
||||
|
||||
// Vanilla packets
|
||||
private static Map<Integer, Class> previousValues = new HashMap<Integer, Class>();
|
||||
|
||||
@SuppressWarnings({ "unchecked" })
|
||||
public static Map<Class, Integer> getPacketToID() {
|
||||
// Initialize it, if we haven't already
|
||||
if (packetToID == null) {
|
||||
try {
|
||||
Field packetsField = getPacketRegistry().getFieldByType("packetsField", Map.class);
|
||||
packetToID = (Map<Class, Integer>) FieldUtils.readStaticField(packetsField, true);
|
||||
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException("Unable to retrieve the packetClassToIdMap", e);
|
||||
}
|
||||
}
|
||||
|
||||
return packetToID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the cached fuzzy reflection instance allowing access to the packet registry.
|
||||
* @return Reflected packet registry.
|
||||
*/
|
||||
private static FuzzyReflection getPacketRegistry() {
|
||||
if (packetRegistry == null)
|
||||
packetRegistry = FuzzyReflection.fromClass(Packet.class, true);
|
||||
return packetRegistry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the injected proxy classes handlig each packet ID.
|
||||
* @return Injected classes.
|
||||
*/
|
||||
public static Map<Integer, Class> getOverwrittenPackets() {
|
||||
return overwrittenPackets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the vanilla classes handling each packet ID.
|
||||
* @return Vanilla classes.
|
||||
*/
|
||||
public static 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 static Set<Integer> getServerPackets() throws FieldAccessException {
|
||||
initializeSets();
|
||||
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 static Set<Integer> getClientPackets() throws FieldAccessException {
|
||||
initializeSets();
|
||||
return clientPackets;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static void initializeSets() throws FieldAccessException {
|
||||
if (serverPackets == null || clientPackets == null) {
|
||||
List<Field> sets = getPacketRegistry().getFieldListByType(Set.class);
|
||||
|
||||
try {
|
||||
if (sets.size() > 1) {
|
||||
serverPackets = (Set<Integer>) FieldUtils.readStaticField(sets.get(0), true);
|
||||
clientPackets = (Set<Integer>) FieldUtils.readStaticField(sets.get(1), true);
|
||||
|
||||
// Impossible
|
||||
if (serverPackets == null || clientPackets == null)
|
||||
throw new FieldAccessException("Packet sets are in an illegal state.");
|
||||
|
||||
// NEVER allow callers to modify the underlying sets
|
||||
serverPackets = ImmutableSet.copyOf(serverPackets);
|
||||
clientPackets = ImmutableSet.copyOf(clientPackets);
|
||||
|
||||
} else {
|
||||
throw new FieldAccessException("Cannot retrieve packet client/server sets.");
|
||||
}
|
||||
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new FieldAccessException("Cannot access field.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the correct packet class from a given packet ID.
|
||||
* @param packetID - the packet ID.
|
||||
* @return The associated class.
|
||||
*/
|
||||
public static 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 static Class getPacketClassFromID(int packetID, boolean forceVanilla) {
|
||||
|
||||
Map<Integer, Class> lookup = forceVanilla ? previousValues : overwrittenPackets;
|
||||
|
||||
// Optimized lookup
|
||||
if (lookup.containsKey(packetID)) {
|
||||
return lookup.get(packetID);
|
||||
}
|
||||
|
||||
// Will most likely not be used
|
||||
for (Map.Entry<Class, Integer> entry : getPacketToID().entrySet()) {
|
||||
if (Objects.equal(entry.getValue(), packetID)) {
|
||||
// Attempt to get the vanilla class here too
|
||||
if (!forceVanilla || entry.getKey().getName().startsWith("net.minecraft.server"))
|
||||
return entry.getKey();
|
||||
}
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("The packet ID " + packetID + " is not registered.");
|
||||
}
|
||||
}
|
||||
@@ -1,824 +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.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import net.minecraft.server.Packet;
|
||||
import net.sf.cglib.proxy.Enhancer;
|
||||
import net.sf.cglib.proxy.MethodInterceptor;
|
||||
import net.sf.cglib.proxy.MethodProxy;
|
||||
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.bukkit.event.player.PlayerQuitEvent;
|
||||
import org.bukkit.event.server.PluginDisableEvent;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.bukkit.plugin.PluginManager;
|
||||
|
||||
import com.comphenix.protocol.AsynchronousManager;
|
||||
import com.comphenix.protocol.ProtocolManager;
|
||||
import com.comphenix.protocol.async.AsyncFilterManager;
|
||||
import com.comphenix.protocol.async.AsyncMarker;
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.events.*;
|
||||
import com.comphenix.protocol.injector.player.PlayerInjectionHandler;
|
||||
import com.comphenix.protocol.reflect.FieldAccessException;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.google.common.base.Objects;
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
|
||||
public final class PacketFilterManager implements ProtocolManager, ListenerInvoker {
|
||||
|
||||
/**
|
||||
* Sets the inject hook type. Different types allow for maximum compatibility.
|
||||
* @author Kristian
|
||||
*/
|
||||
public enum PlayerInjectHooks {
|
||||
/**
|
||||
* The injection hook that does nothing. Set when every other inject hook fails.
|
||||
*/
|
||||
NONE,
|
||||
|
||||
/**
|
||||
* Override the network handler object itself. Only works in 1.3.
|
||||
* <p>
|
||||
* Cannot intercept MapChunk packets.
|
||||
*/
|
||||
NETWORK_MANAGER_OBJECT,
|
||||
|
||||
/**
|
||||
* Override the packet queue lists in NetworkHandler.
|
||||
* <p>
|
||||
* Cannot intercept MapChunk packets.
|
||||
*/
|
||||
NETWORK_HANDLER_FIELDS,
|
||||
|
||||
/**
|
||||
* Override the server handler object. Versatile, but a tad slower.
|
||||
*/
|
||||
NETWORK_SERVER_OBJECT;
|
||||
}
|
||||
|
||||
// The amount of time to wait until we actually unhook every player
|
||||
private static final int TICKS_PER_SECOND = 20;
|
||||
private static final int UNHOOK_DELAY = 5 * TICKS_PER_SECOND;
|
||||
|
||||
// Delayed unhook
|
||||
private DelayedSingleTask unhookTask;
|
||||
|
||||
// Create a concurrent set
|
||||
private Set<PacketListener> packetListeners =
|
||||
Collections.newSetFromMap(new ConcurrentHashMap<PacketListener, Boolean>());
|
||||
|
||||
// Packet injection
|
||||
private PacketInjector packetInjector;
|
||||
|
||||
// Different injection types per game phase
|
||||
private PlayerInjectionHandler playerInjection;
|
||||
|
||||
// The two listener containers
|
||||
private SortedPacketListenerList recievedListeners = new SortedPacketListenerList();
|
||||
private SortedPacketListenerList sendingListeners = new SortedPacketListenerList();
|
||||
|
||||
// Whether or not this class has been closed
|
||||
private volatile boolean hasClosed;
|
||||
|
||||
// The default class loader
|
||||
private ClassLoader classLoader;
|
||||
|
||||
// Error repoter
|
||||
private ErrorReporter reporter;
|
||||
|
||||
// The current server
|
||||
private Server server;
|
||||
|
||||
// The async packet handler
|
||||
private AsyncFilterManager asyncFilterManager;
|
||||
|
||||
// Valid server and client packets
|
||||
private Set<Integer> serverPackets;
|
||||
private Set<Integer> clientPackets;
|
||||
|
||||
// Ensure that we're not performing too may injections
|
||||
private AtomicInteger phaseLoginCount = new AtomicInteger(0);
|
||||
private AtomicInteger phasePlayingCount = new AtomicInteger(0);
|
||||
|
||||
// Whether or not plugins are using the send/receive methods
|
||||
private AtomicBoolean packetCreation = new AtomicBoolean();
|
||||
|
||||
/**
|
||||
* Only create instances of this class if protocol lib is disabled.
|
||||
* @param unhookTask
|
||||
*/
|
||||
public PacketFilterManager(ClassLoader classLoader, Server server, DelayedSingleTask unhookTask, ErrorReporter reporter) {
|
||||
if (reporter == null)
|
||||
throw new IllegalArgumentException("reporter cannot be NULL.");
|
||||
if (classLoader == null)
|
||||
throw new IllegalArgumentException("classLoader cannot be NULL.");
|
||||
|
||||
// Just boilerplate
|
||||
final DelayedSingleTask finalUnhookTask = unhookTask;
|
||||
|
||||
// References
|
||||
this.unhookTask = unhookTask;
|
||||
this.server = server;
|
||||
this.classLoader = classLoader;
|
||||
this.reporter = reporter;
|
||||
|
||||
// Used to determine if injection is needed
|
||||
Predicate<GamePhase> isInjectionNecessary = new Predicate<GamePhase>() {
|
||||
@Override
|
||||
public boolean apply(@Nullable GamePhase phase) {
|
||||
boolean result = true;
|
||||
|
||||
if (phase.hasLogin())
|
||||
result &= getPhaseLoginCount() > 0;
|
||||
// Note that we will still hook players if the unhooking has been delayed
|
||||
if (phase.hasPlaying())
|
||||
result &= getPhasePlayingCount() > 0 || finalUnhookTask.isRunning();
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
// Initialize injection mangers
|
||||
this.playerInjection = new PlayerInjectionHandler(classLoader, reporter, isInjectionNecessary, this, packetListeners, server);
|
||||
this.packetInjector = new PacketInjector(classLoader, this, playerInjection, reporter);
|
||||
this.asyncFilterManager = new AsyncFilterManager(reporter, server.getScheduler(), this);
|
||||
|
||||
// Attempt to load the list of server and client packets
|
||||
try {
|
||||
this.serverPackets = MinecraftRegistry.getServerPackets();
|
||||
this.clientPackets = MinecraftRegistry.getClientPackets();
|
||||
} catch (FieldAccessException e) {
|
||||
reporter.reportWarning(this, "Cannot load server and client packet list.", e);
|
||||
}
|
||||
|
||||
} catch (IllegalAccessException e) {
|
||||
reporter.reportWarning(this, "Unable to initialize packet injector.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public AsynchronousManager getAsynchronousManager() {
|
||||
return asyncFilterManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves how the server packets are read.
|
||||
* @return Injection method for reading server packets.
|
||||
*/
|
||||
public PlayerInjectHooks getPlayerHook() {
|
||||
return playerInjection.getPlayerHook();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets how the server packets are read.
|
||||
* @param playerHook - the new injection method for reading server packets.
|
||||
*/
|
||||
public void setPlayerHook(PlayerInjectHooks playerHook) {
|
||||
playerInjection.setPlayerHook(playerHook);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImmutableSet<PacketListener> getPacketListeners() {
|
||||
return ImmutableSet.copyOf(packetListeners);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addPacketListener(PacketListener listener) {
|
||||
if (listener == null)
|
||||
throw new IllegalArgumentException("listener cannot be NULL.");
|
||||
|
||||
// A listener can only be added once
|
||||
if (packetListeners.contains(listener))
|
||||
return;
|
||||
|
||||
ListeningWhitelist sending = listener.getSendingWhitelist();
|
||||
ListeningWhitelist receiving = listener.getReceivingWhitelist();
|
||||
boolean hasSending = sending != null && sending.isEnabled();
|
||||
boolean hasReceiving = receiving != null && receiving.isEnabled();
|
||||
|
||||
if (hasSending || hasReceiving) {
|
||||
// Add listeners and hooks
|
||||
if (hasSending) {
|
||||
verifyWhitelist(listener, sending);
|
||||
sendingListeners.addListener(listener, sending);
|
||||
enablePacketFilters(listener, ConnectionSide.SERVER_SIDE, sending.getWhitelist());
|
||||
|
||||
// Make sure this is possible
|
||||
playerInjection.checkListener(listener);
|
||||
}
|
||||
if (hasReceiving) {
|
||||
verifyWhitelist(listener, receiving);
|
||||
recievedListeners.addListener(listener, receiving);
|
||||
enablePacketFilters(listener, ConnectionSide.CLIENT_SIDE, receiving.getWhitelist());
|
||||
}
|
||||
|
||||
// Increment phases too
|
||||
if (hasSending)
|
||||
incrementPhases(sending.getGamePhase());
|
||||
if (hasReceiving)
|
||||
incrementPhases(receiving.getGamePhase());
|
||||
|
||||
// Inform our injected hooks
|
||||
packetListeners.add(listener);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked to handle the different game phases of a added listener.
|
||||
* @param phase - listener's game game phase.
|
||||
*/
|
||||
private void incrementPhases(GamePhase phase) {
|
||||
if (phase.hasLogin())
|
||||
phaseLoginCount.incrementAndGet();
|
||||
|
||||
// We may have to inject into every current player
|
||||
if (phase.hasPlaying()) {
|
||||
if (phasePlayingCount.incrementAndGet() == 1) {
|
||||
// If we're about to uninitialize every player, cancel that instead
|
||||
if (unhookTask.isRunning())
|
||||
unhookTask.cancel();
|
||||
else
|
||||
// Inject our hook into already existing players
|
||||
initializePlayers(server.getOnlinePlayers());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked to handle the different game phases of a removed listener.
|
||||
* @param phase - listener's game game phase.
|
||||
*/
|
||||
private void decrementPhases(GamePhase phase) {
|
||||
if (phase.hasLogin())
|
||||
phaseLoginCount.decrementAndGet();
|
||||
|
||||
// We may have to inject into every current player
|
||||
if (phase.hasPlaying()) {
|
||||
if (phasePlayingCount.decrementAndGet() == 0) {
|
||||
// Schedule unhooking in the future
|
||||
unhookTask.schedule(UNHOOK_DELAY, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// Inject our hook into already existing players
|
||||
uninitializePlayers(server.getOnlinePlayers());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the packet IDs in a whitelist is valid.
|
||||
* @param listener - the listener that will be mentioned in the error.
|
||||
* @param whitelist - whitelist of packet IDs.
|
||||
* @throws IllegalArgumentException If the whitelist is illegal.
|
||||
*/
|
||||
public static void verifyWhitelist(PacketListener listener, ListeningWhitelist whitelist) {
|
||||
for (Integer id : whitelist.getWhitelist()) {
|
||||
if (id >= 256 || id < 0) {
|
||||
throw new IllegalArgumentException(String.format("Invalid packet id %s in listener %s.",
|
||||
id, PacketAdapter.getPluginName(listener))
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removePacketListener(PacketListener listener) {
|
||||
if (listener == null)
|
||||
throw new IllegalArgumentException("listener cannot be NULL");
|
||||
|
||||
List<Integer> sendingRemoved = null;
|
||||
List<Integer> receivingRemoved = null;
|
||||
|
||||
ListeningWhitelist sending = listener.getSendingWhitelist();
|
||||
ListeningWhitelist receiving = listener.getReceivingWhitelist();
|
||||
|
||||
// Remove from the overal list of listeners
|
||||
if (!packetListeners.remove(listener))
|
||||
return;
|
||||
|
||||
// Remove listeners and phases
|
||||
if (sending != null && sending.isEnabled()) {
|
||||
sendingRemoved = sendingListeners.removeListener(listener, sending);
|
||||
decrementPhases(sending.getGamePhase());
|
||||
}
|
||||
if (receiving != null && receiving.isEnabled()) {
|
||||
receivingRemoved = recievedListeners.removeListener(listener, receiving);
|
||||
decrementPhases(receiving.getGamePhase());
|
||||
}
|
||||
|
||||
// Remove hooks, if needed
|
||||
if (sendingRemoved != null && sendingRemoved.size() > 0)
|
||||
disablePacketFilters(ConnectionSide.SERVER_SIDE, sendingRemoved);
|
||||
if (receivingRemoved != null && receivingRemoved.size() > 0)
|
||||
disablePacketFilters(ConnectionSide.CLIENT_SIDE, receivingRemoved);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removePacketListeners(Plugin plugin) {
|
||||
|
||||
// Iterate through every packet listener
|
||||
for (PacketListener listener : packetListeners) {
|
||||
// Remove the listener
|
||||
if (Objects.equal(listener.getPlugin(), plugin)) {
|
||||
removePacketListener(listener);
|
||||
}
|
||||
}
|
||||
|
||||
// Do the same for the asynchronous events
|
||||
asyncFilterManager.unregisterAsyncHandlers(plugin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invokePacketRecieving(PacketEvent event) {
|
||||
handlePacket(recievedListeners, event, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invokePacketSending(PacketEvent event) {
|
||||
handlePacket(sendingListeners, event, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a packet sending or receiving event.
|
||||
* <p>
|
||||
* Note that we also handle asynchronous events.
|
||||
* @param packetListeners - packet listeners that will receive this event.
|
||||
* @param event - the evnet to broadcast.
|
||||
*/
|
||||
private void handlePacket(SortedPacketListenerList packetListeners, PacketEvent event, boolean sending) {
|
||||
|
||||
// By default, asynchronous packets are queued for processing
|
||||
if (asyncFilterManager.hasAsynchronousListeners(event)) {
|
||||
event.setAsyncMarker(asyncFilterManager.createAsyncMarker());
|
||||
}
|
||||
|
||||
// Process synchronous events
|
||||
if (sending)
|
||||
packetListeners.invokePacketSending(reporter, event);
|
||||
else
|
||||
packetListeners.invokePacketRecieving(reporter, event);
|
||||
|
||||
// To cancel asynchronous processing, use the async marker
|
||||
if (!event.isCancelled() && !hasAsyncCancelled(event.getAsyncMarker())) {
|
||||
asyncFilterManager.enqueueSyncPacket(event, event.getAsyncMarker());
|
||||
|
||||
// The above makes a copy of the event, so it's safe to cancel it
|
||||
event.setCancelled(true);
|
||||
}
|
||||
}
|
||||
|
||||
// NULL marker mean we're dealing with no asynchronous listeners
|
||||
private boolean hasAsyncCancelled(AsyncMarker marker) {
|
||||
return marker == null || marker.isAsyncCancelled();
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables packet events for a given packet ID.
|
||||
* <p>
|
||||
* Note that all packets are disabled by default.
|
||||
*
|
||||
* @param listener - the listener that requested to enable these filters.
|
||||
* @param side - which side the event will arrive from.
|
||||
* @param packets - the packet id(s).
|
||||
*/
|
||||
private void enablePacketFilters(PacketListener listener, ConnectionSide side, Iterable<Integer> packets) {
|
||||
if (side == null)
|
||||
throw new IllegalArgumentException("side cannot be NULL.");
|
||||
|
||||
// Note the difference between unsupported and valid.
|
||||
// Every packet ID between and including 0 - 255 is valid, but only a subset is supported.
|
||||
|
||||
for (int packetID : packets) {
|
||||
// Only register server packets that are actually supported by Minecraft
|
||||
if (side.isForServer()) {
|
||||
if (serverPackets != null && serverPackets.contains(packetID))
|
||||
playerInjection.addPacketHandler(packetID);
|
||||
else
|
||||
reporter.reportWarning(this, String.format(
|
||||
"[%s] Unsupported server packet ID in current Minecraft version: %s",
|
||||
PacketAdapter.getPluginName(listener), packetID
|
||||
));
|
||||
}
|
||||
|
||||
// As above, only for client packets
|
||||
if (side.isForClient() && packetInjector != null) {
|
||||
if (clientPackets != null && clientPackets.contains(packetID))
|
||||
packetInjector.addPacketHandler(packetID);
|
||||
else
|
||||
reporter.reportWarning(this, String.format(
|
||||
"[%s] Unsupported client packet ID in current Minecraft version: %s",
|
||||
PacketAdapter.getPluginName(listener), packetID
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables packet events from a given packet ID.
|
||||
* @param packets - the packet id(s).
|
||||
* @param side - which side the event no longer should arrive from.
|
||||
*/
|
||||
private void disablePacketFilters(ConnectionSide side, Iterable<Integer> packets) {
|
||||
if (side == null)
|
||||
throw new IllegalArgumentException("side cannot be NULL.");
|
||||
|
||||
for (int packetID : packets) {
|
||||
if (side.isForServer())
|
||||
playerInjection.removePacketHandler(packetID);
|
||||
if (side.isForClient() && packetInjector != null)
|
||||
packetInjector.removePacketHandler(packetID);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendServerPacket(Player reciever, PacketContainer packet) throws InvocationTargetException {
|
||||
sendServerPacket(reciever, packet, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendServerPacket(Player reciever, PacketContainer packet, boolean filters) throws InvocationTargetException {
|
||||
if (reciever == null)
|
||||
throw new IllegalArgumentException("reciever cannot be NULL.");
|
||||
if (packet == null)
|
||||
throw new IllegalArgumentException("packet cannot be NULL.");
|
||||
// We may have to enable player injection indefinitely after this
|
||||
if (packetCreation.compareAndSet(false, true))
|
||||
incrementPhases(GamePhase.PLAYING);
|
||||
|
||||
playerInjection.sendServerPacket(reciever, packet, filters);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void recieveClientPacket(Player sender, PacketContainer packet) throws IllegalAccessException, InvocationTargetException {
|
||||
recieveClientPacket(sender, packet, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void recieveClientPacket(Player sender, PacketContainer packet, boolean filters) throws IllegalAccessException, InvocationTargetException {
|
||||
if (sender == null)
|
||||
throw new IllegalArgumentException("sender cannot be NULL.");
|
||||
if (packet == null)
|
||||
throw new IllegalArgumentException("packet cannot be NULL.");
|
||||
// And here too
|
||||
if (packetCreation.compareAndSet(false, true))
|
||||
incrementPhases(GamePhase.PLAYING);
|
||||
|
||||
Packet mcPacket = packet.getHandle();
|
||||
|
||||
// Make sure the packet isn't cancelled
|
||||
packetInjector.undoCancel(packet.getID(), mcPacket);
|
||||
|
||||
if (filters) {
|
||||
PacketEvent event = packetInjector.packetRecieved(packet, sender);
|
||||
|
||||
if (!event.isCancelled())
|
||||
mcPacket = event.getPacket().getHandle();
|
||||
else
|
||||
return;
|
||||
}
|
||||
|
||||
playerInjection.processPacket(sender, mcPacket);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketContainer createPacket(int id) {
|
||||
return createPacket(id, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketContainer createPacket(int id, boolean forceDefaults) {
|
||||
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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketConstructor createPacketConstructor(int id, Object... arguments) {
|
||||
return PacketConstructor.DEFAULT.withPacket(id, arguments);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Integer> getSendingFilters() {
|
||||
return playerInjection.getSendingFilters();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Integer> getReceivingFilters() {
|
||||
return ImmutableSet.copyOf(packetInjector.getPacketHandlers());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateEntity(Entity entity, List<Player> observers) throws FieldAccessException {
|
||||
EntityUtilities.updateEntity(entity, observers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the packet injection for every player.
|
||||
* @param players - list of players to inject.
|
||||
*/
|
||||
public void initializePlayers(Player[] players) {
|
||||
for (Player player : players)
|
||||
playerInjection.injectPlayer(player);
|
||||
}
|
||||
|
||||
/**
|
||||
* Uninitialize the packet injection of every player.
|
||||
* @param players - list of players to uninject.
|
||||
*/
|
||||
public void uninitializePlayers(Player[] players) {
|
||||
for (Player player : players)
|
||||
playerInjection.uninjectPlayer(player);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register this protocol manager on Bukkit.
|
||||
* @param manager - Bukkit plugin manager that provides player join/leave events.
|
||||
* @param plugin - the parent plugin.
|
||||
*/
|
||||
public void registerEvents(PluginManager manager, final Plugin plugin) {
|
||||
|
||||
try {
|
||||
manager.registerEvents(new Listener() {
|
||||
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
|
||||
public void onPrePlayerJoin(PlayerJoinEvent event) {
|
||||
try {
|
||||
// Let's clean up the other injection first.
|
||||
playerInjection.uninjectPlayer(event.getPlayer().getAddress());
|
||||
} catch (Exception e) {
|
||||
reporter.reportDetailed(PacketFilterManager.this, "Unable to uninject net handler for player.", e, event);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void onPlayerJoin(PlayerJoinEvent event) {
|
||||
try {
|
||||
// This call will be ignored if no listeners are registered
|
||||
playerInjection.injectPlayer(event.getPlayer());
|
||||
} catch (Exception e) {
|
||||
reporter.reportDetailed(PacketFilterManager.this, "Unable to inject player.", e, event);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void onPlayerQuit(PlayerQuitEvent event) {
|
||||
try {
|
||||
playerInjection.handleDisconnect(event.getPlayer());
|
||||
playerInjection.uninjectPlayer(event.getPlayer());
|
||||
} catch (Exception e) {
|
||||
reporter.reportDetailed(PacketFilterManager.this, "Unable to uninject logged off player.", e, event);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void onPluginDisabled(PluginDisableEvent event) {
|
||||
try {
|
||||
// Clean up in case the plugin forgets
|
||||
if (event.getPlugin() != plugin) {
|
||||
removePacketListeners(event.getPlugin());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
reporter.reportDetailed(PacketFilterManager.this, "Unable handle disabled plugin.", e, event);
|
||||
}
|
||||
}
|
||||
|
||||
}, plugin);
|
||||
|
||||
} catch (NoSuchMethodError e) {
|
||||
// Oh wow! We're running on 1.0.0 or older.
|
||||
registerOld(manager, plugin);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the number of listeners that expect packets during playing.
|
||||
* @return Number of listeners.
|
||||
*/
|
||||
private int getPhasePlayingCount() {
|
||||
return phasePlayingCount.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the number of listeners that expect packets during login.
|
||||
* @return Number of listeners
|
||||
*/
|
||||
private int getPhaseLoginCount() {
|
||||
return phaseLoginCount.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPacketID(Packet packet) {
|
||||
if (packet == null)
|
||||
throw new IllegalArgumentException("Packet cannot be NULL.");
|
||||
|
||||
return MinecraftRegistry.getPacketToID().get(packet.getClass());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerPacketClass(Class<?> clazz, int packetID) {
|
||||
MinecraftRegistry.getPacketToID().put(clazz, packetID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterPacketClass(Class<?> clazz) {
|
||||
MinecraftRegistry.getPacketToID().remove(clazz);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getPacketClassFromID(int packetID, boolean forceVanilla) {
|
||||
return MinecraftRegistry.getPacketClassFromID(packetID, forceVanilla);
|
||||
}
|
||||
|
||||
// Yes, this is crazy.
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
private void registerOld(PluginManager manager, Plugin plugin) {
|
||||
|
||||
try {
|
||||
ClassLoader loader = manager.getClass().getClassLoader();
|
||||
|
||||
// The different enums we are going to need
|
||||
Class eventTypes = loader.loadClass("org.bukkit.event.Event$Type");
|
||||
Class eventPriority = loader.loadClass("org.bukkit.event.Event$Priority");
|
||||
|
||||
// Get the priority
|
||||
Object priorityMonitor = Enum.valueOf(eventPriority, "Monitor");
|
||||
|
||||
// Get event types
|
||||
Object playerJoinType = Enum.valueOf(eventTypes, "PLAYER_JOIN");
|
||||
Object playerQuitType = Enum.valueOf(eventTypes, "PLAYER_QUIT");
|
||||
Object pluginDisabledType = Enum.valueOf(eventTypes, "PLUGIN_DISABLE");
|
||||
|
||||
// The player listener! Good times.
|
||||
Class<?> playerListener = loader.loadClass("org.bukkit.event.player.PlayerListener");
|
||||
Class<?> serverListener = loader.loadClass("org.bukkit.event.server.ServerListener");
|
||||
|
||||
// Find the register event method
|
||||
Method registerEvent = FuzzyReflection.fromObject(manager).getMethodByParameters("registerEvent",
|
||||
eventTypes, Listener.class, eventPriority, Plugin.class);
|
||||
|
||||
Enhancer playerEx = new Enhancer();
|
||||
Enhancer serverEx = new Enhancer();
|
||||
|
||||
playerEx.setSuperclass(playerListener);
|
||||
playerEx.setClassLoader(classLoader);
|
||||
playerEx.setCallback(new MethodInterceptor() {
|
||||
@Override
|
||||
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
|
||||
// Must have a parameter
|
||||
if (args.length == 1) {
|
||||
Object event = args[0];
|
||||
|
||||
// Check for the correct event
|
||||
if (event instanceof PlayerJoinEvent) {
|
||||
Player player = ((PlayerJoinEvent) event).getPlayer();
|
||||
playerInjection.injectPlayer(player);
|
||||
} else if (event instanceof PlayerQuitEvent) {
|
||||
Player player = ((PlayerQuitEvent) event).getPlayer();
|
||||
playerInjection.handleDisconnect(player);
|
||||
playerInjection.uninjectPlayer(player);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
serverEx.setSuperclass(serverListener);
|
||||
serverEx.setClassLoader(classLoader);
|
||||
serverEx.setCallback(new MethodInterceptor() {
|
||||
@Override
|
||||
public Object intercept(Object obj, Method method, Object[] args,
|
||||
MethodProxy proxy) throws Throwable {
|
||||
// Must have a parameter
|
||||
if (args.length == 1) {
|
||||
Object event = args[0];
|
||||
|
||||
if (event instanceof PluginDisableEvent)
|
||||
removePacketListeners(((PluginDisableEvent) event).getPlugin());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
// Create our listener
|
||||
Object playerProxy = playerEx.create();
|
||||
Object serverProxy = serverEx.create();
|
||||
|
||||
registerEvent.invoke(manager, playerJoinType, playerProxy, priorityMonitor, plugin);
|
||||
registerEvent.invoke(manager, playerQuitType, playerProxy, priorityMonitor, plugin);
|
||||
registerEvent.invoke(manager, pluginDisabledType, serverProxy, priorityMonitor, plugin);
|
||||
|
||||
// A lot can go wrong
|
||||
} catch (ClassNotFoundException e1) {
|
||||
e1.printStackTrace();
|
||||
} catch (IllegalArgumentException e) {
|
||||
e.printStackTrace();
|
||||
} catch (IllegalAccessException e) {
|
||||
e.printStackTrace();
|
||||
} catch (InvocationTargetException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 static Set<Integer> getServerPackets() throws FieldAccessException {
|
||||
return MinecraftRegistry.getServerPackets();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 static Set<Integer> getClientPackets() throws FieldAccessException {
|
||||
return MinecraftRegistry.getClientPackets();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the current plugin class loader.
|
||||
* @return Class loader.
|
||||
*/
|
||||
public ClassLoader getClassLoader() {
|
||||
return classLoader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isClosed() {
|
||||
return hasClosed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when ProtocolLib is closing.
|
||||
*/
|
||||
public void close() {
|
||||
// Guard
|
||||
if (hasClosed)
|
||||
return;
|
||||
|
||||
// Remove packet handlers
|
||||
if (packetInjector != null)
|
||||
packetInjector.cleanupAll();
|
||||
|
||||
// Remove server handler
|
||||
playerInjection.close();
|
||||
hasClosed = true;
|
||||
|
||||
// Remove listeners
|
||||
packetListeners.clear();
|
||||
|
||||
// Clean up async handlers. We have to do this last.
|
||||
asyncFilterManager.cleanupAll();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void finalize() throws Throwable {
|
||||
close();
|
||||
}
|
||||
}
|
||||
@@ -1,249 +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.io.DataInputStream;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import net.minecraft.server.Packet;
|
||||
import net.sf.cglib.proxy.Callback;
|
||||
import net.sf.cglib.proxy.Enhancer;
|
||||
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.injector.player.PlayerInjectionHandler;
|
||||
import com.comphenix.protocol.reflect.FieldUtils;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
|
||||
/**
|
||||
* This class is responsible for adding or removing proxy objects that intercepts recieved packets.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
class PacketInjector {
|
||||
|
||||
// The "put" method that associates a packet ID with a packet class
|
||||
private static Method putMethod;
|
||||
private static Object intHashMap;
|
||||
|
||||
// The packet filter manager
|
||||
private ListenerInvoker manager;
|
||||
|
||||
// Error reporter
|
||||
private ErrorReporter reporter;
|
||||
|
||||
// Allows us to determine the sender
|
||||
private PlayerInjectionHandler playerInjection;
|
||||
|
||||
// Allows us to look up read packet injectors
|
||||
private Map<Integer, ReadPacketModifier> readModifier;
|
||||
|
||||
// Class loader
|
||||
private ClassLoader classLoader;
|
||||
|
||||
public PacketInjector(ClassLoader classLoader, ListenerInvoker manager,
|
||||
PlayerInjectionHandler playerInjection, ErrorReporter reporter) throws IllegalAccessException {
|
||||
|
||||
this.classLoader = classLoader;
|
||||
this.manager = manager;
|
||||
this.playerInjection = playerInjection;
|
||||
this.reporter = reporter;
|
||||
this.readModifier = new ConcurrentHashMap<Integer, ReadPacketModifier>();
|
||||
initialize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Undo a packet cancel.
|
||||
* @param id - the id of the packet.
|
||||
* @param packet - packet to uncancel.
|
||||
*/
|
||||
public void undoCancel(Integer id, Packet packet) {
|
||||
ReadPacketModifier modifier = readModifier.get(id);
|
||||
|
||||
// Cancelled packets are represented with NULL
|
||||
if (modifier != null && modifier.getOverride(packet) == null) {
|
||||
modifier.removeOverride(packet);
|
||||
}
|
||||
}
|
||||
|
||||
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(Packet.class, true).getFieldByType(FuzzyReflection.MINECRAFT_OBJECT);
|
||||
|
||||
try {
|
||||
intHashMap = FieldUtils.readField(intHashMapField, (Object) null, true);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new RuntimeException("Minecraft is incompatible.", e);
|
||||
}
|
||||
|
||||
// Now, get the "put" method.
|
||||
putMethod = FuzzyReflection.fromObject(intHashMap).getMethodByParameters("put", int.class, Object.class);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
public boolean addPacketHandler(int packetID) {
|
||||
if (hasPacketHandler(packetID))
|
||||
return false;
|
||||
|
||||
Enhancer ex = new Enhancer();
|
||||
|
||||
// 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 = MinecraftRegistry.getOverwrittenPackets();
|
||||
Map<Integer, Class> previous = MinecraftRegistry.getPreviousPackets();
|
||||
Map<Class, Integer> registry = MinecraftRegistry.getPacketToID();
|
||||
Class old = MinecraftRegistry.getPacketClassFromID(packetID);
|
||||
|
||||
// If this packet is not known
|
||||
if (old == null) {
|
||||
throw new IllegalStateException("Packet ID " + packetID + " is not a valid packet ID in this version.");
|
||||
}
|
||||
// Check for previous injections
|
||||
if (!old.getName().startsWith("net.minecraft.")) {
|
||||
throw new IllegalStateException("Packet " + packetID + " has already been injected.");
|
||||
}
|
||||
|
||||
// Subclass the specific packet class
|
||||
ex.setSuperclass(old);
|
||||
ex.setCallbackType(ReadPacketModifier.class);
|
||||
ex.setClassLoader(classLoader);
|
||||
Class proxy = ex.createClass();
|
||||
|
||||
// Create the proxy handler
|
||||
ReadPacketModifier modifier = new ReadPacketModifier(packetID, this, reporter);
|
||||
readModifier.put(packetID, modifier);
|
||||
|
||||
// Add a static reference
|
||||
Enhancer.registerStaticCallbacks(proxy, new Callback[] { modifier });
|
||||
|
||||
try {
|
||||
// Override values
|
||||
previous.put(packetID, old);
|
||||
registry.put(proxy, packetID);
|
||||
overwritten.put(packetID, proxy);
|
||||
putMethod.invoke(intHashMap, packetID, proxy);
|
||||
return true;
|
||||
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new RuntimeException("Illegal argument.", e);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException("Cannot access method.", e);
|
||||
} catch (InvocationTargetException e) {
|
||||
throw new RuntimeException("Exception occured in IntHashMap.put.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
public boolean removePacketHandler(int packetID) {
|
||||
if (!hasPacketHandler(packetID))
|
||||
return false;
|
||||
|
||||
Map<Class, Integer> registry = MinecraftRegistry.getPacketToID();
|
||||
Map<Integer, Class> previous = MinecraftRegistry.getPreviousPackets();
|
||||
Map<Integer, Class> overwritten = MinecraftRegistry.getOverwrittenPackets();
|
||||
|
||||
// Use the old class definition
|
||||
try {
|
||||
Class old = previous.get(packetID);
|
||||
Class proxy = MinecraftRegistry.getPacketClassFromID(packetID);
|
||||
|
||||
putMethod.invoke(intHashMap, packetID, old);
|
||||
previous.remove(packetID);
|
||||
readModifier.remove(packetID);
|
||||
registry.remove(proxy);
|
||||
overwritten.remove(packetID);
|
||||
return true;
|
||||
|
||||
// Handle some problems
|
||||
} catch (IllegalArgumentException e) {
|
||||
return false;
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException("Cannot access method.", e);
|
||||
} catch (InvocationTargetException e) {
|
||||
throw new RuntimeException("Exception occured in IntHashMap.put.", e);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasPacketHandler(int packetID) {
|
||||
return MinecraftRegistry.getPreviousPackets().containsKey(packetID);
|
||||
}
|
||||
|
||||
public Set<Integer> getPacketHandlers() {
|
||||
return MinecraftRegistry.getPreviousPackets().keySet();
|
||||
}
|
||||
|
||||
// Called from the ReadPacketModified monitor
|
||||
PacketEvent packetRecieved(PacketContainer packet, DataInputStream input) {
|
||||
Player client = playerInjection.getPlayerByConnection(input);
|
||||
|
||||
// Never invoke a event if we don't know where it's from
|
||||
if (client != null)
|
||||
return packetRecieved(packet, client);
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Let the packet listeners process the given packet.
|
||||
* @param packet - a packet to process.
|
||||
* @param client - the client that sent the packet.
|
||||
* @return The resulting packet event.
|
||||
*/
|
||||
public PacketEvent packetRecieved(PacketContainer packet, Player client) {
|
||||
|
||||
PacketEvent event = PacketEvent.fromClient((Object) manager, packet, client);
|
||||
|
||||
manager.invokePacketRecieving(event);
|
||||
return event;
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
public synchronized void cleanupAll() {
|
||||
Map<Integer, Class> overwritten = MinecraftRegistry.getOverwrittenPackets();
|
||||
Map<Integer, Class> previous = MinecraftRegistry.getPreviousPackets();
|
||||
|
||||
// Remove every packet handler
|
||||
for (Integer id : previous.keySet().toArray(new Integer[0])) {
|
||||
removePacketHandler(id);
|
||||
}
|
||||
|
||||
overwritten.clear();
|
||||
previous.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Inform the current PlayerInjector that it should update the DataInputStream next.
|
||||
* @param player - the player to update.
|
||||
*/
|
||||
public void scheduleDataInputRefresh(Player player) {
|
||||
playerInjection.scheduleDataInputRefresh(player);
|
||||
}
|
||||
}
|
||||
-58
@@ -1,58 +0,0 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol.injector;
|
||||
|
||||
/**
|
||||
* Invoked when attempting to use a player that has already logged out.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public class PlayerLoggedOutException extends RuntimeException {
|
||||
|
||||
/**
|
||||
* Generated by Eclipse.
|
||||
*/
|
||||
private static final long serialVersionUID = 4889257862160145234L;
|
||||
|
||||
public PlayerLoggedOutException() {
|
||||
// Default error message
|
||||
super("Cannot inject a player that has already logged out.");
|
||||
}
|
||||
|
||||
public PlayerLoggedOutException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public PlayerLoggedOutException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public PlayerLoggedOutException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an exception from a formatted message.
|
||||
* @param message - the message to format.
|
||||
* @param params - parameters.
|
||||
* @return The formated exception
|
||||
*/
|
||||
public static PlayerLoggedOutException fromFormat(String message, Object... params) {
|
||||
return new PlayerLoggedOutException(String.format(message, params));
|
||||
}
|
||||
}
|
||||
@@ -1,145 +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.io.DataInputStream;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.WeakHashMap;
|
||||
|
||||
import com.comphenix.protocol.Packets;
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
|
||||
import net.minecraft.server.Packet;
|
||||
import net.sf.cglib.proxy.MethodInterceptor;
|
||||
import net.sf.cglib.proxy.MethodProxy;
|
||||
|
||||
class ReadPacketModifier implements MethodInterceptor {
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
private static Class[] parameters = { DataInputStream.class };
|
||||
|
||||
// A cancel marker
|
||||
private static final Object CANCEL_MARKER = new Object();
|
||||
|
||||
// Common for all packets of the same type
|
||||
private PacketInjector packetInjector;
|
||||
private int packetID;
|
||||
|
||||
// Report errors
|
||||
private ErrorReporter reporter;
|
||||
|
||||
// Whether or not a packet has been cancelled
|
||||
private static Map<Object, Object> override = Collections.synchronizedMap(new WeakHashMap<Object, Object>());
|
||||
|
||||
public ReadPacketModifier(int packetID, PacketInjector packetInjector, ErrorReporter reporter) {
|
||||
this.packetID = packetID;
|
||||
this.packetInjector = packetInjector;
|
||||
this.reporter = reporter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove any packet overrides.
|
||||
* @param packet - the packet to rever
|
||||
*/
|
||||
public void removeOverride(Packet packet) {
|
||||
override.remove(packet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the packet that overrides the methods of the given packet.
|
||||
* @param packet - the given packet.
|
||||
* @return Overriden object.
|
||||
*/
|
||||
public Object getOverride(Packet packet) {
|
||||
return override.get(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object intercept(Object thisObj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
|
||||
|
||||
Object returnValue = null;
|
||||
String methodName = method.getName();
|
||||
|
||||
// We always pass these down (otherwise, we'll end up with a infinite loop)
|
||||
if (methodName.equals("hashCode") || methodName.equals("equals") || methodName.equals("toString")) {
|
||||
return proxy.invokeSuper(thisObj, args);
|
||||
}
|
||||
|
||||
// Atomic retrieval
|
||||
Object overridenObject = override.get(thisObj);
|
||||
|
||||
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 (returnValue == null &&
|
||||
Arrays.equals(method.getParameterTypes(), parameters)) {
|
||||
|
||||
try {
|
||||
// We need this in order to get the correct player
|
||||
DataInputStream input = (DataInputStream) args[0];
|
||||
|
||||
// Let the people know
|
||||
PacketContainer container = new PacketContainer(packetID, (Packet) thisObj);
|
||||
PacketEvent event = packetInjector.packetRecieved(container, input);
|
||||
|
||||
// Handle override
|
||||
if (event != null) {
|
||||
Packet result = event.getPacket().getHandle();
|
||||
|
||||
if (event.isCancelled()) {
|
||||
override.put(thisObj, CANCEL_MARKER);
|
||||
} else if (!objectEquals(thisObj, result)) {
|
||||
override.put(thisObj, result);
|
||||
}
|
||||
|
||||
// Update DataInputStream next time
|
||||
if (!event.isCancelled() && packetID == Packets.Server.KEY_RESPONSE) {
|
||||
packetInjector.scheduleDataInputRefresh(event.getPlayer());
|
||||
}
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
// Minecraft cannot handle this error
|
||||
reporter.reportDetailed(this, "Cannot handle clienet packet.", e, args[0]);
|
||||
}
|
||||
}
|
||||
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
private boolean objectEquals(Object a, Object b) {
|
||||
return System.identityHashCode(a) != System.identityHashCode(b);
|
||||
}
|
||||
}
|
||||
-77
@@ -1,77 +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.Collection;
|
||||
|
||||
import com.comphenix.protocol.concurrency.AbstractConcurrentListenerMultimap;
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.events.PacketListener;
|
||||
|
||||
/**
|
||||
* Registry of synchronous packet listeners.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
class SortedPacketListenerList extends AbstractConcurrentListenerMultimap<PacketListener> {
|
||||
|
||||
/**
|
||||
* Invokes the given packet event for every registered listener.
|
||||
* @param reporter - the error reporter that will be used to inform about listener exceptions.
|
||||
* @param event - the packet event to invoke.
|
||||
*/
|
||||
public void invokePacketRecieving(ErrorReporter reporter, PacketEvent event) {
|
||||
Collection<PrioritizedListener<PacketListener>> list = getListener(event.getPacketID());
|
||||
|
||||
if (list == null)
|
||||
return;
|
||||
|
||||
// The returned list is thread-safe
|
||||
for (PrioritizedListener<PacketListener> element : list) {
|
||||
try {
|
||||
element.getListener().onPacketReceiving(event);
|
||||
} catch (Throwable e) {
|
||||
// Minecraft doesn't want your Exception.
|
||||
reporter.reportMinimal(element.getListener().getPlugin(), "onPacketReceiving()", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the given packet event for every registered listener.
|
||||
* @param reporter - the error reporter that will be used to inform about listener exceptions.
|
||||
* @param event - the packet event to invoke.
|
||||
*/
|
||||
public void invokePacketSending(ErrorReporter reporter, PacketEvent event) {
|
||||
Collection<PrioritizedListener<PacketListener>> list = getListener(event.getPacketID());
|
||||
|
||||
if (list == null)
|
||||
return;
|
||||
|
||||
for (PrioritizedListener<PacketListener> element : list) {
|
||||
try {
|
||||
element.getListener().onPacketSending(event);
|
||||
} catch (Throwable e) {
|
||||
// Minecraft doesn't want your Exception.
|
||||
reporter.reportMinimal(element.getListener().getPlugin(), "onPacketSending()", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,113 +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 net.minecraft.server.Packet;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* Caches structure modifiers.
|
||||
* @author Kristian
|
||||
*/
|
||||
public class StructureCache {
|
||||
// Structure modifiers
|
||||
private static ConcurrentMap<Integer, StructureModifier<Object>> structureModifiers =
|
||||
new ConcurrentHashMap<Integer, StructureModifier<Object>>();
|
||||
|
||||
private static Set<Integer> compiling = new HashSet<Integer>();
|
||||
|
||||
/**
|
||||
* Creates an empty Minecraft packet of the given ID.
|
||||
* @param id - packet ID.
|
||||
* @return Created packet.
|
||||
*/
|
||||
public static Packet newPacket(int id) {
|
||||
try {
|
||||
return (Packet) MinecraftRegistry.getPacketClassFromID(id, true).newInstance();
|
||||
} catch (InstantiationException e) {
|
||||
return null;
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException("Access denied.", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a cached structure modifier for the given packet id.
|
||||
* @param id - packet ID.
|
||||
* @return A structure modifier.
|
||||
*/
|
||||
public static StructureModifier<Object> getStructure(int id) {
|
||||
// Compile structures by default
|
||||
return getStructure(id, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a cached structure modifier for the given packet id.
|
||||
* @param id - packet ID.
|
||||
* @param compile - whether or not to asynchronously compile the structure modifier.
|
||||
* @return A structure modifier.
|
||||
*/
|
||||
public static StructureModifier<Object> getStructure(int id, boolean compile) {
|
||||
|
||||
StructureModifier<Object> result = structureModifiers.get(id);
|
||||
|
||||
// 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>(
|
||||
MinecraftRegistry.getPacketClassFromID(id, true), Packet.class, true);
|
||||
|
||||
result = structureModifiers.putIfAbsent(id, 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 int idCopy = id;
|
||||
final BackgroundCompiler compiler = BackgroundCompiler.getInstance();
|
||||
|
||||
if (!compiling.contains(id) && compiler != null) {
|
||||
compiler.scheduleCompilation(result, new CompileListener<Object>() {
|
||||
@Override
|
||||
public void onCompiled(StructureModifier<Object> compiledModifier) {
|
||||
structureModifiers.put(idCopy, compiledModifier);
|
||||
}
|
||||
});
|
||||
compiling.add(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
-151
@@ -1,151 +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 com.comphenix.protocol.injector.ListenerInvoker;
|
||||
import com.comphenix.protocol.injector.player.NetworkFieldInjector.FakePacket;
|
||||
|
||||
import net.minecraft.server.Packet;
|
||||
import net.sf.cglib.proxy.Enhancer;
|
||||
import net.sf.cglib.proxy.MethodInterceptor;
|
||||
import net.sf.cglib.proxy.MethodProxy;
|
||||
|
||||
/**
|
||||
* The array list that notifies when packets are sent by the server.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
class InjectedArrayList extends ArrayList<Packet> {
|
||||
|
||||
/**
|
||||
* Silly Eclipse.
|
||||
*/
|
||||
private static final long serialVersionUID = -1173865905404280990L;
|
||||
|
||||
private PlayerInjector injector;
|
||||
private Set<Packet> ignoredPackets;
|
||||
private ClassLoader classLoader;
|
||||
|
||||
public InjectedArrayList(ClassLoader classLoader, PlayerInjector injector, Set<Packet> ignoredPackets) {
|
||||
this.classLoader = classLoader;
|
||||
this.injector = injector;
|
||||
this.ignoredPackets = ignoredPackets;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean add(Packet packet) {
|
||||
|
||||
Packet result = null;
|
||||
|
||||
// Check for fake packets and ignored packets
|
||||
if (packet instanceof FakePacket) {
|
||||
return true;
|
||||
} else if (ignoredPackets.contains(packet)) {
|
||||
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), true);
|
||||
}
|
||||
|
||||
// Collection.add contract
|
||||
return true;
|
||||
|
||||
} catch (InvocationTargetException e) {
|
||||
throw new RuntimeException("Reverting cancelled packet failed.", e.getTargetException());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
Packet createNegativePacket(Packet source) {
|
||||
ListenerInvoker invoker = injector.getInvoker();
|
||||
|
||||
int packetID = invoker.getPacketID(source);
|
||||
Class<?> type = invoker.getPacketClassFromID(packetID, true);
|
||||
|
||||
// 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 = new Enhancer();
|
||||
ex.setSuperclass(type);
|
||||
ex.setInterfaces(new Class[] { FakePacket.class } );
|
||||
ex.setUseCache(true);
|
||||
ex.setClassLoader(classLoader);
|
||||
ex.setCallbackType(InvertedIntegerCallback.class);
|
||||
|
||||
Class<?> proxyClass = ex.createClass();
|
||||
|
||||
// Temporarily associate the fake packet class
|
||||
invoker.registerPacketClass(proxyClass, packetID);
|
||||
|
||||
Packet fake = (Packet) Enhancer.create(proxyClass, new InvertedIntegerCallback());
|
||||
|
||||
// Remove this association
|
||||
invoker.unregisterPacketClass(proxyClass);
|
||||
return fake;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
if (method.getReturnType().equals(int.class) && args.length == 0) {
|
||||
Integer result = (Integer) proxy.invokeSuper(obj, args);
|
||||
return -result;
|
||||
} else {
|
||||
return proxy.invokeSuper(obj, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-297
@@ -1,297 +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.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import net.minecraft.server.NetLoginHandler;
|
||||
import net.sf.cglib.proxy.Factory;
|
||||
|
||||
import org.bukkit.Server;
|
||||
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.reflect.FieldUtils;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.comphenix.protocol.reflect.ObjectCloner;
|
||||
import com.comphenix.protocol.reflect.VolatileField;
|
||||
|
||||
/**
|
||||
* Used to ensure that the 1.3 server is referencing the correct server handler.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
class InjectedServerConnection {
|
||||
|
||||
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;
|
||||
|
||||
// Used to inject net handlers
|
||||
private NetLoginInjector netLoginInjector;
|
||||
|
||||
private Server server;
|
||||
private ErrorReporter reporter;
|
||||
private boolean hasAttempted;
|
||||
private boolean hasSuccess;
|
||||
|
||||
private Object minecraftServer = null;
|
||||
|
||||
public InjectedServerConnection(ErrorReporter reporter, Server server, NetLoginInjector netLoginInjector) {
|
||||
this.listFields = new ArrayList<VolatileField>();
|
||||
this.replacedLists = new ArrayList<ReplacedArrayList<Object>>();
|
||||
this.reporter = reporter;
|
||||
this.server = server;
|
||||
this.netLoginInjector = netLoginInjector;
|
||||
}
|
||||
|
||||
public void injectList() {
|
||||
|
||||
// Only execute this method once
|
||||
if (!hasAttempted)
|
||||
hasAttempted = true;
|
||||
else
|
||||
return;
|
||||
|
||||
if (minecraftServerField == null)
|
||||
minecraftServerField = FuzzyReflection.fromObject(server, true).getFieldByType(".*MinecraftServer");
|
||||
|
||||
try {
|
||||
minecraftServer = FieldUtils.readField(minecraftServerField, server, true);
|
||||
} catch (IllegalAccessException e1) {
|
||||
reporter.reportWarning(this, "Cannot extract minecraft server from Bukkit.");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (serverConnectionMethod == null)
|
||||
serverConnectionMethod = FuzzyReflection.fromClass(minecraftServerField.getType()).
|
||||
getMethodByParameters("getServerConnection", ".*ServerConnection", new String[] {});
|
||||
// We're using Minecraft 1.3.1
|
||||
injectServerConnection();
|
||||
|
||||
} catch (IllegalArgumentException e) {
|
||||
|
||||
// Minecraft 1.2.5 or lower
|
||||
injectListenerThread();
|
||||
|
||||
} catch (Exception e) {
|
||||
// Oh damn - inform the player
|
||||
reporter.reportDetailed(this, "Cannot inject into server connection. Bad things will happen.", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void injectListenerThread() {
|
||||
|
||||
try {
|
||||
|
||||
if (listenerThreadField == null)
|
||||
listenerThreadField = FuzzyReflection.fromObject(minecraftServer).
|
||||
getFieldByType(".*NetworkListenThread");
|
||||
} catch (RuntimeException e) {
|
||||
reporter.reportDetailed(this, "Cannot find listener thread in MinecraftServer.", e, minecraftServer);
|
||||
return;
|
||||
}
|
||||
|
||||
Object listenerThread = null;
|
||||
|
||||
// Attempt to get the thread
|
||||
try {
|
||||
listenerThread = listenerThreadField.get(minecraftServer);
|
||||
} catch (Exception e) {
|
||||
reporter.reportWarning(this, "Unable to read the listener thread.", e);
|
||||
return;
|
||||
}
|
||||
|
||||
// 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 = serverConnectionMethod.invoke(minecraftServer);
|
||||
} catch (Exception ex) {
|
||||
reporter.reportDetailed(this, "Unable to retrieve server connection", ex, minecraftServer);
|
||||
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, "Unexpected number of threads in " + serverConnection.getClass().getName());
|
||||
else
|
||||
dedicatedThreadField = matches.get(0);
|
||||
}
|
||||
|
||||
// Next, try to get the dedicated thread
|
||||
try {
|
||||
if (dedicatedThreadField != null)
|
||||
injectEveryListField(FieldUtils.readField(dedicatedThreadField, serverConnection, true), 1);
|
||||
} catch (IllegalAccessException e) {
|
||||
reporter.reportWarning(this, "Unable to retrieve net handler thread.", e);
|
||||
}
|
||||
|
||||
injectIntoList(serverConnection, listField);
|
||||
hasSuccess = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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, "Unable to inject " + minimum + " lists in " + container.getClass().getName());
|
||||
}
|
||||
}
|
||||
|
||||
@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;
|
||||
|
||||
@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 {
|
||||
ObjectCloner.copyTo(inserting, replacement, inserting.getClass());
|
||||
} catch (Throwable e) {
|
||||
reporter.reportDetailed(InjectedServerConnection.this, "Cannot copy old " + inserting +
|
||||
" to new.", e, inserting, replacement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onInserting(Object inserting) {
|
||||
// Ready for some login handler injection?
|
||||
if (inserting instanceof NetLoginHandler) {
|
||||
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 (removing instanceof NetLoginHandler) {
|
||||
netLoginInjector.cleanup(removing);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace the server handler instance kept by the "keep alive" object.
|
||||
* @param oldHandler - old server handler.
|
||||
* @param newHandler - new, proxied server handler.
|
||||
*/
|
||||
public void replaceServerHandler(Object oldHandler, Object newHandler) {
|
||||
if (!hasAttempted) {
|
||||
injectList();
|
||||
}
|
||||
|
||||
if (hasSuccess) {
|
||||
for (ReplacedArrayList<Object> replacedList : replacedLists) {
|
||||
replacedList.addMapping(oldHandler, newHandler);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Revert to the old vanilla server handler, if it has been replaced.
|
||||
* @param oldHandler - old vanilla server handler.
|
||||
*/
|
||||
public void revertServerHandler(Object oldHandler) {
|
||||
if (hasSuccess) {
|
||||
for (ReplacedArrayList<Object> replacedList : replacedLists) {
|
||||
replacedList.removeMapping(oldHandler);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Undoes everything.
|
||||
*/
|
||||
public void cleanupAll() {
|
||||
if (replacedLists.size() > 0) {
|
||||
// Repair the underlying lists
|
||||
for (ReplacedArrayList<Object> replacedList : replacedLists) {
|
||||
replacedList.revertAll();
|
||||
}
|
||||
for (VolatileField field : listFields) {
|
||||
field.revertValue();
|
||||
}
|
||||
|
||||
listFields.clear();
|
||||
replacedLists.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
package com.comphenix.protocol.injector.player;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Represents a very quick integer set that uses a lookup table to store membership.
|
||||
* <p>
|
||||
* This class is intentionally missing a size method.
|
||||
* @author Kristian
|
||||
*/
|
||||
class IntegerSet {
|
||||
private final boolean[] array;
|
||||
|
||||
/**
|
||||
* Initialize a lookup table with the given maximum number of elements.
|
||||
* <p>
|
||||
* This creates a set for elements in the range [0, count).
|
||||
* <p>
|
||||
* Formally, the current set must be a subset of [0, 1, 2, ..., count - 1].
|
||||
* @param maximumCount - maximum element value and count.
|
||||
*/
|
||||
public IntegerSet(int maximumCount) {
|
||||
this.array = new boolean[maximumCount];
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether or not the given element exists in the set.
|
||||
* @param value - the element to check. Must be in the range [0, count).
|
||||
* @return TRUE if the given element exists, FALSE otherwise.
|
||||
*/
|
||||
public boolean contains(int element) {
|
||||
return array[element];
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the given element to the set, or do nothing if it already exists.
|
||||
* @param element - element to add.
|
||||
* @throws OutOfBoundsException If the given element is not in the range [0, count).
|
||||
*/
|
||||
public void add(int element) {
|
||||
array[element] = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the given element from the set, or do nothing if it's already removed.
|
||||
* @param element - element to remove.
|
||||
*/
|
||||
public void remove(int element) {
|
||||
// We don't actually care if the caller tries to remove an element outside the valid set
|
||||
if (element >= 0 && element < array.length)
|
||||
array[element] = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove every element from the set.
|
||||
*/
|
||||
public void clear() {
|
||||
Arrays.fill(array, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the current IntegerSet to an equivalent HashSet.
|
||||
* @return The resulting HashSet.
|
||||
*/
|
||||
public Set<Integer> toSet() {
|
||||
Set<Integer> elements = new HashSet<Integer>();
|
||||
|
||||
for (int i = 0; i < array.length; i++) {
|
||||
if (array[i])
|
||||
elements.add(i);
|
||||
}
|
||||
return elements;
|
||||
}
|
||||
}
|
||||
-139
@@ -1,139 +0,0 @@
|
||||
package com.comphenix.protocol.injector.player;
|
||||
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReadWriteLock;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.injector.GamePhase;
|
||||
import com.comphenix.protocol.injector.player.TemporaryPlayerFactory.InjectContainer;
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
/**
|
||||
* Injects every NetLoginHandler created by the server.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
class NetLoginInjector {
|
||||
|
||||
private ConcurrentMap<Object, PlayerInjector> injectedLogins = Maps.newConcurrentMap();
|
||||
|
||||
// Handles every hook
|
||||
private PlayerInjectionHandler injectionHandler;
|
||||
private Server server;
|
||||
|
||||
// The current error rerporter
|
||||
private ErrorReporter reporter;
|
||||
|
||||
private ReadWriteLock injectionLock = new ReentrantReadWriteLock();
|
||||
|
||||
// Used to create fake players
|
||||
private TemporaryPlayerFactory tempPlayerFactory = new TemporaryPlayerFactory();
|
||||
|
||||
public NetLoginInjector(ErrorReporter reporter, PlayerInjectionHandler injectionHandler, Server server) {
|
||||
this.reporter = reporter;
|
||||
this.injectionHandler = injectionHandler;
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
|
||||
injectionLock.writeLock().lock();
|
||||
|
||||
try {
|
||||
// Make sure we actually need to inject during this phase
|
||||
if (!injectionHandler.isInjectionNecessary(GamePhase.LOGIN))
|
||||
return inserting;
|
||||
|
||||
Player fakePlayer = tempPlayerFactory.createTemporaryPlayer(server);
|
||||
PlayerInjector injector = injectionHandler.injectPlayer(fakePlayer, inserting, GamePhase.LOGIN);
|
||||
injector.updateOnLogin = true;
|
||||
|
||||
// Associate the injector too
|
||||
InjectContainer container = (InjectContainer) fakePlayer;
|
||||
container.setInjector(injector);
|
||||
|
||||
// Save the login
|
||||
injectedLogins.putIfAbsent(inserting, injector);
|
||||
|
||||
// NetServerInjector can never work (currently), so we don't need to replace the NetLoginHandler
|
||||
return inserting;
|
||||
|
||||
} catch (Throwable e) {
|
||||
// Minecraft can't handle this, so we'll deal with it here
|
||||
reporter.reportDetailed(this, "Unable to hook NetLoginHandler.", e, inserting);
|
||||
return inserting;
|
||||
|
||||
} finally {
|
||||
injectionLock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the lock used for reading.
|
||||
* @return Reading lock.
|
||||
*/
|
||||
public Lock getReadLock() {
|
||||
return injectionLock.readLock();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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());
|
||||
|
||||
// Update NetworkManager
|
||||
if (newInjector == null) {
|
||||
injectionHandler.uninjectPlayer(player);
|
||||
} else {
|
||||
injectionHandler.uninjectPlayer(player, false);
|
||||
|
||||
if (injected instanceof NetworkObjectInjector)
|
||||
newInjector.setNetworkManager(injected.getNetworkManager(), true);
|
||||
}
|
||||
|
||||
} catch (Throwable e) {
|
||||
// Don't leak this to Minecraft
|
||||
reporter.reportDetailed(this, "Cannot cleanup NetLoginHandler.", e, removing);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all injected hooks.
|
||||
*/
|
||||
public void cleanupAll() {
|
||||
for (PlayerInjector injector : injectedLogins.values()) {
|
||||
injector.cleanupAll();
|
||||
}
|
||||
|
||||
injectedLogins.clear();
|
||||
}
|
||||
}
|
||||
-212
@@ -1,212 +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.error.ErrorReporter;
|
||||
import com.comphenix.protocol.events.ListeningWhitelist;
|
||||
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.google.common.collect.Sets;
|
||||
|
||||
import net.minecraft.server.Packet;
|
||||
|
||||
/**
|
||||
* 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
|
||||
}
|
||||
|
||||
// Packets to ignore
|
||||
private Set<Packet> ignoredPackets = Sets.newSetFromMap(new ConcurrentHashMap<Packet, 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;
|
||||
|
||||
// Used to construct proxy objects
|
||||
private ClassLoader classLoader;
|
||||
|
||||
public NetworkFieldInjector(ClassLoader classLoader, ErrorReporter reporter, Player player,
|
||||
ListenerInvoker manager, IntegerSet sendingFilters) throws IllegalAccessException {
|
||||
|
||||
super(reporter, player, manager);
|
||||
this.classLoader = classLoader;
|
||||
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(Packet packet, boolean filtered) throws InvocationTargetException {
|
||||
|
||||
if (networkManager != null) {
|
||||
try {
|
||||
if (!filtered) {
|
||||
ignoredPackets.add(packet);
|
||||
}
|
||||
|
||||
// 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(PacketListener listener) {
|
||||
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<Packet> minecraftList = (List<Packet>) overwriter.getOldValue();
|
||||
|
||||
synchronized(syncObject) {
|
||||
// The list we'll be inserting
|
||||
List<Packet> hackedList = new InjectedArrayList(classLoader, this, ignoredPackets);
|
||||
|
||||
// Add every previously stored packet
|
||||
for (Packet 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<Packet> minecraftList = (List<Packet>) overriden.getOldValue();
|
||||
List<Packet> hacketList = (List<Packet>) overriden.getValue();
|
||||
|
||||
if (minecraftList == hacketList) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get a lock before we modify the list
|
||||
synchronized(syncObject) {
|
||||
try {
|
||||
// Copy over current packets
|
||||
for (Packet packet : (List<Packet>) 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;
|
||||
}
|
||||
}
|
||||
-188
@@ -1,188 +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 net.minecraft.server.Packet;
|
||||
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 java.lang.reflect.Method;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import com.comphenix.protocol.Packets;
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.events.ListeningWhitelist;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Injection method that overrides the NetworkHandler itself, and it's sendPacket-method.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
class NetworkObjectInjector extends PlayerInjector {
|
||||
// Determine if we're listening
|
||||
private IntegerSet sendingFilters;
|
||||
|
||||
// Used to construct proxy objects
|
||||
private ClassLoader classLoader;
|
||||
|
||||
// Shared callback filter - avoid creating a new class every time
|
||||
private static CallbackFilter callbackFilter;
|
||||
|
||||
public NetworkObjectInjector(ClassLoader classLoader, ErrorReporter reporter, Player player,
|
||||
ListenerInvoker invoker, IntegerSet sendingFilters) throws IllegalAccessException {
|
||||
super(reporter, player, invoker);
|
||||
this.sendingFilters = sendingFilters;
|
||||
this.classLoader = classLoader;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean hasListener(int packetID) {
|
||||
return sendingFilters.contains(packetID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendServerPacket(Packet packet, boolean filtered) throws InvocationTargetException {
|
||||
Object networkDelegate = filtered ? networkManagerRef.getValue() : networkManagerRef.getOldValue();
|
||||
|
||||
if (networkDelegate != null) {
|
||||
try {
|
||||
// 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(PacketListener listener) {
|
||||
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 {
|
||||
Packet packet = (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 = new Enhancer();
|
||||
ex.setClassLoader(classLoader);
|
||||
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;
|
||||
}
|
||||
}
|
||||
-319
@@ -1,319 +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 net.minecraft.server.Packet;
|
||||
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.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.PlayerInjectHooks;
|
||||
import com.comphenix.protocol.reflect.FieldUtils;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.comphenix.protocol.reflect.ObjectCloner;
|
||||
import com.comphenix.protocol.reflect.VolatileField;
|
||||
import com.comphenix.protocol.reflect.instances.DefaultInstances;
|
||||
import com.comphenix.protocol.reflect.instances.ExistingGenerator;
|
||||
|
||||
/**
|
||||
* Represents a player hook into the NetServerHandler class.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public class NetworkServerInjector extends PlayerInjector {
|
||||
|
||||
private volatile static CallbackFilter callbackFilter;
|
||||
|
||||
private static Field disconnectField;
|
||||
private static Method sendPacketMethod;
|
||||
private InjectedServerConnection serverInjection;
|
||||
|
||||
// Determine if we're listening
|
||||
private IntegerSet sendingFilters;
|
||||
|
||||
// Used to create proxy objects
|
||||
private ClassLoader classLoader;
|
||||
|
||||
// Whether or not the player has disconnected
|
||||
private boolean hasDisconnected;
|
||||
|
||||
public NetworkServerInjector(
|
||||
ClassLoader classLoader, ErrorReporter reporter, Player player,
|
||||
ListenerInvoker invoker, IntegerSet sendingFilters,
|
||||
InjectedServerConnection serverInjection) throws IllegalAccessException {
|
||||
|
||||
super(reporter, player, invoker);
|
||||
this.classLoader = classLoader;
|
||||
this.sendingFilters = sendingFilters;
|
||||
this.serverInjection = serverInjection;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean hasListener(int packetID) {
|
||||
return sendingFilters.contains(packetID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize(Object injectionSource) throws IllegalAccessException {
|
||||
super.initialize(injectionSource);
|
||||
|
||||
// Get the send packet method!
|
||||
if (hasInitialized) {
|
||||
if (sendPacketMethod == null)
|
||||
sendPacketMethod = FuzzyReflection.fromObject(serverHandler).getMethodByName("sendPacket.*");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendServerPacket(Packet packet, boolean filtered) throws InvocationTargetException {
|
||||
Object serverDeleage = filtered ? serverHandlerRef.getValue() : serverHandlerRef.getOldValue();
|
||||
|
||||
if (serverDeleage != null) {
|
||||
try {
|
||||
// Note that invocation target exception is a wrapper for a checked exception
|
||||
sendPacketMethod.invoke(serverDeleage, 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()) {
|
||||
|
||||
// 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.");
|
||||
|
||||
// Try again
|
||||
if (tryInjectManager()) {
|
||||
// It worked - probably
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
throw new RuntimeException(
|
||||
"Cannot hook player: Unable to find a valid constructor for the NetServerHandler object.");
|
||||
}
|
||||
}
|
||||
|
||||
private boolean tryInjectManager() {
|
||||
Class<?> serverClass = serverHandler.getClass();
|
||||
|
||||
Enhancer ex = new Enhancer();
|
||||
Callback sendPacketCallback = new MethodInterceptor() {
|
||||
@Override
|
||||
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
|
||||
|
||||
Packet packet = (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 CallbackFilter() {
|
||||
@Override
|
||||
public int accept(Method method) {
|
||||
if (method.equals(sendPacketMethod))
|
||||
return 0;
|
||||
else
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
ex.setClassLoader(classLoader);
|
||||
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) {
|
||||
// This will be done by InjectedServerConnection instead
|
||||
//copyTo(serverHandler, proxyObject);
|
||||
|
||||
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 (Throwable e) {
|
||||
// Oh well
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private Class<?> getFirstMinecraftSuperClass(Class<?> clazz) {
|
||||
if (clazz.getName().startsWith("net.minecraft.server."))
|
||||
return clazz;
|
||||
else if (clazz.equals(Object.class))
|
||||
return clazz;
|
||||
else
|
||||
return getFirstMinecraftSuperClass(clazz.getSuperclass());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void cleanHook() {
|
||||
if (serverHandlerRef != null && serverHandlerRef.isCurrentSet()) {
|
||||
ObjectCloner.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) {
|
||||
reporter.reportDetailed(this, "Unable to find disconnect field. Is ProtocolLib up to date?", e, handler);
|
||||
} catch (IllegalAccessException e) {
|
||||
reporter.reportWarning(this, "Unable to update disconnected field. Player quit event may be sent twice.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public UnsupportedListener checkListener(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;
|
||||
}
|
||||
}
|
||||
-626
@@ -1,626 +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.lang.reflect.InvocationTargetException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketAddress;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import net.minecraft.server.Packet;
|
||||
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.events.PacketAdapter;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.events.PacketListener;
|
||||
import com.comphenix.protocol.injector.GamePhase;
|
||||
import com.comphenix.protocol.injector.ListenerInvoker;
|
||||
import com.comphenix.protocol.injector.PlayerLoggedOutException;
|
||||
import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks;
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
/**
|
||||
* Responsible for injecting into a player's sendPacket method.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public class PlayerInjectionHandler {
|
||||
|
||||
/**
|
||||
* The highest possible packet ID. It's unlikely that this value will ever change.
|
||||
*/
|
||||
private static final int MAXIMUM_PACKET_ID = 255;
|
||||
|
||||
// Server connection injection
|
||||
private InjectedServerConnection serverInjection;
|
||||
|
||||
// NetLogin injector
|
||||
private NetLoginInjector netLoginInjector;
|
||||
|
||||
// The last successful player hook
|
||||
private PlayerInjector lastSuccessfulHook;
|
||||
|
||||
// Player injection
|
||||
private Map<SocketAddress, PlayerInjector> addressLookup = Maps.newConcurrentMap();
|
||||
private Map<DataInputStream, PlayerInjector> dataInputLookup = Maps.newConcurrentMap();
|
||||
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;
|
||||
|
||||
// Enabled packet filters
|
||||
private IntegerSet sendingFilters = new IntegerSet(MAXIMUM_PACKET_ID + 1);
|
||||
|
||||
// List of packet listeners
|
||||
private Set<PacketListener> packetListeners;
|
||||
|
||||
// The class loader we're using
|
||||
private ClassLoader classLoader;
|
||||
|
||||
// Used to filter injection attempts
|
||||
private Predicate<GamePhase> injectionFilter;
|
||||
|
||||
public PlayerInjectionHandler(ClassLoader classLoader, ErrorReporter reporter, Predicate<GamePhase> injectionFilter,
|
||||
ListenerInvoker invoker, Set<PacketListener> packetListeners, Server server) {
|
||||
|
||||
this.classLoader = classLoader;
|
||||
this.reporter = reporter;
|
||||
this.invoker = invoker;
|
||||
this.injectionFilter = injectionFilter;
|
||||
this.packetListeners = packetListeners;
|
||||
this.netLoginInjector = new NetLoginInjector(reporter, this, server);
|
||||
this.serverInjection = new InjectedServerConnection(reporter, server, netLoginInjector);
|
||||
serverInjection.injectList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves how the server packets are read.
|
||||
* @return Injection method for reading server packets.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets how the server packets are read.
|
||||
* @param playerHook - the new injection method for reading server packets.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an underlying packet handler of the given ID.
|
||||
* @param packetID - packet ID to register.
|
||||
*/
|
||||
public void addPacketHandler(int packetID) {
|
||||
sendingFilters.add(packetID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an underlying packet handler of ths ID.
|
||||
* @param packetID - packet ID to unregister.
|
||||
*/
|
||||
public void removePacketHandler(int packetID) {
|
||||
sendingFilters.remove(packetID);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(classLoader, reporter, player, invoker, sendingFilters);
|
||||
case NETWORK_MANAGER_OBJECT:
|
||||
return new NetworkObjectInjector(classLoader, reporter, player, invoker, sendingFilters);
|
||||
case NETWORK_SERVER_OBJECT:
|
||||
return new NetworkServerInjector(classLoader, 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.
|
||||
*/
|
||||
public Player getPlayerByConnection(DataInputStream inputStream) {
|
||||
try {
|
||||
// Concurrency issue!
|
||||
netLoginInjector.getReadLock().lock();
|
||||
|
||||
PlayerInjector injector = dataInputLookup.get(inputStream);
|
||||
|
||||
if (injector != null) {
|
||||
return injector.getPlayer();
|
||||
} else {
|
||||
reporter.reportWarning(this, "Unable to find stream: " + inputStream);
|
||||
return null;
|
||||
}
|
||||
|
||||
} finally {
|
||||
netLoginInjector.getReadLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
*/
|
||||
public void injectPlayer(Player player) {
|
||||
// Inject using the player instance itself
|
||||
if (isInjectionNecessary(GamePhase.PLAYING)) {
|
||||
injectPlayer(player, player, 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, GamePhase phase) {
|
||||
// Unfortunately, due to NetLoginHandler, multiple threads may potentially call this method.
|
||||
synchronized (player) {
|
||||
return injectPlayerInternal(player, injectionPoint, phase);
|
||||
}
|
||||
}
|
||||
|
||||
// Unsafe variant of the above
|
||||
private PlayerInjector injectPlayerInternal(Player player, Object injectionPoint, 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 && player != null && (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);
|
||||
|
||||
DataInputStream inputStream = injector.getInputStream(false);
|
||||
|
||||
Socket socket = injector.getSocket();
|
||||
SocketAddress address = socket != null ? socket.getRemoteSocketAddress() : null;
|
||||
|
||||
// Make sure the current player is not logged out
|
||||
if (socket != null && socket.isClosed()) {
|
||||
throw new PlayerLoggedOutException();
|
||||
}
|
||||
|
||||
// Guard against NPE here too
|
||||
PlayerInjector previous = address != null ? addressLookup.get(address) : null;
|
||||
|
||||
// Close any previously associated hooks before we proceed
|
||||
if (previous != null) {
|
||||
uninjectPlayer(previous.getPlayer(), false, true);
|
||||
}
|
||||
|
||||
injector.injectManager();
|
||||
|
||||
if (inputStream != null)
|
||||
dataInputLookup.put(inputStream, injector);
|
||||
if (address != null)
|
||||
addressLookup.put(address, injector);
|
||||
break;
|
||||
}
|
||||
|
||||
} catch (PlayerLoggedOutException e) {
|
||||
throw e;
|
||||
|
||||
} catch (Exception e) {
|
||||
// Mark this injection attempt as a failure
|
||||
reporter.reportDetailed(this, "Player hook " + tempHook.toString() + " failed.",
|
||||
e, player, injectionPoint, phase);
|
||||
hookFailed = true;
|
||||
}
|
||||
|
||||
// Choose the previous player hook type
|
||||
tempHook = PlayerInjectHooks.values()[tempHook.ordinal() - 1];
|
||||
|
||||
if (hookFailed)
|
||||
reporter.reportWarning(this, "Switching to " + tempHook.toString() + " instead.");
|
||||
|
||||
// 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 = injector;
|
||||
if (permanentHook != getPlayerHook(phase))
|
||||
setPlayerHook(phase, tempHook);
|
||||
|
||||
// Save injector
|
||||
if (injector != null) {
|
||||
playerInjection.put(player, injector);
|
||||
}
|
||||
}
|
||||
|
||||
return injector;
|
||||
}
|
||||
|
||||
private void cleanupHook(PlayerInjector injector) {
|
||||
// Clean up as much as possible
|
||||
try {
|
||||
if (injector != null)
|
||||
injector.cleanupAll();
|
||||
} catch (Exception ex) {
|
||||
reporter.reportDetailed(this, "Cleaing up after player hook failed.", ex, injector);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke special routines for handling disconnect before a player is uninjected.
|
||||
* @param player - player to process.
|
||||
*/
|
||||
public void handleDisconnect(Player player) {
|
||||
PlayerInjector injector = getInjector(player);
|
||||
|
||||
if (injector != null) {
|
||||
injector.handleDisconnect();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters the given player.
|
||||
* @param player - player to unregister.
|
||||
* @return TRUE if a player has been uninjected, FALSE otherwise.
|
||||
*/
|
||||
public boolean uninjectPlayer(Player player) {
|
||||
return uninjectPlayer(player, true, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters the given player.
|
||||
* @param player - player to unregister.
|
||||
* @param removeAuxiliary - TRUE to remove auxiliary information, such as input stream and address.
|
||||
* @return TRUE if a player has been uninjected, FALSE otherwise.
|
||||
*/
|
||||
public boolean uninjectPlayer(Player player, boolean removeAuxiliary) {
|
||||
return uninjectPlayer(player, removeAuxiliary, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters the given player.
|
||||
* @param player - player to unregister.
|
||||
* @param removeAuxiliary - TRUE to remove auxiliary information, such as input stream and address.
|
||||
* @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 removeAuxiliary, boolean prepareNextHook) {
|
||||
if (!hasClosed && player != null) {
|
||||
|
||||
PlayerInjector injector = playerInjection.remove(player);
|
||||
|
||||
if (injector != null) {
|
||||
DataInputStream input = injector.getInputStream(true);
|
||||
InetSocketAddress address = player.getAddress();
|
||||
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, "Unable to fully revert old injector. May cause conflicts.", e);
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up
|
||||
if (removeAuxiliary) {
|
||||
if (input != null)
|
||||
dataInputLookup.remove(input);
|
||||
if (address != null)
|
||||
addressLookup.remove(address);
|
||||
}
|
||||
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.
|
||||
* @param serverHandler - whether or not the net server handler has already been created.
|
||||
* @return TRUE if a player has been uninjected, FALSE otherwise.
|
||||
*/
|
||||
public boolean uninjectPlayer(InetSocketAddress address) {
|
||||
if (!hasClosed && address != null) {
|
||||
PlayerInjector injector = addressLookup.get(address);
|
||||
|
||||
// Clean up
|
||||
if (injector != null)
|
||||
uninjectPlayer(injector.getPlayer(), false, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the given packet to the given reciever.
|
||||
* @param reciever - 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.
|
||||
*/
|
||||
public void sendServerPacket(Player reciever, PacketContainer packet, boolean filters) throws InvocationTargetException {
|
||||
PlayerInjector injector = getInjector(reciever);
|
||||
|
||||
// Send the packet, or drop it completely
|
||||
if (injector != null)
|
||||
injector.sendServerPacket(packet.getHandle(), filters);
|
||||
else
|
||||
reporter.reportWarning(this, String.format(
|
||||
"Unable to send packet %s (%s): Player %s has logged out.",
|
||||
packet.getID(), packet, reciever.getName()
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a packet as if it were sent by the given player.
|
||||
* @param player - the sender.
|
||||
* @param mcPacket - the packet to process.
|
||||
* @throws IllegalAccessException If the reflection machinery failed.
|
||||
* @throws InvocationTargetException If the underlying method caused an error.
|
||||
*/
|
||||
public void processPacket(Player player, Packet mcPacket) throws IllegalAccessException, InvocationTargetException {
|
||||
|
||||
PlayerInjector injector = getInjector(player);
|
||||
|
||||
// Process the given packet, or simply give up
|
||||
if (injector != null)
|
||||
injector.processPacket(mcPacket);
|
||||
else
|
||||
reporter.reportWarning(this, String.format(
|
||||
"Unable to receieve packet %s. Player %s has logged out.",
|
||||
mcPacket, player.getName()
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
return playerInjection.get(player);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the given listeners are valid.
|
||||
* @param listeners - listeners to check.
|
||||
*/
|
||||
public void checkListener(Set<PacketListener> listeners) {
|
||||
// Make sure the current listeners are compatible
|
||||
if (lastSuccessfulHook != null) {
|
||||
for (PacketListener listener : listeners) {
|
||||
checkListener(listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a listener is valid or not.
|
||||
* <p>
|
||||
* If not, a warning will be printed to the console.
|
||||
* @param listener - listener to check.
|
||||
*/
|
||||
public void checkListener(PacketListener listener) {
|
||||
if (lastSuccessfulHook != null) {
|
||||
UnsupportedListener result = lastSuccessfulHook.checkListener(listener);
|
||||
|
||||
// We won't prevent the listener, as it may still have valid packets
|
||||
if (result != null) {
|
||||
reporter.reportWarning(this, "Cannot fully register listener for " +
|
||||
PacketAdapter.getPluginName(listener) + ": " + result.toString());
|
||||
|
||||
// These are illegal
|
||||
for (int packetID : result.getPackets())
|
||||
removePacketHandler(packetID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the current list of registered sending listeners.
|
||||
* @return List of the sending listeners's packet IDs.
|
||||
*/
|
||||
public Set<Integer> getSendingFilters() {
|
||||
return sendingFilters.toSet();
|
||||
}
|
||||
|
||||
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 (serverInjection != null)
|
||||
serverInjection.cleanupAll();
|
||||
if (netLoginInjector != null)
|
||||
netLoginInjector.cleanupAll();
|
||||
serverInjection = null;
|
||||
netLoginInjector = null;
|
||||
hasClosed = true;
|
||||
|
||||
playerInjection.clear();
|
||||
dataInputLookup.clear();
|
||||
addressLookup.clear();
|
||||
invoker = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inform the current PlayerInjector that it should update the DataInputStream next.
|
||||
* @param player - the player to update.
|
||||
*/
|
||||
public void scheduleDataInputRefresh(Player player) {
|
||||
final PlayerInjector injector = getInjector(player);
|
||||
final DataInputStream old = injector.getInputStream(true);
|
||||
|
||||
// Update the DataInputStream
|
||||
if (injector != null) {
|
||||
injector.scheduleAction(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
dataInputLookup.remove(old);
|
||||
dataInputLookup.put(injector.getInputStream(false), injector);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,617 +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 net.minecraft.server.EntityPlayer;
|
||||
import net.minecraft.server.NetLoginHandler;
|
||||
import net.minecraft.server.Packet;
|
||||
import net.sf.cglib.proxy.Factory;
|
||||
|
||||
import org.bukkit.craftbukkit.entity.CraftPlayer;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import com.comphenix.protocol.Packets;
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
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.reflect.FieldUtils;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.comphenix.protocol.reflect.StructureModifier;
|
||||
import com.comphenix.protocol.reflect.VolatileField;
|
||||
|
||||
abstract class PlayerInjector {
|
||||
|
||||
// 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 inputField;
|
||||
protected static Field netHandlerField;
|
||||
protected static Field socketField;
|
||||
|
||||
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 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
|
||||
protected Socket socket;
|
||||
|
||||
// The packet manager and filters
|
||||
protected ListenerInvoker invoker;
|
||||
|
||||
// Previous data input
|
||||
protected DataInputStream cachedInput;
|
||||
|
||||
// Handle errors
|
||||
protected ErrorReporter reporter;
|
||||
|
||||
// Scheduled action on the next packet event
|
||||
protected Runnable scheduledAction;
|
||||
|
||||
// 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;
|
||||
Player updatedPlayer;
|
||||
|
||||
public PlayerInjector(ErrorReporter reporter, Player player, ListenerInvoker invoker) throws IllegalAccessException {
|
||||
this.reporter = reporter;
|
||||
this.player = player;
|
||||
this.invoker = invoker;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the notch (NMS) entity player object.
|
||||
* @param player - the player to retrieve.
|
||||
* @return Notch player object.
|
||||
*/
|
||||
protected EntityPlayer getEntityPlayer(Player player) {
|
||||
CraftPlayer craft = (CraftPlayer) player;
|
||||
return craft.getHandle();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(injectionSource);
|
||||
else if (injectionSource instanceof NetLoginHandler)
|
||||
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(Object player) {
|
||||
|
||||
EntityPlayer notchEntity = getEntityPlayer((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");
|
||||
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");
|
||||
initializeNetworkManager(networkManagerField, serverHandler);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the player injector for a NetLoginHandler instead.
|
||||
* @param netLoginHandler - the net login handler to inject.
|
||||
*/
|
||||
public void initializeLogin(Object netLoginHandler) {
|
||||
if (!hasInitialized) {
|
||||
hasInitialized = true;
|
||||
loginHandler = netLoginHandler;
|
||||
|
||||
if (netLoginNetworkField == null)
|
||||
netLoginNetworkField = FuzzyReflection.fromObject(netLoginHandler).getFieldByType(".*NetworkManager");
|
||||
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", Packet.class );
|
||||
|
||||
// And the data input stream that we'll use to identify a player
|
||||
if (inputField == null)
|
||||
inputField = FuzzyReflection.fromObject(networkManager, true).
|
||||
getFieldByType("java\\.io\\.DataInputStream");
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public Socket getSocket() throws IllegalAccessException {
|
||||
try {
|
||||
if (socketField == null)
|
||||
socketField = FuzzyReflection.fromObject(networkManager).getFieldListByType(Socket.class).get(0);
|
||||
if (socket == null)
|
||||
socket = (Socket) FieldUtils.readField(socketField, networkManager);
|
||||
return socket;
|
||||
|
||||
} catch (IndexOutOfBoundsException e) {
|
||||
throw new IllegalAccessException("Unable to read the socket field.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the associated address of this player.
|
||||
* @return The associated address.
|
||||
* @throws IllegalAccessException If we're unable to read the socket field.
|
||||
*/
|
||||
public SocketAddress getAddress() throws IllegalAccessException {
|
||||
Socket socket = getSocket();
|
||||
|
||||
// Guard against NULL
|
||||
if (socket != null)
|
||||
return socket.getRemoteSocketAddress();
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to disconnect the current client.
|
||||
* @param message - the message to display.
|
||||
* @throws InvocationTargetException If disconnection failed.
|
||||
*/
|
||||
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) {
|
||||
disconnect = FuzzyReflection.fromObject(handler).getMethodByName("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, "Invalid argument passed to disconnect method: " + message, e, handler);
|
||||
} catch (IllegalAccessException e) {
|
||||
reporter.reportWarning(this, "Unable to access disconnect method.", e);
|
||||
}
|
||||
}
|
||||
|
||||
// Fuck it
|
||||
try {
|
||||
Socket socket = getSocket();
|
||||
|
||||
try {
|
||||
socket.close();
|
||||
} catch (IOException e) {
|
||||
reporter.reportDetailed(this, "Unable to close socket.", e, socket);
|
||||
}
|
||||
|
||||
} catch (IllegalAccessException e) {
|
||||
reporter.reportWarning(this, "Insufficient permissions. Cannot close socket.", e);
|
||||
}
|
||||
}
|
||||
|
||||
private Field getProxyField(EntityPlayer notchEntity, Field serverField) {
|
||||
|
||||
try {
|
||||
Object handler = FieldUtils.readField(serverHandlerField, notchEntity, true);
|
||||
|
||||
// Is this a Minecraft hook?
|
||||
if (handler != null && !handler.getClass().getName().startsWith("net.minecraft.server")) {
|
||||
|
||||
// This is our proxy object
|
||||
if (handler instanceof Factory)
|
||||
return null;
|
||||
|
||||
hasProxyType = true;
|
||||
reporter.reportWarning(this, "Detected server handler proxy type by another plugin. Conflict may occur!");
|
||||
|
||||
// No? Is it a Proxy type?
|
||||
try {
|
||||
FuzzyReflection reflection = FuzzyReflection.fromObject(handler, true);
|
||||
|
||||
// It might be
|
||||
return reflection.getFieldByType(".*NetServerHandler");
|
||||
|
||||
} catch (RuntimeException e) {
|
||||
// Damn
|
||||
}
|
||||
}
|
||||
|
||||
} catch (IllegalAccessException e) {
|
||||
reporter.reportWarning(this, "Unable to load server handler from proxy type.");
|
||||
}
|
||||
|
||||
// Nope, just go with it
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
|
||||
// What a mess
|
||||
try {
|
||||
if (netHandlerField == null)
|
||||
netHandlerField = FuzzyReflection.fromClass(networkManager.getClass(), true).
|
||||
getFieldByType("net\\.minecraft\\.NetHandler");
|
||||
} 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(FuzzyReflection.MINECRAFT_OBJECT);
|
||||
|
||||
} catch (RuntimeException e2) {
|
||||
throw new IllegalAccessException("Cannot locate net handler. " + e2.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// Get the handler
|
||||
if (netHandler == null)
|
||||
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 EntityPlayer getEntityPlayer(Object netHandler) throws IllegalAccessException {
|
||||
if (entityPlayerField == null)
|
||||
entityPlayerField = FuzzyReflection.fromObject(netHandler).getFieldByType(".*EntityPlayer");
|
||||
return (EntityPlayer) 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(Packet packet) throws IllegalAccessException, InvocationTargetException {
|
||||
|
||||
Object netHandler = getNetHandler();
|
||||
|
||||
// Get the process method
|
||||
if (processMethod == null) {
|
||||
try {
|
||||
processMethod = FuzzyReflection.fromClass(Packet.class).
|
||||
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 filtered - whether or not the packet will be filtered by our listeners.
|
||||
* @param InvocationTargetException If an error occured when sending the packet.
|
||||
*/
|
||||
public abstract void sendServerPacket(Packet packet, 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();
|
||||
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 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(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.
|
||||
*/
|
||||
public Packet handlePacketSending(Packet packet) {
|
||||
try {
|
||||
// Get the packet ID too
|
||||
Integer id = invoker.getPacketID(packet);
|
||||
Player currentPlayer = player;
|
||||
|
||||
// Hack #1: Handle a single scheduled action
|
||||
if (scheduledAction != null) {
|
||||
scheduledAction.run();
|
||||
scheduledAction = null;
|
||||
}
|
||||
// Hack #2
|
||||
if (updateOnLogin) {
|
||||
if (id == Packets.Server.LOGIN) {
|
||||
try {
|
||||
updatedPlayer = getEntityPlayer(getNetHandler()).getBukkitEntity();
|
||||
} catch (IllegalAccessException e) {
|
||||
reporter.reportDetailed(this, "Cannot update player in PlayerEvent.", e, packet);
|
||||
}
|
||||
}
|
||||
|
||||
// This will only occur in the NetLoginHandler injection
|
||||
if (updatedPlayer != null)
|
||||
currentPlayer = updatedPlayer;
|
||||
}
|
||||
|
||||
// Make sure we're listening
|
||||
if (id != null && hasListener(id)) {
|
||||
// A packet has been sent guys!
|
||||
PacketContainer container = new PacketContainer(id, packet);
|
||||
PacketEvent event = PacketEvent.fromServer(invoker, container, currentPlayer);
|
||||
invoker.invokePacketSending(event);
|
||||
|
||||
// Cancelling is pretty simple. Just ignore the packet.
|
||||
if (event.isCancelled())
|
||||
return null;
|
||||
|
||||
// Right, remember to replace the packet again
|
||||
return event.getPacket().getHandle();
|
||||
}
|
||||
|
||||
} catch (Throwable e) {
|
||||
reporter.reportDetailed(this, "Cannot handle server packet.", e, 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) {
|
||||
if (inputField == null)
|
||||
throw new IllegalStateException("Input field is NULL.");
|
||||
if (networkManager == null)
|
||||
throw new IllegalStateException("Network manager is NULL.");
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule an action to occur on the next sent packet.
|
||||
* @param action - action to execute.
|
||||
*/
|
||||
public void scheduleAction(Runnable action) {
|
||||
scheduledAction = action;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the hooked player.
|
||||
*/
|
||||
public Player getPlayer() {
|
||||
return 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.
|
||||
*/
|
||||
public Player getUpdatedPlayer() {
|
||||
if (updatedPlayer != null)
|
||||
return updatedPlayer;
|
||||
else
|
||||
return player;
|
||||
}
|
||||
}
|
||||
-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();
|
||||
}
|
||||
}
|
||||
-163
@@ -1,163 +0,0 @@
|
||||
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.MethodInterceptor;
|
||||
import net.sf.cglib.proxy.MethodProxy;
|
||||
import net.sf.cglib.proxy.NoOp;
|
||||
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import com.comphenix.protocol.injector.PacketConstructor;
|
||||
import com.comphenix.protocol.reflect.FieldAccessException;
|
||||
|
||||
/**
|
||||
* Create fake player instances that represents pre-authenticated clients.
|
||||
*/
|
||||
class TemporaryPlayerFactory {
|
||||
|
||||
/**
|
||||
* Able to store a PlayerInjector.
|
||||
* <p>
|
||||
* A necessary hack.
|
||||
* @author Kristian
|
||||
*/
|
||||
public static class InjectContainer {
|
||||
private PlayerInjector injector;
|
||||
|
||||
public PlayerInjector getInjector() {
|
||||
return injector;
|
||||
}
|
||||
|
||||
public void setInjector(PlayerInjector injector) {
|
||||
this.injector = injector;
|
||||
}
|
||||
}
|
||||
|
||||
// Helpful constructors
|
||||
private final PacketConstructor chatPacket;
|
||||
|
||||
// Prevent too many class creations
|
||||
private static CallbackFilter callbackFilter;
|
||||
|
||||
public TemporaryPlayerFactory() {
|
||||
chatPacket = PacketConstructor.DEFAULT.withPacket(3, new Object[] { "DEMO" });
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a temporary player that supports a subset of every player command.
|
||||
* <p>
|
||||
* Supported methods include:
|
||||
* <ul>
|
||||
* <li>getPlayer()</li>
|
||||
* <li>getAddress()</li>
|
||||
* <li>getServer()</li>
|
||||
* <li>chat(String)</li>
|
||||
* <li>sendMessage(String)</li>
|
||||
* <li>sendMessage(String[])</li>
|
||||
* <li>kickPlayer(String)</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* Note that the player a player has not been assigned a name yet, and thus cannot be
|
||||
* uniquely identified. Use the
|
||||
* @param injector - the player injector used.
|
||||
* @param server - the current server.
|
||||
* @return A temporary player instance.
|
||||
*/
|
||||
public Player createTemporaryPlayer(final Server server) {
|
||||
|
||||
// Default implementation
|
||||
Callback implementation = new MethodInterceptor() {
|
||||
@Override
|
||||
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
|
||||
|
||||
String methodName = method.getName();
|
||||
PlayerInjector injector = ((InjectContainer) obj).getInjector();
|
||||
|
||||
if (injector == null)
|
||||
throw new IllegalStateException("Unable to find injector.");
|
||||
|
||||
// Use the socket to get the address
|
||||
if (methodName.equalsIgnoreCase("getName"))
|
||||
return "UNKNOWN[" + injector.getSocket().getRemoteSocketAddress() + "]";
|
||||
if (methodName.equalsIgnoreCase("getPlayer"))
|
||||
return injector.getUpdatedPlayer();
|
||||
if (methodName.equalsIgnoreCase("getAddress"))
|
||||
return injector.getAddress();
|
||||
if (methodName.equalsIgnoreCase("getServer"))
|
||||
return server;
|
||||
|
||||
try {
|
||||
// Handle send message methods
|
||||
if (methodName.equalsIgnoreCase("chat") || methodName.equalsIgnoreCase("sendMessage")) {
|
||||
Object argument = args[0];
|
||||
|
||||
// Dynamic overloading
|
||||
if (argument instanceof String) {
|
||||
return sendMessage(injector, (String) argument);
|
||||
} else if (argument instanceof String[]) {
|
||||
for (String message : (String[]) argument) {
|
||||
sendMessage(injector, message);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
} catch (InvocationTargetException e) {
|
||||
throw e.getCause();
|
||||
}
|
||||
|
||||
// Also, handle kicking
|
||||
if (methodName.equalsIgnoreCase("kickPlayer")) {
|
||||
injector.disconnect((String) args[0]);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Ignore all other methods
|
||||
throw new UnsupportedOperationException(
|
||||
"The method " + method.getName() + " is not supported for temporary players.");
|
||||
}
|
||||
};
|
||||
|
||||
// Shared callback filter
|
||||
if (callbackFilter == null) {
|
||||
callbackFilter = new CallbackFilter() {
|
||||
@Override
|
||||
public int accept(Method method) {
|
||||
// Do not override the object method or the superclass methods
|
||||
if (method.getDeclaringClass().equals(Object.class) ||
|
||||
method.getDeclaringClass().equals(InjectContainer.class))
|
||||
return 0;
|
||||
else
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// CGLib is amazing
|
||||
Enhancer ex = new Enhancer();
|
||||
ex.setSuperclass(InjectContainer.class);
|
||||
ex.setInterfaces(new Class[] { Player.class });
|
||||
ex.setCallbacks(new Callback[] { NoOp.INSTANCE, implementation });
|
||||
ex.setCallbackFilter(callbackFilter);
|
||||
|
||||
return (Player) ex.create();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a message to the given client.
|
||||
* @param injector - the injector representing the client.
|
||||
* @param message - a message.
|
||||
* @return Always NULL.
|
||||
* @throws InvocationTargetException If the message couldn't be sent.
|
||||
* @throws FieldAccessException If we were unable to construct the message packet.
|
||||
*/
|
||||
private Object sendMessage(PlayerInjector injector, String message) throws InvocationTargetException, FieldAccessException {
|
||||
injector.sendServerPacket(chatPacket.createPacket(message).getHandle(), false);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
-47
@@ -1,47 +0,0 @@
|
||||
package com.comphenix.protocol.injector.player;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
|
||||
/**
|
||||
* Represents an error message from a player injector.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
class UnsupportedListener {
|
||||
private String message;
|
||||
private int[] packets;
|
||||
|
||||
/**
|
||||
* Create a new error message.
|
||||
* @param message - the message.
|
||||
* @param packets - unsupported packets.
|
||||
*/
|
||||
public UnsupportedListener(String message, int[] packets) {
|
||||
super();
|
||||
this.message = message;
|
||||
this.packets = packets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the error message.
|
||||
* @return Error message.
|
||||
*/
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all unsupported packets.
|
||||
* @return Unsupported packets.
|
||||
*/
|
||||
public int[] getPackets() {
|
||||
return packets;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("%s (%s)", message, Joiner.on(", ").join(Arrays.asList(packets)));
|
||||
}
|
||||
}
|
||||
@@ -1,648 +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 org.bukkit.Bukkit;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.bukkit.configuration.InvalidConfigurationException;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.bukkit.plugin.PluginDescriptionFile;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* <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(Metrics.Plotter plotter); <br/>
|
||||
* void start(); <br/>
|
||||
* </code>
|
||||
*/
|
||||
class Metrics {
|
||||
|
||||
/**
|
||||
* The current revision number
|
||||
*/
|
||||
private final static int REVISION = 5;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Lock for synchronization
|
||||
*/
|
||||
private final Object optOutLock = new Object();
|
||||
|
||||
/**
|
||||
* Id of the scheduled task
|
||||
*/
|
||||
private volatile int taskId = -1;
|
||||
|
||||
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());
|
||||
|
||||
// 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");
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 Metrics 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 (taskId >= 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Begin hitting the server with glorious data
|
||||
taskId = plugin.getServer().getScheduler().scheduleAsyncRepeatingTask(plugin, new Runnable() {
|
||||
|
||||
private boolean firstPost = true;
|
||||
|
||||
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() && taskId > 0) {
|
||||
plugin.getServer().getScheduler().cancelTask(taskId);
|
||||
taskId = -1;
|
||||
// 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) {
|
||||
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) {
|
||||
Bukkit.getLogger().log(Level.INFO, "[Metrics] " + ex.getMessage());
|
||||
return true;
|
||||
} catch (InvalidConfigurationException ex) {
|
||||
Bukkit.getLogger().log(Level.INFO, "[Metrics] " + ex.getMessage());
|
||||
return true;
|
||||
}
|
||||
return configuration.getBoolean("opt-out", false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables metrics for the server by setting "opt-out" to false in the config file and starting the metrics task.
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
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 (taskId < 0) {
|
||||
start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables metrics for the server by setting "opt-out" to true in the config file and canceling the metrics task.
|
||||
*
|
||||
* @throws 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 (taskId > 0) {
|
||||
this.plugin.getServer().getScheduler().cancelTask(taskId);
|
||||
taskId = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
// The plugin's description file containg all of the plugin data such as name, version, author, etc
|
||||
final PluginDescriptionFile description = plugin.getDescription();
|
||||
|
||||
// Construct the post data
|
||||
final StringBuilder data = new StringBuilder();
|
||||
data.append(encode("guid")).append('=').append(encode(guid));
|
||||
encodeDataPair(data, "version", description.getVersion());
|
||||
encodeDataPair(data, "server", Bukkit.getVersion());
|
||||
encodeDataPair(data, "players", Integer.toString(Bukkit.getServer().getOnlinePlayers().length));
|
||||
encodeDataPair(data, "revision", String.valueOf(REVISION));
|
||||
|
||||
// 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(plugin.getDescription().getName())));
|
||||
|
||||
// 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 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 Metrics 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,81 +0,0 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol.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);
|
||||
metrics.start();
|
||||
|
||||
// Determine who is using this library
|
||||
addPluginUserGraph(metrics);
|
||||
}
|
||||
|
||||
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,511 +0,0 @@
|
||||
package com.comphenix.protocol.reflect;
|
||||
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import java.lang.reflect.AccessibleObject;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Member;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Utilities for working with fields by reflection. Adapted and refactored from
|
||||
* the dormant [reflect] Commons sandbox component.
|
||||
* <p>
|
||||
* The ability is provided to break the scoping restrictions coded by the
|
||||
* programmer. This can allow fields to be changed that shouldn't be. This
|
||||
* facility should be used with care.
|
||||
*
|
||||
* @author Apache Software Foundation
|
||||
* @author Matt Benson
|
||||
* @since 2.5
|
||||
* @version $Id: FieldUtils.java 1057009 2011-01-09 19:48:06Z niallp $
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
public class FieldUtils {
|
||||
|
||||
/**
|
||||
* FieldUtils instances should NOT be constructed in standard programming.
|
||||
* <p>
|
||||
* This constructor is public to permit tools that require a JavaBean
|
||||
* instance to operate.
|
||||
*/
|
||||
public FieldUtils() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an accessible <code>Field</code> by name respecting scope.
|
||||
* Superclasses/interfaces will be considered.
|
||||
*
|
||||
* @param cls the class to reflect, must not be null
|
||||
* @param fieldName the field name to obtain
|
||||
* @return the Field object
|
||||
* @throws IllegalArgumentException if the class or field name is null
|
||||
*/
|
||||
public static Field getField(Class cls, String fieldName) {
|
||||
Field field = getField(cls, fieldName, false);
|
||||
MemberUtils.setAccessibleWorkaround(field);
|
||||
return field;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an accessible <code>Field</code> by name breaking scope if
|
||||
* requested. Superclasses/interfaces will be considered.
|
||||
*
|
||||
* @param cls the class to reflect, must not be null
|
||||
* @param fieldName the field name to obtain
|
||||
* @param forceAccess whether to break scope restrictions using the
|
||||
* <code>setAccessible</code> method. <code>False</code> will
|
||||
* only match public fields.
|
||||
* @return the Field object
|
||||
* @throws IllegalArgumentException if the class or field name is null
|
||||
*/
|
||||
public static Field getField(final Class cls, String fieldName, boolean forceAccess) {
|
||||
if (cls == null) {
|
||||
throw new IllegalArgumentException("The class must not be null");
|
||||
}
|
||||
if (fieldName == null) {
|
||||
throw new IllegalArgumentException("The field name must not be null");
|
||||
}
|
||||
// Sun Java 1.3 has a bugged implementation of getField hence we write
|
||||
// the
|
||||
// code ourselves
|
||||
|
||||
// getField() will return the Field object with the declaring class
|
||||
// set correctly to the class that declares the field. Thus requesting
|
||||
// the
|
||||
// field on a subclass will return the field from the superclass.
|
||||
//
|
||||
// priority order for lookup:
|
||||
// searchclass private/protected/package/public
|
||||
// superclass protected/package/public
|
||||
// private/different package blocks access to further superclasses
|
||||
// implementedinterface public
|
||||
|
||||
// check up the superclass hierarchy
|
||||
for (Class acls = cls; acls != null; acls = acls.getSuperclass()) {
|
||||
try {
|
||||
Field field = acls.getDeclaredField(fieldName);
|
||||
// getDeclaredField checks for non-public scopes as well
|
||||
// and it returns accurate results
|
||||
if (!Modifier.isPublic(field.getModifiers())) {
|
||||
if (forceAccess) {
|
||||
field.setAccessible(true);
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return field;
|
||||
} catch (NoSuchFieldException ex) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
// check the public interface case. This must be manually searched for
|
||||
// in case there is a public supersuperclass field hidden by a
|
||||
// private/package
|
||||
// superclass field.
|
||||
Field match = null;
|
||||
for (Iterator intf = getAllInterfaces(cls).iterator(); intf.hasNext();) {
|
||||
try {
|
||||
Field test = ((Class) intf.next()).getField(fieldName);
|
||||
if (match != null) {
|
||||
throw new IllegalArgumentException("Reference to field " + fieldName
|
||||
+ " is ambiguous relative to " + cls
|
||||
+ "; a matching field exists on two or more implemented interfaces.");
|
||||
}
|
||||
match = test;
|
||||
} catch (NoSuchFieldException ex) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
return match;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Gets a <code>List</code> of all interfaces implemented by the given
|
||||
* class and its superclasses.</p>
|
||||
*
|
||||
* <p>The order is determined by looking through each interface in turn as
|
||||
* declared in the source file and following its hierarchy up. Then each
|
||||
* superclass is considered in the same way. Later duplicates are ignored,
|
||||
* so the order is maintained.</p>
|
||||
*
|
||||
* @param cls the class to look up, may be <code>null</code>
|
||||
* @return the <code>List</code> of interfaces in order,
|
||||
* <code>null</code> if null input
|
||||
*/
|
||||
private static List getAllInterfaces(Class cls) {
|
||||
if (cls == null) {
|
||||
return null;
|
||||
}
|
||||
List<Class> list = new ArrayList<Class>();
|
||||
|
||||
while (cls != null) {
|
||||
Class[] interfaces = cls.getInterfaces();
|
||||
for (int i = 0; i < interfaces.length; i++) {
|
||||
if (list.contains(interfaces[i]) == false) {
|
||||
list.add(interfaces[i]);
|
||||
}
|
||||
List superInterfaces = getAllInterfaces(interfaces[i]);
|
||||
for (Iterator it = superInterfaces.iterator(); it.hasNext();) {
|
||||
Class intface = (Class) it.next();
|
||||
if (list.contains(intface) == false) {
|
||||
list.add(intface);
|
||||
}
|
||||
}
|
||||
}
|
||||
cls = cls.getSuperclass();
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read an accessible static Field.
|
||||
*
|
||||
* @param field to read
|
||||
* @return the field value
|
||||
* @throws IllegalArgumentException if the field is null or not static
|
||||
* @throws IllegalAccessException if the field is not accessible
|
||||
*/
|
||||
public static Object readStaticField(Field field) throws IllegalAccessException {
|
||||
return readStaticField(field, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a static Field.
|
||||
*
|
||||
* @param field to read
|
||||
* @param forceAccess whether to break scope restrictions using the
|
||||
* <code>setAccessible</code> method.
|
||||
* @return the field value
|
||||
* @throws IllegalArgumentException if the field is null or not static
|
||||
* @throws IllegalAccessException if the field is not made accessible
|
||||
*/
|
||||
public static Object readStaticField(Field field, boolean forceAccess)
|
||||
throws IllegalAccessException {
|
||||
if (field == null) {
|
||||
throw new IllegalArgumentException("The field must not be null");
|
||||
}
|
||||
if (!Modifier.isStatic(field.getModifiers())) {
|
||||
throw new IllegalArgumentException("The field '" + field.getName() + "' is not static");
|
||||
}
|
||||
return readField(field, (Object) null, forceAccess);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the named public static field. Superclasses will be considered.
|
||||
*
|
||||
* @param cls the class to reflect, must not be null
|
||||
* @param fieldName the field name to obtain
|
||||
* @return the value of the field
|
||||
* @throws IllegalArgumentException if the class or field name is null
|
||||
* @throws IllegalAccessException if the field is not accessible
|
||||
*/
|
||||
public static Object readStaticField(Class cls, String fieldName) throws IllegalAccessException {
|
||||
return readStaticField(cls, fieldName, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the named static field. Superclasses will be considered.
|
||||
*
|
||||
* @param cls the class to reflect, must not be null
|
||||
* @param fieldName the field name to obtain
|
||||
* @param forceAccess whether to break scope restrictions using the
|
||||
* <code>setAccessible</code> method. <code>False</code> will
|
||||
* only match public fields.
|
||||
* @return the Field object
|
||||
* @throws IllegalArgumentException if the class or field name is null
|
||||
* @throws IllegalAccessException if the field is not made accessible
|
||||
*/
|
||||
public static Object readStaticField(Class cls, String fieldName, boolean forceAccess)
|
||||
throws IllegalAccessException {
|
||||
Field field = getField(cls, fieldName, forceAccess);
|
||||
if (field == null) {
|
||||
throw new IllegalArgumentException("Cannot locate field " + fieldName + " on " + cls);
|
||||
}
|
||||
// already forced access above, don't repeat it here:
|
||||
return readStaticField(field, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read an accessible Field.
|
||||
*
|
||||
* @param field the field to use
|
||||
* @param target the object to call on, may be null for static fields
|
||||
* @return the field value
|
||||
* @throws IllegalArgumentException if the field is null
|
||||
* @throws IllegalAccessException if the field is not accessible
|
||||
*/
|
||||
public static Object readField(Field field, Object target) throws IllegalAccessException {
|
||||
return readField(field, target, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a Field.
|
||||
*
|
||||
* @param field the field to use
|
||||
* @param target the object to call on, may be null for static fields
|
||||
* @param forceAccess whether to break scope restrictions using the
|
||||
* <code>setAccessible</code> method.
|
||||
* @return the field value
|
||||
* @throws IllegalArgumentException if the field is null
|
||||
* @throws IllegalAccessException if the field is not made accessible
|
||||
*/
|
||||
public static Object readField(Field field, Object target, boolean forceAccess) throws IllegalAccessException {
|
||||
if (field == null)
|
||||
throw new IllegalArgumentException("The field must not be null");
|
||||
|
||||
if (forceAccess && !field.isAccessible()) {
|
||||
field.setAccessible(true);
|
||||
} else {
|
||||
MemberUtils.setAccessibleWorkaround(field);
|
||||
}
|
||||
return field.get(target);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the named public field. Superclasses will be considered.
|
||||
*
|
||||
* @param target the object to reflect, must not be null
|
||||
* @param fieldName the field name to obtain
|
||||
* @return the value of the field
|
||||
* @throws IllegalArgumentException if the class or field name is null
|
||||
* @throws IllegalAccessException if the named field is not public
|
||||
*/
|
||||
public static Object readField(Object target, String fieldName) throws IllegalAccessException {
|
||||
return readField(target, fieldName, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the named field. Superclasses will be considered.
|
||||
*
|
||||
* @param target the object to reflect, must not be null
|
||||
* @param fieldName the field name to obtain
|
||||
* @param forceAccess whether to break scope restrictions using the
|
||||
* <code>setAccessible</code> method. <code>False</code> will
|
||||
* only match public fields.
|
||||
* @return the field value
|
||||
* @throws IllegalArgumentException if the class or field name is null
|
||||
* @throws IllegalAccessException if the named field is not made accessible
|
||||
*/
|
||||
public static Object readField(Object target, String fieldName, boolean forceAccess)
|
||||
throws IllegalAccessException {
|
||||
if (target == null) {
|
||||
throw new IllegalArgumentException("target object must not be null");
|
||||
}
|
||||
Class cls = target.getClass();
|
||||
Field field = getField(cls, fieldName, forceAccess);
|
||||
if (field == null) {
|
||||
throw new IllegalArgumentException("Cannot locate field " + fieldName + " on " + cls);
|
||||
}
|
||||
// already forced access above, don't repeat it here:
|
||||
return readField(field, target);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a public static Field.
|
||||
*
|
||||
* @param field to write
|
||||
* @param value to set
|
||||
* @throws IllegalArgumentException if the field is null or not static
|
||||
* @throws IllegalAccessException if the field is not public or is final
|
||||
*/
|
||||
public static void writeStaticField(Field field, Object value) throws IllegalAccessException {
|
||||
writeStaticField(field, value, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a static Field.
|
||||
*
|
||||
* @param field to write
|
||||
* @param value to set
|
||||
* @param forceAccess whether to break scope restrictions using the
|
||||
* <code>setAccessible</code> method. <code>False</code> will
|
||||
* only match public fields.
|
||||
* @throws IllegalArgumentException if the field is null or not static
|
||||
* @throws IllegalAccessException if the field is not made accessible or is
|
||||
* final
|
||||
*/
|
||||
public static void writeStaticField(Field field, Object value, boolean forceAccess)
|
||||
throws IllegalAccessException {
|
||||
if (field == null) {
|
||||
throw new IllegalArgumentException("The field must not be null");
|
||||
}
|
||||
if (!Modifier.isStatic(field.getModifiers())) {
|
||||
throw new IllegalArgumentException("The field '" + field.getName() + "' is not static");
|
||||
}
|
||||
writeField(field, (Object) null, value, forceAccess);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a named public static Field. Superclasses will be considered.
|
||||
*
|
||||
* @param cls Class on which the Field is to be found
|
||||
* @param fieldName to write
|
||||
* @param value to set
|
||||
* @throws IllegalArgumentException if the field cannot be located or is not
|
||||
* static
|
||||
* @throws IllegalAccessException if the field is not public or is final
|
||||
*/
|
||||
public static void writeStaticField(Class cls, String fieldName, Object value)
|
||||
throws IllegalAccessException {
|
||||
writeStaticField(cls, fieldName, value, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a named static Field. Superclasses will be considered.
|
||||
*
|
||||
* @param cls Class on which the Field is to be found
|
||||
* @param fieldName to write
|
||||
* @param value to set
|
||||
* @param forceAccess whether to break scope restrictions using the
|
||||
* <code>setAccessible</code> method. <code>False</code> will
|
||||
* only match public fields.
|
||||
* @throws IllegalArgumentException if the field cannot be located or is not
|
||||
* static
|
||||
* @throws IllegalAccessException if the field is not made accessible or is
|
||||
* final
|
||||
*/
|
||||
public static void writeStaticField(Class cls, String fieldName, Object value,
|
||||
boolean forceAccess) throws IllegalAccessException {
|
||||
Field field = getField(cls, fieldName, forceAccess);
|
||||
if (field == null) {
|
||||
throw new IllegalArgumentException("Cannot locate field " + fieldName + " on " + cls);
|
||||
}
|
||||
// already forced access above, don't repeat it here:
|
||||
writeStaticField(field, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write an accessible field.
|
||||
*
|
||||
* @param field to write
|
||||
* @param target the object to call on, may be null for static fields
|
||||
* @param value to set
|
||||
* @throws IllegalArgumentException if the field is null
|
||||
* @throws IllegalAccessException if the field is not accessible or is final
|
||||
*/
|
||||
public static void writeField(Field field, Object target, Object value)
|
||||
throws IllegalAccessException {
|
||||
writeField(field, target, value, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a field.
|
||||
*
|
||||
* @param field to write
|
||||
* @param target the object to call on, may be null for static fields
|
||||
* @param value to set
|
||||
* @param forceAccess whether to break scope restrictions using the
|
||||
* <code>setAccessible</code> method. <code>False</code> will
|
||||
* only match public fields.
|
||||
* @throws IllegalArgumentException if the field is null
|
||||
* @throws IllegalAccessException if the field is not made accessible or is
|
||||
* final
|
||||
*/
|
||||
public static void writeField(Field field, Object target, Object value, boolean forceAccess)
|
||||
throws IllegalAccessException {
|
||||
if (field == null) {
|
||||
throw new IllegalArgumentException("The field must not be null");
|
||||
}
|
||||
if (forceAccess && !field.isAccessible()) {
|
||||
field.setAccessible(true);
|
||||
} else {
|
||||
MemberUtils.setAccessibleWorkaround(field);
|
||||
}
|
||||
field.set(target, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a public field. Superclasses will be considered.
|
||||
*
|
||||
* @param target the object to reflect, must not be null
|
||||
* @param fieldName the field name to obtain
|
||||
* @param value to set
|
||||
* @throws IllegalArgumentException if <code>target</code> or
|
||||
* <code>fieldName</code> is null
|
||||
* @throws IllegalAccessException if the field is not accessible
|
||||
*/
|
||||
public static void writeField(Object target, String fieldName, Object value)
|
||||
throws IllegalAccessException {
|
||||
writeField(target, fieldName, value, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a field. Superclasses will be considered.
|
||||
*
|
||||
* @param target the object to reflect, must not be null
|
||||
* @param fieldName the field name to obtain
|
||||
* @param value to set
|
||||
* @param forceAccess whether to break scope restrictions using the
|
||||
* <code>setAccessible</code> method. <code>False</code> will
|
||||
* only match public fields.
|
||||
* @throws IllegalArgumentException if <code>target</code> or
|
||||
* <code>fieldName</code> is null
|
||||
* @throws IllegalAccessException if the field is not made accessible
|
||||
*/
|
||||
public static void writeField(Object target, String fieldName, Object value, boolean forceAccess)
|
||||
throws IllegalAccessException {
|
||||
if (target == null) {
|
||||
throw new IllegalArgumentException("target object must not be null");
|
||||
}
|
||||
Class cls = target.getClass();
|
||||
Field field = getField(cls, fieldName, forceAccess);
|
||||
if (field == null) {
|
||||
throw new IllegalArgumentException("Cannot locate declared field " + cls.getName()
|
||||
+ "." + fieldName);
|
||||
}
|
||||
// already forced access above, don't repeat it here:
|
||||
writeField(field, target, value);
|
||||
}
|
||||
|
||||
// Useful member methods
|
||||
private static class MemberUtils {
|
||||
|
||||
private static final int ACCESS_TEST = Modifier.PUBLIC | Modifier.PROTECTED
|
||||
| Modifier.PRIVATE;
|
||||
|
||||
public static void setAccessibleWorkaround(AccessibleObject o) {
|
||||
if (o == null || o.isAccessible()) {
|
||||
return;
|
||||
}
|
||||
Member m = (Member) o;
|
||||
if (Modifier.isPublic(m.getModifiers())
|
||||
&& isPackageAccess(m.getDeclaringClass().getModifiers())) {
|
||||
try {
|
||||
o.setAccessible(true);
|
||||
} catch (SecurityException e) { // NOPMD
|
||||
// ignore in favor of subsequent IllegalAccessException
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether a given set of modifiers implies package access.
|
||||
*
|
||||
* @param modifiers to test
|
||||
* @return true unless package/protected/private modifier detected
|
||||
*/
|
||||
public static boolean isPackageAccess(int modifiers) {
|
||||
return (modifiers & ACCESS_TEST) == 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,396 +0,0 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol.reflect;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Retrieves fields and methods by signature, not just name.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public class FuzzyReflection {
|
||||
|
||||
/**
|
||||
* Matches a Minecraft object.
|
||||
*/
|
||||
public static final String MINECRAFT_OBJECT = "net\\.minecraft(\\.\\w+)+";
|
||||
|
||||
// The class we're actually representing
|
||||
private Class<?> source;
|
||||
|
||||
// Whether or not to lookup private members
|
||||
private boolean forceAccess;
|
||||
|
||||
public FuzzyReflection(Class<?> source, boolean forceAccess) {
|
||||
this.source = source;
|
||||
this.forceAccess = forceAccess;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a fuzzy reflection instance from a given class.
|
||||
* @param source - the class we'll use.
|
||||
* @return A fuzzy reflection instance.
|
||||
*/
|
||||
public static FuzzyReflection fromClass(Class<?> source) {
|
||||
return fromClass(source, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a fuzzy reflection instance from a given class.
|
||||
* @param source - the class we'll use.
|
||||
* @param forceAccess - whether or not to override scope restrictions.
|
||||
* @return A fuzzy reflection instance.
|
||||
*/
|
||||
public static FuzzyReflection fromClass(Class<?> source, boolean forceAccess) {
|
||||
return new FuzzyReflection(source, forceAccess);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a fuzzy reflection instance from an object.
|
||||
* @param reference - the object we'll use.
|
||||
* @return A fuzzy reflection instance that uses the class of the given object.
|
||||
*/
|
||||
public static FuzzyReflection fromObject(Object reference) {
|
||||
return new FuzzyReflection(reference.getClass(), false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a fuzzy reflection instance from an object.
|
||||
* @param reference - the object we'll use.
|
||||
* @param forceAccess - whether or not to override scope restrictions.
|
||||
* @return A fuzzy reflection instance that uses the class of the given object.
|
||||
*/
|
||||
public static FuzzyReflection fromObject(Object reference, boolean forceAccess) {
|
||||
return new FuzzyReflection(reference.getClass(), forceAccess);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the underlying class.
|
||||
*/
|
||||
public Class<?> getSource() {
|
||||
return source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a method by looking at its name.
|
||||
* @param nameRegex - regular expression that will match method names.
|
||||
* @return The first method that satisfies the regular expression.
|
||||
* @throws IllegalArgumentException If the method cannot be found.
|
||||
*/
|
||||
public Method getMethodByName(String nameRegex) {
|
||||
|
||||
Pattern match = Pattern.compile(nameRegex);
|
||||
|
||||
for (Method method : getMethods()) {
|
||||
if (match.matcher(method.getName()).matches()) {
|
||||
// Right - this is probably it.
|
||||
return method;
|
||||
}
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("Unable to find a method with the pattern " +
|
||||
nameRegex + " in " + source.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a method by looking at the parameter types only.
|
||||
* @param name - potential name of the method. Only used by the error mechanism.
|
||||
* @param args - parameter types of the method to find.
|
||||
* @return The first method that satisfies the parameter types.
|
||||
* @throws IllegalArgumentException If the method cannot be found.
|
||||
*/
|
||||
public Method getMethodByParameters(String name, Class<?>... args) {
|
||||
|
||||
// Find the correct method to call
|
||||
for (Method method : getMethods()) {
|
||||
if (Arrays.equals(method.getParameterTypes(), args)) {
|
||||
return method;
|
||||
}
|
||||
}
|
||||
|
||||
// That sucks
|
||||
throw new IllegalArgumentException("Unable to find " + name + " in " + source.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a method by looking at the parameter types and return type only.
|
||||
* @param name - potential name of the method. Only used by the error mechanism.
|
||||
* @param returnType - return type of the method to find.
|
||||
* @param args - parameter types of the method to find.
|
||||
* @return The first method that satisfies the parameter types.
|
||||
* @throws IllegalArgumentException If the method cannot be found.
|
||||
*/
|
||||
public Method getMethodByParameters(String name, Class<?> returnType, Class<?>[] args) {
|
||||
// Find the correct method to call
|
||||
List<Method> methods = getMethodListByParameters(returnType, args);
|
||||
|
||||
if (methods.size() > 0) {
|
||||
return methods.get(0);
|
||||
} else {
|
||||
// That sucks
|
||||
throw new IllegalArgumentException("Unable to find " + name + " in " + source.getName());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a method by looking at the parameter types and return type only.
|
||||
* @param name - potential name of the method. Only used by the error mechanism.
|
||||
* @param returnTypeRegex - regular expression matching the return type of the method to find.
|
||||
* @param argsRegex - regular expressions of the matching parameter types.
|
||||
* @return The first method that satisfies the parameter types.
|
||||
* @throws IllegalArgumentException If the method cannot be found.
|
||||
*/
|
||||
public Method getMethodByParameters(String name, String returnTypeRegex, String[] argsRegex) {
|
||||
|
||||
Pattern match = Pattern.compile(returnTypeRegex);
|
||||
Pattern[] argMatch = new Pattern[argsRegex.length];
|
||||
|
||||
for (int i = 0; i < argsRegex.length; i++) {
|
||||
argMatch[i] = Pattern.compile(argsRegex[i]);
|
||||
}
|
||||
|
||||
// Find the correct method to call
|
||||
for (Method method : getMethods()) {
|
||||
if (match.matcher(method.getReturnType().getName()).matches()) {
|
||||
if (matchParameters(argMatch, method.getParameterTypes()))
|
||||
return method;
|
||||
}
|
||||
}
|
||||
|
||||
// That sucks
|
||||
throw new IllegalArgumentException("Unable to find " + name + " in " + source.getName());
|
||||
}
|
||||
|
||||
private boolean matchParameters(Pattern[] parameterMatchers, Class<?>[] argTypes) {
|
||||
if (parameterMatchers.length != argTypes.length)
|
||||
throw new IllegalArgumentException("Arrays must have the same cardinality.");
|
||||
|
||||
// Check types against the regular expressions
|
||||
for (int i = 0; i < argTypes.length; i++) {
|
||||
if (!parameterMatchers[i].matcher(argTypes[i].getName()).matches())
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves every method that has the given parameter types and return type.
|
||||
* @param returnType - return type of the method to find.
|
||||
* @param args - parameter types of the method to find.
|
||||
* @return Every method that satisfies the given constraints.
|
||||
*/
|
||||
public List<Method> getMethodListByParameters(Class<?> returnType, Class<?>[] args) {
|
||||
|
||||
List<Method> methods = new ArrayList<Method>();
|
||||
|
||||
// Find the correct method to call
|
||||
for (Method method : getMethods()) {
|
||||
if (method.getReturnType().equals(returnType) && Arrays.equals(method.getParameterTypes(), args)) {
|
||||
methods.add(method);
|
||||
}
|
||||
}
|
||||
|
||||
return methods;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a field by name.
|
||||
* @param nameRegex - regular expression that will match a field name.
|
||||
* @return The first field to match the given expression.
|
||||
* @throws IllegalArgumentException If the field cannot be found.
|
||||
*/
|
||||
public Field getFieldByName(String nameRegex) {
|
||||
|
||||
Pattern match = Pattern.compile(nameRegex);
|
||||
|
||||
for (Field field : getFields()) {
|
||||
if (match.matcher(field.getName()).matches()) {
|
||||
// Right - this is probably it.
|
||||
return field;
|
||||
}
|
||||
}
|
||||
|
||||
// Looks like we're outdated. Too bad.
|
||||
throw new IllegalArgumentException("Unable to find a field with the pattern " +
|
||||
nameRegex + " in " + source.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the first field with a type equal to or more specific to the given type.
|
||||
* @param name - name the field probably is given. This will only be used in the error message.
|
||||
* @param type - type of the field to find.
|
||||
* @return The first field with a type that is an instance of the given type.
|
||||
*/
|
||||
public Field getFieldByType(String name, Class<?> type) {
|
||||
|
||||
List<Field> fields = getFieldListByType(type);
|
||||
|
||||
if (fields.size() > 0) {
|
||||
return fields.get(0);
|
||||
} else {
|
||||
// Looks like we're outdated. Too bad.
|
||||
throw new IllegalArgumentException(String.format("Unable to find a field %s with the type %s in %s",
|
||||
name, type.getName(), source.getName())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves every field with a type equal to or more specific to the given type.
|
||||
* @param type - type of the fields to find.
|
||||
* @return Every field with a type that is an instance of the given type.
|
||||
*/
|
||||
public List<Field> getFieldListByType(Class<?> type) {
|
||||
|
||||
List<Field> fields = new ArrayList<Field>();
|
||||
|
||||
// Field with a compatible type
|
||||
for (Field field : getFields()) {
|
||||
// A assignable from B -> B instanceOf A
|
||||
if (type.isAssignableFrom(field.getType())) {
|
||||
fields.add(field);
|
||||
}
|
||||
}
|
||||
|
||||
return fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a field by type.
|
||||
* <p>
|
||||
* Note that the type is matched using the full canonical representation, i.e.:
|
||||
* <ul>
|
||||
* <li>java.util.List</li>
|
||||
* <li>net.comphenix.xp.ExperienceMod</li>
|
||||
* </ul>
|
||||
* @param typeRegex - regular expression that will match the field type.
|
||||
* @return The first field with a type that matches the given regular expression.
|
||||
* @throws IllegalArgumentException If the field cannot be found.
|
||||
*/
|
||||
public Field getFieldByType(String typeRegex) {
|
||||
|
||||
Pattern match = Pattern.compile(typeRegex);
|
||||
|
||||
// Like above, only here we test the field type
|
||||
for (Field field : getFields()) {
|
||||
String name = field.getType().getName();
|
||||
|
||||
if (match.matcher(name).matches()) {
|
||||
return field;
|
||||
}
|
||||
}
|
||||
|
||||
// Looks like we're outdated. Too bad.
|
||||
throw new IllegalArgumentException("Unable to find a field with the type " +
|
||||
typeRegex + " in " + source.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a field by type.
|
||||
* <p>
|
||||
* Note that the type is matched using the full canonical representation, i.e.:
|
||||
* <ul>
|
||||
* <li>java.util.List</li>
|
||||
* <li>net.comphenix.xp.ExperienceMod</li>
|
||||
* </ul>
|
||||
* @param typeRegex - regular expression that will match the field type.
|
||||
* @param ignored - types to ignore.
|
||||
* @return The first field with a type that matches the given regular expression.
|
||||
* @throws IllegalArgumentException If the field cannot be found.
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
public Field getFieldByType(String typeRegex, Set<Class> ignored) {
|
||||
|
||||
Pattern match = Pattern.compile(typeRegex);
|
||||
|
||||
// Like above, only here we test the field type
|
||||
for (Field field : getFields()) {
|
||||
Class type = field.getType();
|
||||
|
||||
if (!ignored.contains(type) && match.matcher(type.getName()).matches()) {
|
||||
return field;
|
||||
}
|
||||
}
|
||||
|
||||
// Looks like we're outdated. Too bad.
|
||||
throw new IllegalArgumentException("Unable to find a field with the type " +
|
||||
typeRegex + " in " + source.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all private and public fields in declared order (after JDK 1.5).
|
||||
* @return Every field.
|
||||
*/
|
||||
public Set<Field> getFields() {
|
||||
// We will only consider private fields in the declared class
|
||||
if (forceAccess)
|
||||
return setUnion(source.getDeclaredFields(), source.getFields());
|
||||
else
|
||||
return setUnion(source.getFields());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all private and public methods in declared order (after JDK 1.5).
|
||||
* @return Every method.
|
||||
*/
|
||||
public Set<Method> getMethods() {
|
||||
// We will only consider private methods in the declared class
|
||||
if (forceAccess)
|
||||
return setUnion(source.getDeclaredMethods(), source.getMethods());
|
||||
else
|
||||
return setUnion(source.getMethods());
|
||||
}
|
||||
|
||||
// Prevent duplicate fields
|
||||
private static <T> Set<T> setUnion(T[]... array) {
|
||||
Set<T> result = new LinkedHashSet<T>();
|
||||
|
||||
for (T[] elements : array) {
|
||||
for (T element : elements) {
|
||||
result.add(element);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves whether or not not to override any scope restrictions.
|
||||
* @return TRUE if we override scope, FALSE otherwise.
|
||||
*/
|
||||
public boolean isForceAccess() {
|
||||
return forceAccess;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether or not not to override any scope restrictions.
|
||||
* @param forceAccess - TRUE if we override scope, FALSE otherwise.
|
||||
*/
|
||||
public void setForceAccess(boolean forceAccess) {
|
||||
this.forceAccess = forceAccess;
|
||||
}
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol.reflect;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Set;
|
||||
|
||||
import com.google.common.collect.BiMap;
|
||||
import com.google.common.collect.HashBiMap;
|
||||
|
||||
/**
|
||||
* Represents a traditional int field enum.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public class IntEnum {
|
||||
|
||||
// Used to convert between IDs and names
|
||||
protected BiMap<Integer, String> members = HashBiMap.create();
|
||||
|
||||
/**
|
||||
* Registers every declared integer field.
|
||||
*/
|
||||
public IntEnum() {
|
||||
registerAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers every public int field as a member.
|
||||
*/
|
||||
protected void registerAll() {
|
||||
try {
|
||||
// Register every int field
|
||||
for (Field entry : this.getClass().getFields()) {
|
||||
if (entry.getType().equals(int.class)) {
|
||||
registerMember(entry.getInt(this), entry.getName());
|
||||
}
|
||||
}
|
||||
|
||||
} catch (IllegalArgumentException e) {
|
||||
e.printStackTrace();
|
||||
} catch (IllegalAccessException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a member.
|
||||
* @param id - id of member.
|
||||
* @param name - name of member.
|
||||
*/
|
||||
protected void registerMember(int id, String name) {
|
||||
members.put(id, name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether or not the given member exists.
|
||||
* @param id - the ID of the member to find.
|
||||
* @return TRUE if a member with the given ID exists, FALSE otherwise.
|
||||
*/
|
||||
public boolean hasMember(int id) {
|
||||
return members.containsKey(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the ID of the member with the given name.
|
||||
* @param name - name of member to retrieve.
|
||||
* @return ID of the member, or NULL if not found.
|
||||
*/
|
||||
public Integer valueOf(String name) {
|
||||
return members.inverse().get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the name of the member with the given id.
|
||||
* @param id - id of the member to retrieve.
|
||||
* @return Declared name of the member, or NULL if not found.
|
||||
*/
|
||||
public String getDeclaredName(Integer id) {
|
||||
return members.get(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the ID of every registered member.
|
||||
* @return Enumeration of every value.
|
||||
*/
|
||||
public Set<Integer> values() {
|
||||
return members.keySet();
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,87 +0,0 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol.reflect;
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
|
||||
/**
|
||||
* Can copy an object field by field.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public class ObjectCloner {
|
||||
|
||||
// Cache structure modifiers
|
||||
@SuppressWarnings("rawtypes")
|
||||
private static ConcurrentMap<Class, StructureModifier<Object>> cache =
|
||||
new ConcurrentHashMap<Class, StructureModifier<Object>>();
|
||||
|
||||
/**
|
||||
* Copy every field in object A to object B.
|
||||
* <p>
|
||||
* The two objects must have the same number of fields of the same type.
|
||||
* @param source - fields to copy.
|
||||
* @param destination - fields to copy to.
|
||||
* @param commonType - type containing each field to copy.
|
||||
*/
|
||||
public static void copyTo(Object source, Object destination, Class<?> commonType) {
|
||||
|
||||
if (source == null)
|
||||
throw new IllegalArgumentException("Source cannot be NULL");
|
||||
if (destination == null)
|
||||
throw new IllegalArgumentException("Destination cannot be NULL");
|
||||
|
||||
StructureModifier<Object> modifier = cache.get(commonType);
|
||||
|
||||
// Create the structure modifier if we haven't already
|
||||
if (modifier == null) {
|
||||
StructureModifier<Object> value = new StructureModifier<Object>(commonType, null, false);
|
||||
modifier = cache.putIfAbsent(commonType, value);
|
||||
|
||||
if (modifier == null)
|
||||
modifier = value;
|
||||
}
|
||||
|
||||
// Add target
|
||||
StructureModifier<Object> modifierSource = modifier.withTarget(source);
|
||||
StructureModifier<Object> modifierDest = modifier.withTarget(destination);
|
||||
|
||||
// Copy every field
|
||||
try {
|
||||
for (int i = 0; i < modifierSource.size(); i++) {
|
||||
Object value = modifierSource.read(i);
|
||||
modifierDest.write(i, value);
|
||||
|
||||
// System.out.println(String.format("Writing value %s to %s",
|
||||
// value, modifier.getFields().get(i).getName()));
|
||||
}
|
||||
|
||||
// Copy private fields underneath
|
||||
Class<?> superclass = commonType.getSuperclass();
|
||||
|
||||
if (!superclass.equals(Object.class)) {
|
||||
copyTo(source, destination, superclass);
|
||||
}
|
||||
|
||||
} catch (FieldAccessException e) {
|
||||
throw new RuntimeException("Unable to copy fields from " + commonType.getName(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,124 +0,0 @@
|
||||
package com.comphenix.protocol.reflect;
|
||||
|
||||
/*
|
||||
* Copyright (C) 2008 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Contains static utility methods pertaining to primitive types and their
|
||||
* corresponding wrapper types.
|
||||
*
|
||||
* @author Kevin Bourrillion
|
||||
*/
|
||||
public final class PrimitiveUtils {
|
||||
private PrimitiveUtils() {
|
||||
}
|
||||
|
||||
/** A map from primitive types to their corresponding wrapper types. */
|
||||
private static final Map<Class<?>, Class<?>> PRIMITIVE_TO_WRAPPER_TYPE;
|
||||
|
||||
/** A map from wrapper types to their corresponding primitive types. */
|
||||
private static final Map<Class<?>, Class<?>> WRAPPER_TO_PRIMITIVE_TYPE;
|
||||
|
||||
// Sad that we can't use a BiMap. :(
|
||||
static {
|
||||
Map<Class<?>, Class<?>> primToWrap = new HashMap<Class<?>, Class<?>>(16);
|
||||
Map<Class<?>, Class<?>> wrapToPrim = new HashMap<Class<?>, Class<?>>(16);
|
||||
|
||||
add(primToWrap, wrapToPrim, boolean.class, Boolean.class);
|
||||
add(primToWrap, wrapToPrim, byte.class, Byte.class);
|
||||
add(primToWrap, wrapToPrim, char.class, Character.class);
|
||||
add(primToWrap, wrapToPrim, double.class, Double.class);
|
||||
add(primToWrap, wrapToPrim, float.class, Float.class);
|
||||
add(primToWrap, wrapToPrim, int.class, Integer.class);
|
||||
add(primToWrap, wrapToPrim, long.class, Long.class);
|
||||
add(primToWrap, wrapToPrim, short.class, Short.class);
|
||||
add(primToWrap, wrapToPrim, void.class, Void.class);
|
||||
|
||||
PRIMITIVE_TO_WRAPPER_TYPE = Collections.unmodifiableMap(primToWrap);
|
||||
WRAPPER_TO_PRIMITIVE_TYPE = Collections.unmodifiableMap(wrapToPrim);
|
||||
}
|
||||
|
||||
private static void add(Map<Class<?>, Class<?>> forward,
|
||||
Map<Class<?>, Class<?>> backward, Class<?> key, Class<?> value) {
|
||||
forward.put(key, value);
|
||||
backward.put(value, key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this type is a primitive.
|
||||
*/
|
||||
public static boolean isPrimitive(Type type) {
|
||||
return PRIMITIVE_TO_WRAPPER_TYPE.containsKey(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if {@code type} is one of the nine primitive-wrapper
|
||||
* types, such as {@link Integer}.
|
||||
*
|
||||
* @see Class#isPrimitive
|
||||
*/
|
||||
public static boolean isWrapperType(Type type) {
|
||||
return WRAPPER_TO_PRIMITIVE_TYPE.containsKey(checkNotNull(type));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the corresponding wrapper type of {@code type} if it is a
|
||||
* primitive type; otherwise returns {@code type} itself. Idempotent.
|
||||
*
|
||||
* <pre>
|
||||
* wrap(int.class) == Integer.class
|
||||
* wrap(Integer.class) == Integer.class
|
||||
* wrap(String.class) == String.class
|
||||
* </pre>
|
||||
*/
|
||||
public static <T> Class<T> wrap(Class<T> type) {
|
||||
// cast is safe: long.class and Long.class are both of type Class<Long>
|
||||
@SuppressWarnings("unchecked")
|
||||
Class<T> wrapped = (Class<T>) PRIMITIVE_TO_WRAPPER_TYPE
|
||||
.get(checkNotNull(type));
|
||||
return (wrapped == null) ? type : wrapped;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the corresponding primitive type of {@code type} if it is a
|
||||
* wrapper type; otherwise returns {@code type} itself. Idempotent.
|
||||
*
|
||||
* <pre>
|
||||
* unwrap(Integer.class) == int.class
|
||||
* unwrap(int.class) == int.class
|
||||
* unwrap(String.class) == String.class
|
||||
* </pre>
|
||||
*/
|
||||
public static <T> Class<T> unwrap(Class<T> type) {
|
||||
// cast is safe: long.class and Long.class are both of type Class<Long>
|
||||
@SuppressWarnings("unchecked")
|
||||
Class<T> unwrapped = (Class<T>) WRAPPER_TO_PRIMITIVE_TYPE
|
||||
.get(checkNotNull(type));
|
||||
return (unwrapped == null) ? type : unwrapped;
|
||||
}
|
||||
|
||||
public static <T> T checkNotNull(T obj) {
|
||||
if (obj == null) {
|
||||
throw new NullPointerException();
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
@@ -1,463 +0,0 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol.reflect;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import com.comphenix.protocol.reflect.compiler.BackgroundCompiler;
|
||||
import com.comphenix.protocol.reflect.instances.DefaultInstances;
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
/**
|
||||
* Provides list-oriented access to the fields of a Minecraft packet.
|
||||
* <p>
|
||||
* Implemented by using reflection. Use a CompiledStructureModifier, if speed is essential.
|
||||
*
|
||||
* @author Kristian
|
||||
* @param <TField> Type of the fields to retrieve.
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
public class StructureModifier<TField> {
|
||||
|
||||
// Object and its type
|
||||
protected Class targetType;
|
||||
protected Object target;
|
||||
|
||||
// Converter. May be NULL.
|
||||
protected EquivalentConverter<TField> converter;
|
||||
|
||||
// The fields to read in order
|
||||
protected Class fieldType;
|
||||
protected List<Field> data = new ArrayList<Field>();
|
||||
|
||||
// Improved default values
|
||||
protected Map<Field, Integer> defaultFields;
|
||||
|
||||
// Cache of previous types
|
||||
protected Map<Class, StructureModifier> subtypeCache;
|
||||
|
||||
// Whether or subclasses should handle conversion
|
||||
protected boolean customConvertHandling;
|
||||
|
||||
/**
|
||||
* Creates a structure modifier.
|
||||
* @param targetType - the structure to modify.
|
||||
* @param superclassExclude - a superclass to exclude.
|
||||
* @param requireDefault - whether or not we will be using writeDefaults().
|
||||
*/
|
||||
public StructureModifier(Class targetType, Class superclassExclude, boolean requireDefault) {
|
||||
List<Field> fields = getFields(targetType, superclassExclude);
|
||||
Map<Field, Integer> defaults = requireDefault ? generateDefaultFields(fields) : new HashMap<Field, Integer>();
|
||||
|
||||
initialize(targetType, Object.class, fields, defaults, null, new ConcurrentHashMap<Class, StructureModifier>());
|
||||
}
|
||||
|
||||
/**
|
||||
* Consumers of this method should call "initialize".
|
||||
*/
|
||||
protected StructureModifier() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize using the same field types.
|
||||
* @param other - information to set.
|
||||
*/
|
||||
protected void initialize(StructureModifier<TField> other) {
|
||||
initialize(other.targetType, other.fieldType, other.data,
|
||||
other.defaultFields, other.converter, other.subtypeCache);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize every field of this class.
|
||||
* @param targetType - type of the object we're reading and writing from.
|
||||
* @param fieldType - the common type of the fields we're modifying.
|
||||
* @param data - list of fields to modify.
|
||||
* @param defaultFields - list of fields that will be automatically initialized.
|
||||
* @param converter - converts between the common field type and the actual type the consumer expects.
|
||||
* @param subTypeCache - a structure modifier cache.
|
||||
*/
|
||||
protected void initialize(Class targetType, Class fieldType,
|
||||
List<Field> data, Map<Field, Integer> defaultFields,
|
||||
EquivalentConverter<TField> converter, Map<Class, StructureModifier> subTypeCache) {
|
||||
this.targetType = targetType;
|
||||
this.fieldType = fieldType;
|
||||
this.data = data;
|
||||
this.defaultFields = defaultFields;
|
||||
this.converter = converter;
|
||||
this.subtypeCache = subTypeCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the value of a field given its index.
|
||||
* @param fieldIndex - index of the field.
|
||||
* @return Value of the field.
|
||||
* @throws FieldAccessException The field doesn't exist, or it cannot be accessed under the current security contraints.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public TField read(int fieldIndex) throws FieldAccessException {
|
||||
if (fieldIndex < 0 || fieldIndex >= data.size())
|
||||
throw new FieldAccessException("Field index must be within 0 - count",
|
||||
new IndexOutOfBoundsException("Out of bounds"));
|
||||
if (target == null)
|
||||
throw new IllegalStateException("Cannot read from a NULL target.");
|
||||
|
||||
try {
|
||||
Object result = FieldUtils.readField(data.get(fieldIndex), target, true);
|
||||
|
||||
// Use the converter, if we have it
|
||||
if (needConversion())
|
||||
return converter.getSpecific(result);
|
||||
else
|
||||
return (TField) result;
|
||||
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new FieldAccessException("Cannot read field due to a security limitation.", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the value of a field if and ONLY IF it exists.
|
||||
* @param fieldIndex - index of the field.
|
||||
* @return Value of the field, or NULL if it doesn't exist.
|
||||
* @throws FieldAccessException The field cannot be accessed under the current security contraints.
|
||||
*/
|
||||
public TField readSafely(int fieldIndex) throws FieldAccessException {
|
||||
if (fieldIndex >= 0 && fieldIndex < data.size()) {
|
||||
return read(fieldIndex);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the value of a field given its index.
|
||||
* @param fieldIndex - index of the field.
|
||||
* @param value - new value of the field.
|
||||
* @return This structure modifier - for chaining.
|
||||
* @throws FieldAccessException The field doesn't exist, or it cannot be accessed under the current security contraints.
|
||||
*/
|
||||
public StructureModifier<TField> write(int fieldIndex, TField value) throws FieldAccessException {
|
||||
if (fieldIndex < 0 || fieldIndex >= data.size())
|
||||
throw new FieldAccessException("Field index must be within 0 - count",
|
||||
new IndexOutOfBoundsException("Out of bounds"));
|
||||
if (target == null)
|
||||
throw new IllegalStateException("Cannot write to a NULL target.");
|
||||
|
||||
// Use the converter, if it exists
|
||||
Object obj = needConversion() ? converter.getGeneric(value) : value;
|
||||
|
||||
try {
|
||||
FieldUtils.writeField(data.get(fieldIndex), target, obj, true);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new FieldAccessException("Cannot read field due to a security limitation.", e);
|
||||
}
|
||||
|
||||
// Make this method chainable
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not we should use the converter instance.
|
||||
* @return TRUE if we should, FALSE otherwise.
|
||||
*/
|
||||
private final boolean needConversion() {
|
||||
return converter != null && !customConvertHandling;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the value of a given field IF and ONLY if it exists.
|
||||
* @param fieldIndex - index of the potential field.
|
||||
* @param value - new value of the field.
|
||||
* @return This structure modifer - for chaining.
|
||||
* @throws FieldAccessException The field cannot be accessed under the current security contraints.
|
||||
*/
|
||||
public StructureModifier<TField> writeSafely(int fieldIndex, TField value) throws FieldAccessException {
|
||||
if (fieldIndex >= 0 && fieldIndex < data.size()) {
|
||||
write(fieldIndex, value);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Correctly modifies the value of a field.
|
||||
* @param fieldIndex - index of the field to modify.
|
||||
* @param select - the function that modifies the field value.
|
||||
* @return This structure modifier - for chaining.
|
||||
* @throws FieldAccessException The field cannot be accessed under the current security contraints.
|
||||
*/
|
||||
public StructureModifier<TField> modify(int fieldIndex, Function<TField, TField> select) throws FieldAccessException {
|
||||
TField value = read(fieldIndex);
|
||||
return write(fieldIndex, select.apply(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a structure modifier that only reads and writes fields of a given type.
|
||||
* @param fieldType - the type, or supertype, of every field to modify.
|
||||
* @return A structure modifier for fields of this type.
|
||||
*/
|
||||
public <T> StructureModifier<T> withType(Class fieldType) {
|
||||
return withType(fieldType, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets all non-primitive fields to a more fitting default value. See {@link DefaultInstances#getDefault(Class)}.
|
||||
* @return The current structure modifier - for chaining.
|
||||
* @throws FieldAccessException If we're unable to write to the fields due to a security limitation.
|
||||
*/
|
||||
public StructureModifier<TField> writeDefaults() throws FieldAccessException {
|
||||
|
||||
DefaultInstances generator = DefaultInstances.DEFAULT;
|
||||
|
||||
// Write a default instance to every field
|
||||
for (Field field : defaultFields.keySet()) {
|
||||
try {
|
||||
FieldUtils.writeField(field, target,
|
||||
generator.getDefault(field.getType()), true);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new FieldAccessException("Cannot write to field due to a security limitation.", e);
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a structure modifier that only reads and writes fields of a given type.
|
||||
* @param fieldType - the type, or supertype, of every field to modify.
|
||||
* @param converter - converts objects into the given type.
|
||||
* @return A structure modifier for fields of this type.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> StructureModifier<T> withType(Class fieldType, EquivalentConverter<T> converter) {
|
||||
|
||||
StructureModifier<T> result = subtypeCache.get(fieldType);
|
||||
|
||||
// Do we need to update the cache?
|
||||
if (result == null) {
|
||||
List<Field> filtered = new ArrayList<Field>();
|
||||
Map<Field, Integer> defaults = new HashMap<Field, Integer>();
|
||||
int index = 0;
|
||||
|
||||
for (Field field : data) {
|
||||
if (fieldType != null && fieldType.isAssignableFrom(field.getType())) {
|
||||
filtered.add(field);
|
||||
|
||||
// Don't use the original index
|
||||
if (defaultFields.containsKey(field))
|
||||
defaults.put(field, index);
|
||||
}
|
||||
|
||||
// Keep track of the field index
|
||||
index++;
|
||||
}
|
||||
|
||||
// Cache structure modifiers
|
||||
result = withFieldType(fieldType, filtered, defaults, converter);
|
||||
|
||||
if (fieldType != null) {
|
||||
subtypeCache.put(fieldType, result);
|
||||
|
||||
// Automatically compile the structure modifier
|
||||
if (BackgroundCompiler.getInstance() != null)
|
||||
BackgroundCompiler.getInstance().scheduleCompilation(subtypeCache, fieldType);
|
||||
}
|
||||
}
|
||||
|
||||
// Add the target too
|
||||
result = result.withTarget(target);
|
||||
|
||||
// And the converter, if it's needed
|
||||
if (!sameConverter(result.converter, converter)) {
|
||||
result = result.withConverter(converter);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private boolean sameConverter(EquivalentConverter<?> a, EquivalentConverter<?> b) {
|
||||
// Compare the converter types
|
||||
if (a == null)
|
||||
return b == null;
|
||||
else if (b == null)
|
||||
return a == null;
|
||||
else
|
||||
return a.getSpecificType().equals(b.getSpecificType());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the common type of each field.
|
||||
* @return Common type of each field.
|
||||
*/
|
||||
public Class getFieldType() {
|
||||
return fieldType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the type of the object we're modifying.
|
||||
* @return Type of the object.
|
||||
*/
|
||||
public Class getTargetType() {
|
||||
return targetType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the object we're currently modifying.
|
||||
* @return Object we're modifying.
|
||||
*/
|
||||
public Object getTarget() {
|
||||
return target;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the number of readable types.
|
||||
* @return Readable types.
|
||||
*/
|
||||
public int size() {
|
||||
return data.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new structure modifier for the new field type.
|
||||
* @param fieldType - common type of each field.
|
||||
* @param filtered - list of fields after filtering the original modifier.
|
||||
* @param defaults - list of default values after filtering the original.
|
||||
* @param converter - the new converter.
|
||||
* @return A new structure modifier.
|
||||
*/
|
||||
protected <T> StructureModifier<T> withFieldType(
|
||||
Class fieldType, List<Field> filtered,
|
||||
Map<Field, Integer> defaults, EquivalentConverter<T> converter) {
|
||||
|
||||
StructureModifier<T> result = new StructureModifier<T>();
|
||||
result.initialize(targetType, fieldType, filtered, defaults,
|
||||
converter, new ConcurrentHashMap<Class, StructureModifier>());
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a structure modifier of the same type for a different object target.
|
||||
* @param target - different target of the same type.
|
||||
* @return Structure modifier with the new target.
|
||||
*/
|
||||
public StructureModifier<TField> withTarget(Object target) {
|
||||
StructureModifier<TField> copy = new StructureModifier<TField>();
|
||||
|
||||
// Create a new instance
|
||||
copy.initialize(targetType, fieldType, data, defaultFields, converter, subtypeCache);
|
||||
copy.target = target;
|
||||
return copy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a structure modifier with the same type and target, but using a new object converter.
|
||||
* @param converter - the object converter to use.
|
||||
* @return Structure modifier with the new converter.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> StructureModifier<T> withConverter(EquivalentConverter<T> converter) {
|
||||
StructureModifier copy = withTarget(target);
|
||||
|
||||
copy.setConverter(converter);
|
||||
return copy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current object converter. Should only be called during construction.
|
||||
* @param converter - current object converter.
|
||||
*/
|
||||
protected void setConverter(EquivalentConverter<TField> converter) {
|
||||
this.converter = converter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a list of the fields matching the constraints of this structure modifier.
|
||||
* @return List of fields.
|
||||
*/
|
||||
public List<Field> getFields() {
|
||||
return ImmutableList.copyOf(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve every value stored in the fields of the current type.
|
||||
* @return Every field value.
|
||||
* @throws FieldAccessException Unable to access one or all of the fields
|
||||
*/
|
||||
public List<TField> getValues() throws FieldAccessException {
|
||||
List<TField> values = new ArrayList<TField>();
|
||||
|
||||
for (int i = 0; i < size(); i++) {
|
||||
values.add(read(i));
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
// Used to generate plausible default values
|
||||
private static Map<Field, Integer> generateDefaultFields(List<Field> fields) {
|
||||
|
||||
Map<Field, Integer> requireDefaults = new HashMap<Field, Integer>();
|
||||
DefaultInstances generator = DefaultInstances.DEFAULT;
|
||||
int index = 0;
|
||||
|
||||
for (Field field : fields) {
|
||||
Class<?> type = field.getType();
|
||||
|
||||
// First, ignore primitive fields
|
||||
if (!PrimitiveUtils.isPrimitive(type)) {
|
||||
// Next, see if we actually can generate a default value
|
||||
if (generator.getDefault(type) != null) {
|
||||
// If so, require it
|
||||
requireDefaults.put(field, index);
|
||||
}
|
||||
}
|
||||
|
||||
// Increment field index
|
||||
index++;
|
||||
}
|
||||
|
||||
return requireDefaults;
|
||||
}
|
||||
|
||||
// Used to filter out irrelevant fields
|
||||
private static List<Field> getFields(Class type, Class superclassExclude) {
|
||||
List<Field> result = new ArrayList<Field>();
|
||||
|
||||
// Retrieve every private and public field
|
||||
for (Field field : FuzzyReflection.fromClass(type, true).getFields()) {
|
||||
int mod = field.getModifiers();
|
||||
|
||||
// Ignore static, final and "abstract packet" fields
|
||||
if (!Modifier.isFinal(mod) && !Modifier.isStatic(mod) && (
|
||||
superclassExclude == null || !field.getDeclaringClass().equals(superclassExclude)
|
||||
)) {
|
||||
|
||||
result.add(field);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -1,187 +0,0 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol.reflect;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
/**
|
||||
* Represents a field that will revert to its original state when this class is garbaged collected.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public class VolatileField {
|
||||
|
||||
private Field field;
|
||||
private Object container;
|
||||
|
||||
// The current and previous values
|
||||
private Object previous;
|
||||
private Object current;
|
||||
|
||||
// Whether or not we must reset or load
|
||||
private boolean previousLoaded;
|
||||
private boolean currentSet;
|
||||
|
||||
// Whether or not to break access restrictions
|
||||
private boolean forceAccess;
|
||||
|
||||
/**
|
||||
* Initializes a volatile field with an associated object.
|
||||
* @param field - the field.
|
||||
* @param container - the object this field belongs to.
|
||||
*/
|
||||
public VolatileField(Field field, Object container) {
|
||||
this.field = field;
|
||||
this.container = container;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes a volatile field with an associated object.
|
||||
* @param field - the field.
|
||||
* @param container - the object this field belongs to.
|
||||
* @param forceAccess - whether or not to override any scope restrictions.
|
||||
*/
|
||||
public VolatileField(Field field, Object container, boolean forceAccess) {
|
||||
this.field = field;
|
||||
this.container = container;
|
||||
this.forceAccess = forceAccess;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the current field.
|
||||
* @return The stored field.
|
||||
*/
|
||||
public Field getField() {
|
||||
return field;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the object the field is stored.
|
||||
* @return The reference object.
|
||||
*/
|
||||
public Object getContainer() {
|
||||
return container;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves whether or not not to override any scope restrictions.
|
||||
* @return TRUE if we override scope, FALSE otherwise.
|
||||
*/
|
||||
public boolean isForceAccess() {
|
||||
return forceAccess;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether or not not to override any scope restrictions.
|
||||
* @param forceAccess - TRUE if we override scope, FALSE otherwise.
|
||||
*/
|
||||
public void setForceAccess(boolean forceAccess) {
|
||||
this.forceAccess = forceAccess;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the current field value.
|
||||
* @return The current field value.
|
||||
*/
|
||||
public Object getValue() {
|
||||
// Retrieve the correct value
|
||||
if (!currentSet) {
|
||||
ensureLoaded();
|
||||
return previous;
|
||||
} else {
|
||||
return current;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the field value before the previous setValue(), unless saveValue() has been called.
|
||||
* @return Previous value.
|
||||
*/
|
||||
public Object getOldValue() {
|
||||
ensureLoaded();
|
||||
return previous;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current value. This will be reverted unless saveValue() is called.
|
||||
* @param newValue - new field value.
|
||||
*/
|
||||
public void setValue(Object newValue) {
|
||||
|
||||
// Remember to safe the previous value
|
||||
ensureLoaded();
|
||||
|
||||
try {
|
||||
FieldUtils.writeField(field, container, newValue, forceAccess);
|
||||
current = newValue;
|
||||
currentSet = true;
|
||||
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException("Unable to read field " + field.getName(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that the current value is still set after this class has been garbaged collected.
|
||||
*/
|
||||
public void saveValue() {
|
||||
previous = current;
|
||||
currentSet = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Revert to the previously set value.
|
||||
*/
|
||||
public void revertValue() {
|
||||
// Reset value if it hasn't been changed by anyone else
|
||||
if (currentSet) {
|
||||
if (getValue() == current) {
|
||||
setValue(previous);
|
||||
currentSet = false;
|
||||
} else {
|
||||
// This can be a bad sign
|
||||
System.out.println(String.format("[ProtocolLib] Unable to switch %s to %s. Expected %s but got %s.",
|
||||
field.toGenericString(), previous, current, getValue()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether or not we'll need to revert the value.
|
||||
*/
|
||||
public boolean isCurrentSet() {
|
||||
return currentSet;
|
||||
}
|
||||
|
||||
private void ensureLoaded() {
|
||||
// Load the value if we haven't already
|
||||
if (!previousLoaded) {
|
||||
try {
|
||||
previous = FieldUtils.readField(field, container, forceAccess);
|
||||
previousLoaded = true;
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException("Unable to read field " + field.getName(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void finalize() throws Throwable {
|
||||
revertValue();
|
||||
}
|
||||
}
|
||||
-200
@@ -1,200 +0,0 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol.reflect.compiler;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.comphenix.protocol.reflect.StructureModifier;
|
||||
|
||||
/**
|
||||
* Compiles structure modifiers on a background thread.
|
||||
* <p>
|
||||
* This is necessary as we cannot block the main thread.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public class BackgroundCompiler {
|
||||
|
||||
// How long to wait for a shutdown
|
||||
public static final int SHUTDOWN_DELAY_MS = 2000;
|
||||
|
||||
// The single background compiler we're using
|
||||
private static BackgroundCompiler backgroundCompiler;
|
||||
|
||||
private StructureCompiler compiler;
|
||||
private boolean enabled;
|
||||
private boolean shuttingDown;
|
||||
|
||||
private ExecutorService executor;
|
||||
|
||||
/**
|
||||
* Retrieves the current background compiler.
|
||||
* @return Current background compiler.
|
||||
*/
|
||||
public static BackgroundCompiler getInstance() {
|
||||
return backgroundCompiler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the single background compiler we're using.
|
||||
* @param backgroundCompiler - current background compiler, or NULL if the library is not loaded.
|
||||
*/
|
||||
public static void setInstance(BackgroundCompiler backgroundCompiler) {
|
||||
BackgroundCompiler.backgroundCompiler = backgroundCompiler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a background compiler.
|
||||
* @param loader - class loader from Bukkit.
|
||||
*/
|
||||
public BackgroundCompiler(ClassLoader loader) {
|
||||
this(loader, Executors.newSingleThreadExecutor());
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a background compiler utilizing the given thread pool.
|
||||
* @param loader - class loader from Bukkit.
|
||||
* @param executor - thread pool we'll use.
|
||||
*/
|
||||
public BackgroundCompiler(ClassLoader loader, ExecutorService executor) {
|
||||
if (loader == null)
|
||||
throw new IllegalArgumentException("loader cannot be NULL");
|
||||
if (executor == null)
|
||||
throw new IllegalArgumentException("executor cannot be NULL");
|
||||
|
||||
this.compiler = new StructureCompiler(loader);
|
||||
this.executor = executor;
|
||||
this.enabled = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that the indirectly given structure modifier is eventually compiled.
|
||||
* @param cache - store of structure modifiers.
|
||||
* @param key - key of the structure modifier to compile.
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
public void scheduleCompilation(final Map<Class, StructureModifier> cache, final Class key) {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
final StructureModifier<Object> uncompiled = cache.get(key);
|
||||
|
||||
if (uncompiled != null) {
|
||||
scheduleCompilation(uncompiled, new CompileListener<Object>() {
|
||||
@Override
|
||||
public void onCompiled(StructureModifier<Object> compiledModifier) {
|
||||
// Update cache
|
||||
cache.put(key, compiledModifier);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that the given structure modifier is eventually compiled.
|
||||
* @param uncompiled - structure modifier to compile.
|
||||
* @param listener - listener responsible for responding to the compilation.
|
||||
*/
|
||||
public <TKey> void scheduleCompilation(final StructureModifier<TKey> uncompiled, final CompileListener<TKey> listener) {
|
||||
|
||||
// Only schedule if we're enabled
|
||||
if (enabled && !shuttingDown) {
|
||||
|
||||
// Don't try to schedule anything
|
||||
if (executor == null || executor.isShutdown())
|
||||
return;
|
||||
|
||||
try {
|
||||
executor.submit(new Callable<Object>() {
|
||||
@Override
|
||||
public Object call() throws Exception {
|
||||
|
||||
StructureModifier<TKey> modifier = uncompiled;
|
||||
|
||||
// Do our compilation
|
||||
modifier = compiler.compile(modifier);
|
||||
listener.onCompiled(modifier);
|
||||
|
||||
// We'll also return the new structure modifier
|
||||
return modifier;
|
||||
}
|
||||
});
|
||||
} catch (RejectedExecutionException e) {
|
||||
// Occures when the underlying queue is overflowing. Since the compilation
|
||||
// is only an optmization and not really essential we'll just log this failure
|
||||
// and move on.
|
||||
Logger.getLogger("Minecraft").log(Level.WARNING, "Unable to schedule compilation task.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up after ourselves using the default timeout.
|
||||
*/
|
||||
public void shutdownAll() {
|
||||
shutdownAll(SHUTDOWN_DELAY_MS, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up after ourselves.
|
||||
* @param timeout - the maximum time to wait.
|
||||
* @param unit - the time unit of the timeout argument.
|
||||
*/
|
||||
public void shutdownAll(long timeout, TimeUnit unit) {
|
||||
setEnabled(false);
|
||||
shuttingDown = true;
|
||||
executor.shutdown();
|
||||
|
||||
try {
|
||||
executor.awaitTermination(timeout, unit);
|
||||
} catch (InterruptedException e) {
|
||||
// Unlikely to ever occur.
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve whether or not the background compiler is enabled.
|
||||
* @return TRUE if it is enabled, FALSE otherwise.
|
||||
*/
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether or not the background compiler is enabled.
|
||||
* @param enabled - TRUE to enable it, FALSE otherwise.
|
||||
*/
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the current structure compiler.
|
||||
* @return Current structure compiler.
|
||||
*/
|
||||
public StructureCompiler getCompiler() {
|
||||
return compiler;
|
||||
}
|
||||
}
|
||||
@@ -1,292 +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.compiler;
|
||||
|
||||
import net.sf.cglib.asm.*;
|
||||
|
||||
/**
|
||||
* Used by the compiler to automatically box and unbox values.
|
||||
*/
|
||||
class BoxingHelper {
|
||||
|
||||
private final static Type BYTE_TYPE = Type.getObjectType("java/lang/Byte");
|
||||
private final static Type BOOLEAN_TYPE = Type.getObjectType("java/lang/Boolean");
|
||||
private final static Type SHORT_TYPE = Type.getObjectType("java/lang/Short");
|
||||
private final static Type CHARACTER_TYPE = Type.getObjectType("java/lang/Character");
|
||||
private final static Type INTEGER_TYPE = Type.getObjectType("java/lang/Integer");
|
||||
private final static Type FLOAT_TYPE = Type.getObjectType("java/lang/Float");
|
||||
private final static Type LONG_TYPE = Type.getObjectType("java/lang/Long");
|
||||
private final static Type DOUBLE_TYPE = Type.getObjectType("java/lang/Double");
|
||||
private final static Type NUMBER_TYPE = Type.getObjectType("java/lang/Number");
|
||||
private final static Type OBJECT_TYPE = Type.getObjectType("java/lang/Object");
|
||||
|
||||
private final static MethodDescriptor BOOLEAN_VALUE = MethodDescriptor.getMethod("boolean booleanValue()");
|
||||
private final static MethodDescriptor CHAR_VALUE = MethodDescriptor.getMethod("char charValue()");
|
||||
private final static MethodDescriptor INT_VALUE = MethodDescriptor.getMethod("int intValue()");
|
||||
private final static MethodDescriptor FLOAT_VALUE = MethodDescriptor.getMethod("float floatValue()");
|
||||
private final static MethodDescriptor LONG_VALUE = MethodDescriptor.getMethod("long longValue()");
|
||||
private final static MethodDescriptor DOUBLE_VALUE = MethodDescriptor.getMethod("double doubleValue()");
|
||||
|
||||
private MethodVisitor mv;
|
||||
|
||||
public BoxingHelper(MethodVisitor mv) {
|
||||
this.mv = mv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the instructions to box the top stack value. This value is
|
||||
* replaced by its boxed equivalent on top of the stack.
|
||||
*
|
||||
* @param type the type of the top stack value.
|
||||
*/
|
||||
public void box(final Type type){
|
||||
if(type.getSort() == Type.OBJECT || type.getSort() == Type.ARRAY) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(type == Type.VOID_TYPE) {
|
||||
push((String) null);
|
||||
} else {
|
||||
Type boxed = type;
|
||||
|
||||
switch(type.getSort()) {
|
||||
case Type.BYTE:
|
||||
boxed = BYTE_TYPE;
|
||||
break;
|
||||
case Type.BOOLEAN:
|
||||
boxed = BOOLEAN_TYPE;
|
||||
break;
|
||||
case Type.SHORT:
|
||||
boxed = SHORT_TYPE;
|
||||
break;
|
||||
case Type.CHAR:
|
||||
boxed = CHARACTER_TYPE;
|
||||
break;
|
||||
case Type.INT:
|
||||
boxed = INTEGER_TYPE;
|
||||
break;
|
||||
case Type.FLOAT:
|
||||
boxed = FLOAT_TYPE;
|
||||
break;
|
||||
case Type.LONG:
|
||||
boxed = LONG_TYPE;
|
||||
break;
|
||||
case Type.DOUBLE:
|
||||
boxed = DOUBLE_TYPE;
|
||||
break;
|
||||
}
|
||||
|
||||
newInstance(boxed);
|
||||
if(type.getSize() == 2) {
|
||||
// Pp -> Ppo -> oPpo -> ooPpo -> ooPp -> o
|
||||
dupX2();
|
||||
dupX2();
|
||||
pop();
|
||||
} else {
|
||||
// p -> po -> opo -> oop -> o
|
||||
dupX1();
|
||||
swap();
|
||||
}
|
||||
|
||||
invokeConstructor(boxed, new MethodDescriptor("<init>", Type.VOID_TYPE, new Type[] {type}));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the instruction to invoke a constructor.
|
||||
*
|
||||
* @param type the class in which the constructor is defined.
|
||||
* @param method the constructor to be invoked.
|
||||
*/
|
||||
public void invokeConstructor(final Type type, final MethodDescriptor method){
|
||||
invokeInsn(Opcodes.INVOKESPECIAL, type, method);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a DUP_X1 instruction.
|
||||
*/
|
||||
public void dupX1(){
|
||||
mv.visitInsn(Opcodes.DUP_X1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a DUP_X2 instruction.
|
||||
*/
|
||||
public void dupX2(){
|
||||
mv.visitInsn(Opcodes.DUP_X2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a POP instruction.
|
||||
*/
|
||||
public void pop(){
|
||||
mv.visitInsn(Opcodes.POP);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a SWAP instruction.
|
||||
*/
|
||||
public void swap(){
|
||||
mv.visitInsn(Opcodes.SWAP);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the instruction to push the given value on the stack.
|
||||
*
|
||||
* @param value the value to be pushed on the stack.
|
||||
*/
|
||||
public void push(final boolean value){
|
||||
push(value ? 1 : 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the instruction to push the given value on the stack.
|
||||
*
|
||||
* @param value the value to be pushed on the stack.
|
||||
*/
|
||||
public void push(final int value) {
|
||||
if (value >= -1 && value <= 5) {
|
||||
mv.visitInsn(Opcodes.ICONST_0 + value);
|
||||
} else if (value >= Byte.MIN_VALUE && value <= Byte.MAX_VALUE) {
|
||||
mv.visitIntInsn(Opcodes.BIPUSH, value);
|
||||
} else if (value >= Short.MIN_VALUE && value <= Short.MAX_VALUE) {
|
||||
mv.visitIntInsn(Opcodes.SIPUSH, value);
|
||||
} else {
|
||||
mv.visitLdcInsn(new Integer(value));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the instruction to create a new object.
|
||||
*
|
||||
* @param type the class of the object to be created.
|
||||
*/
|
||||
public void newInstance(final Type type){
|
||||
typeInsn(Opcodes.NEW, type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the instruction to push the given value on the stack.
|
||||
*
|
||||
* @param value the value to be pushed on the stack. May be <tt>null</tt>.
|
||||
*/
|
||||
public void push(final String value) {
|
||||
if (value == null) {
|
||||
mv.visitInsn(Opcodes.ACONST_NULL);
|
||||
} else {
|
||||
mv.visitLdcInsn(value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the instructions to unbox the top stack value. This value is
|
||||
* replaced by its unboxed equivalent on top of the stack.
|
||||
*
|
||||
* @param type
|
||||
* the type of the top stack value.
|
||||
*/
|
||||
public void unbox(final Type type){
|
||||
Type t = NUMBER_TYPE;
|
||||
MethodDescriptor sig = null;
|
||||
|
||||
switch(type.getSort()) {
|
||||
case Type.VOID:
|
||||
return;
|
||||
case Type.CHAR:
|
||||
t = CHARACTER_TYPE;
|
||||
sig = CHAR_VALUE;
|
||||
break;
|
||||
case Type.BOOLEAN:
|
||||
t = BOOLEAN_TYPE;
|
||||
sig = BOOLEAN_VALUE;
|
||||
break;
|
||||
case Type.DOUBLE:
|
||||
sig = DOUBLE_VALUE;
|
||||
break;
|
||||
case Type.FLOAT:
|
||||
sig = FLOAT_VALUE;
|
||||
break;
|
||||
case Type.LONG:
|
||||
sig = LONG_VALUE;
|
||||
break;
|
||||
case Type.INT:
|
||||
case Type.SHORT:
|
||||
case Type.BYTE:
|
||||
sig = INT_VALUE;
|
||||
}
|
||||
|
||||
if(sig == null) {
|
||||
checkCast(type);
|
||||
} else {
|
||||
checkCast(t);
|
||||
invokeVirtual(t, sig);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the instruction to check that the top stack value is of the
|
||||
* given type.
|
||||
*
|
||||
* @param type a class or interface type.
|
||||
*/
|
||||
public void checkCast(final Type type){
|
||||
if(!type.equals(OBJECT_TYPE)) {
|
||||
typeInsn(Opcodes.CHECKCAST, type);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the instruction to invoke a normal method.
|
||||
*
|
||||
* @param owner the class in which the method is defined.
|
||||
* @param method the method to be invoked.
|
||||
*/
|
||||
public void invokeVirtual(final Type owner, final MethodDescriptor method){
|
||||
invokeInsn(Opcodes.INVOKEVIRTUAL, owner, method);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an invoke method instruction.
|
||||
*
|
||||
* @param opcode the instruction's opcode.
|
||||
* @param type the class in which the method is defined.
|
||||
* @param method the method to be invoked.
|
||||
*/
|
||||
private void invokeInsn(final int opcode, final Type type, final MethodDescriptor method){
|
||||
String owner = type.getSort() == Type.ARRAY ? type.getDescriptor() : type.getInternalName();
|
||||
mv.visitMethodInsn(opcode, owner, method.getName(), method.getDescriptor());
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a type dependent instruction.
|
||||
*
|
||||
* @param opcode the instruction's opcode.
|
||||
* @param type the instruction's operand.
|
||||
*/
|
||||
private void typeInsn(final int opcode, final Type type){
|
||||
String desc;
|
||||
|
||||
if(type.getSort() == Type.ARRAY) {
|
||||
desc = type.getDescriptor();
|
||||
} else {
|
||||
desc = type.getInternalName();
|
||||
}
|
||||
|
||||
mv.visitTypeInsn(opcode, desc);
|
||||
}
|
||||
}
|
||||
-111
@@ -1,111 +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.compiler;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Map;
|
||||
|
||||
import com.comphenix.protocol.reflect.FieldAccessException;
|
||||
import com.comphenix.protocol.reflect.StructureModifier;
|
||||
import com.comphenix.protocol.reflect.instances.DefaultInstances;
|
||||
|
||||
/**
|
||||
* Represents a compiled structure modifier.
|
||||
*
|
||||
* @author Kristian
|
||||
* @param <TField> Field type.
|
||||
*/
|
||||
public abstract class CompiledStructureModifier<TField> extends StructureModifier<TField> {
|
||||
// Used to compile instances of structure modifiers
|
||||
protected StructureCompiler compiler;
|
||||
|
||||
public CompiledStructureModifier() {
|
||||
super();
|
||||
customConvertHandling = true;
|
||||
}
|
||||
|
||||
// Speed up the default writer
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public StructureModifier<TField> writeDefaults() throws FieldAccessException {
|
||||
|
||||
DefaultInstances generator = DefaultInstances.DEFAULT;
|
||||
|
||||
// Write a default instance to every field
|
||||
for (Map.Entry<Field, Integer> entry : defaultFields.entrySet()) {
|
||||
Integer index = entry.getValue();
|
||||
Field field = entry.getKey();
|
||||
|
||||
write(index, (TField) generator.getDefault(field.getType()));
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public final TField read(int fieldIndex) throws FieldAccessException {
|
||||
Object result = readGenerated(fieldIndex);
|
||||
|
||||
if (converter != null)
|
||||
return converter.getSpecific(result);
|
||||
else
|
||||
return (TField) result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the given field index using reflection.
|
||||
* @param index - index of field.
|
||||
* @return Resulting value.
|
||||
* @throws FieldAccessException The field doesn't exist, or it cannot be accessed under the current security contraints.
|
||||
*/
|
||||
protected Object readReflected(int index) throws FieldAccessException {
|
||||
return super.read(index);
|
||||
}
|
||||
|
||||
protected abstract Object readGenerated(int fieldIndex) throws FieldAccessException;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public StructureModifier<TField> write(int index, Object value) throws FieldAccessException {
|
||||
if (converter != null)
|
||||
value = converter.getGeneric((TField) value);
|
||||
return writeGenerated(index, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the given field using reflection.
|
||||
* @param index - index of field.
|
||||
* @param value - new value.
|
||||
* @throws FieldAccessException The field doesn't exist, or it cannot be accessed under the current security contraints.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
protected void writeReflected(int index, Object value) throws FieldAccessException {
|
||||
super.write(index, (TField) value);
|
||||
}
|
||||
|
||||
protected abstract StructureModifier<TField> writeGenerated(int index, Object value) throws FieldAccessException;
|
||||
|
||||
@Override
|
||||
public StructureModifier<TField> withTarget(Object target) {
|
||||
if (compiler != null)
|
||||
return compiler.compile(super.withTarget(target));
|
||||
else
|
||||
return super.withTarget(target);
|
||||
}
|
||||
}
|
||||
-237
@@ -1,237 +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.compiler;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import net.sf.cglib.asm.Type;
|
||||
|
||||
/**
|
||||
* Represents a method.
|
||||
*/
|
||||
class MethodDescriptor {
|
||||
|
||||
/**
|
||||
* The method name.
|
||||
*/
|
||||
private final String name;
|
||||
|
||||
/**
|
||||
* The method descriptor.
|
||||
*/
|
||||
private final String desc;
|
||||
|
||||
/**
|
||||
* Maps primitive Java type names to their descriptors.
|
||||
*/
|
||||
private static final Map<String, String> DESCRIPTORS;
|
||||
|
||||
static {
|
||||
DESCRIPTORS = new HashMap<String, String>();
|
||||
DESCRIPTORS.put("void", "V");
|
||||
DESCRIPTORS.put("byte", "B");
|
||||
DESCRIPTORS.put("char", "C");
|
||||
DESCRIPTORS.put("double", "D");
|
||||
DESCRIPTORS.put("float", "F");
|
||||
DESCRIPTORS.put("int", "I");
|
||||
DESCRIPTORS.put("long", "J");
|
||||
DESCRIPTORS.put("short", "S");
|
||||
DESCRIPTORS.put("boolean", "Z");
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link Method}.
|
||||
*
|
||||
* @param name the method's name.
|
||||
* @param desc the method's descriptor.
|
||||
*/
|
||||
public MethodDescriptor(final String name, final String desc) {
|
||||
this.name = name;
|
||||
this.desc = desc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link Method}.
|
||||
*
|
||||
* @param name the method's name.
|
||||
* @param returnType the method's return type.
|
||||
* @param argumentTypes the method's argument types.
|
||||
*/
|
||||
public MethodDescriptor(
|
||||
final String name,
|
||||
final Type returnType,
|
||||
final Type[] argumentTypes)
|
||||
{
|
||||
this(name, Type.getMethodDescriptor(returnType, argumentTypes));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link Method} corresponding to the given Java method
|
||||
* declaration.
|
||||
*
|
||||
* @param method a Java method declaration, without argument names, of the
|
||||
* form "returnType name (argumentType1, ... argumentTypeN)", where
|
||||
* the types are in plain Java (e.g. "int", "float",
|
||||
* "java.util.List", ...). Classes of the java.lang package can be
|
||||
* specified by their unqualified name; all other classes names must
|
||||
* be fully qualified.
|
||||
* @return a {@link Method} corresponding to the given Java method
|
||||
* declaration.
|
||||
* @throws IllegalArgumentException if <code>method</code> could not get
|
||||
* parsed.
|
||||
*/
|
||||
public static MethodDescriptor getMethod(final String method)
|
||||
throws IllegalArgumentException
|
||||
{
|
||||
return getMethod(method, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link Method} corresponding to the given Java method
|
||||
* declaration.
|
||||
*
|
||||
* @param method a Java method declaration, without argument names, of the
|
||||
* form "returnType name (argumentType1, ... argumentTypeN)", where
|
||||
* the types are in plain Java (e.g. "int", "float",
|
||||
* "java.util.List", ...). Classes of the java.lang package may be
|
||||
* specified by their unqualified name, depending on the
|
||||
* defaultPackage argument; all other classes names must be fully
|
||||
* qualified.
|
||||
* @param defaultPackage true if unqualified class names belong to the
|
||||
* default package, or false if they correspond to java.lang classes.
|
||||
* For instance "Object" means "Object" if this option is true, or
|
||||
* "java.lang.Object" otherwise.
|
||||
* @return a {@link Method} corresponding to the given Java method
|
||||
* declaration.
|
||||
* @throws IllegalArgumentException if <code>method</code> could not get
|
||||
* parsed.
|
||||
*/
|
||||
public static MethodDescriptor getMethod(
|
||||
final String method,
|
||||
final boolean defaultPackage) throws IllegalArgumentException
|
||||
{
|
||||
int space = method.indexOf(' ');
|
||||
int start = method.indexOf('(', space) + 1;
|
||||
int end = method.indexOf(')', start);
|
||||
if (space == -1 || start == -1 || end == -1) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
String returnType = method.substring(0, space);
|
||||
String methodName = method.substring(space + 1, start - 1).trim();
|
||||
StringBuffer sb = new StringBuffer();
|
||||
sb.append('(');
|
||||
int p;
|
||||
do {
|
||||
String s;
|
||||
p = method.indexOf(',', start);
|
||||
if (p == -1) {
|
||||
s = map(method.substring(start, end).trim(), defaultPackage);
|
||||
} else {
|
||||
s = map(method.substring(start, p).trim(), defaultPackage);
|
||||
start = p + 1;
|
||||
}
|
||||
sb.append(s);
|
||||
} while (p != -1);
|
||||
sb.append(')');
|
||||
sb.append(map(returnType, defaultPackage));
|
||||
return new MethodDescriptor(methodName, sb.toString());
|
||||
}
|
||||
|
||||
private static String map(final String type, final boolean defaultPackage) {
|
||||
if ("".equals(type)) {
|
||||
return type;
|
||||
}
|
||||
|
||||
StringBuffer sb = new StringBuffer();
|
||||
int index = 0;
|
||||
while ((index = type.indexOf("[]", index) + 1) > 0) {
|
||||
sb.append('[');
|
||||
}
|
||||
|
||||
String t = type.substring(0, type.length() - sb.length() * 2);
|
||||
String desc = (String) DESCRIPTORS.get(t);
|
||||
if (desc != null) {
|
||||
sb.append(desc);
|
||||
} else {
|
||||
sb.append('L');
|
||||
if (t.indexOf('.') < 0) {
|
||||
if (!defaultPackage) {
|
||||
sb.append("java/lang/");
|
||||
}
|
||||
sb.append(t);
|
||||
} else {
|
||||
sb.append(t.replace('.', '/'));
|
||||
}
|
||||
sb.append(';');
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the method described by this object.
|
||||
*
|
||||
* @return the name of the method described by this object.
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the descriptor of the method described by this object.
|
||||
*
|
||||
* @return the descriptor of the method described by this object.
|
||||
*/
|
||||
public String getDescriptor() {
|
||||
return desc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the return type of the method described by this object.
|
||||
*
|
||||
* @return the return type of the method described by this object.
|
||||
*/
|
||||
public Type getReturnType() {
|
||||
return Type.getReturnType(desc);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the argument types of the method described by this object.
|
||||
*
|
||||
* @return the argument types of the method described by this object.
|
||||
*/
|
||||
public Type[] getArgumentTypes() {
|
||||
return Type.getArgumentTypes(desc);
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return name + desc;
|
||||
}
|
||||
|
||||
public boolean equals(final Object o) {
|
||||
if (!(o instanceof MethodDescriptor)) {
|
||||
return false;
|
||||
}
|
||||
MethodDescriptor other = (MethodDescriptor) o;
|
||||
return name.equals(other.name) && desc.equals(other.desc);
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
return name.hashCode() ^ desc.hashCode();
|
||||
}
|
||||
}
|
||||
-459
@@ -1,459 +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.compiler;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.comphenix.protocol.reflect.PrimitiveUtils;
|
||||
import com.comphenix.protocol.reflect.StructureModifier;
|
||||
import com.google.common.base.Objects;
|
||||
|
||||
import net.sf.cglib.asm.*;
|
||||
|
||||
// public class CompiledStructureModifierPacket20<TField> extends CompiledStructureModifier<TField> {
|
||||
//
|
||||
// private Packet20NamedEntitySpawn typedTarget;
|
||||
//
|
||||
// public CompiledStructureModifierPacket20(StructureModifier<TField> other, StructureCompiler compiler) {
|
||||
// super();
|
||||
// initialize(other);
|
||||
// this.target = other.getTarget();
|
||||
// this.typedTarget = (Packet20NamedEntitySpawn) target;
|
||||
// this.compiler = compiler;
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// protected Object readGenerated(int fieldIndex) throws FieldAccessException {
|
||||
//
|
||||
// Packet20NamedEntitySpawn target = typedTarget;
|
||||
//
|
||||
// switch (fieldIndex) {
|
||||
// case 0: return (Object) target.a;
|
||||
// case 1: return (Object) target.b;
|
||||
// case 2: return (Object) target.c;
|
||||
// case 3: return super.read(fieldIndex);
|
||||
// case 4: return super.read(fieldIndex);
|
||||
// case 5: return (Object) target.f;
|
||||
// case 6: return (Object) target.g;
|
||||
// case 7: return (Object) target.h;
|
||||
// default:
|
||||
// throw new FieldAccessException("Invalid index " + fieldIndex);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// protected StructureModifier<TField> writeGenerated(int index, Object value) throws FieldAccessException {
|
||||
//
|
||||
// Packet20NamedEntitySpawn target = typedTarget;
|
||||
//
|
||||
// switch (index) {
|
||||
// case 0: target.a = (Integer) value; break;
|
||||
// case 1: target.b = (String) value; break;
|
||||
// case 2: target.c = (Integer) value; break;
|
||||
// case 3: target.d = (Integer) value; break;
|
||||
// case 4: super.write(index, value); break;
|
||||
// case 5: super.write(index, value); break;
|
||||
// case 6: target.g = (Byte) value; break;
|
||||
// case 7: target.h = (Integer) value; break;
|
||||
// default:
|
||||
// throw new FieldAccessException("Invalid index " + index);
|
||||
// }
|
||||
//
|
||||
// // Chaining
|
||||
// return this;
|
||||
// }
|
||||
// }
|
||||
|
||||
/**
|
||||
* Represents a StructureModifier compiler.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public final class StructureCompiler {
|
||||
|
||||
// Used to store generated classes of different types
|
||||
@SuppressWarnings("rawtypes")
|
||||
private class StructureKey {
|
||||
private Class targetType;
|
||||
private Class fieldType;
|
||||
|
||||
public StructureKey(Class targetType, Class fieldType) {
|
||||
this.targetType = targetType;
|
||||
this.fieldType = fieldType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hashCode(targetType, fieldType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj instanceof StructureKey) {
|
||||
StructureKey other = (StructureKey) obj;
|
||||
return Objects.equal(targetType, other.targetType) &&
|
||||
Objects.equal(fieldType, other.fieldType);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Used to load classes
|
||||
private volatile static Method defineMethod;
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
private Map<StructureKey, Class> compiledCache = new HashMap<StructureKey, Class>();
|
||||
|
||||
// The class loader we'll store our classes
|
||||
private ClassLoader loader;
|
||||
|
||||
// References to other classes
|
||||
private static String PACKAGE_NAME = "com/comphenix/protocol/reflect/compiler";
|
||||
private static String SUPER_CLASS = "com/comphenix/protocol/reflect/StructureModifier";
|
||||
private static String COMPILED_CLASS = PACKAGE_NAME + "/CompiledStructureModifier";
|
||||
private static String FIELD_EXCEPTION_CLASS = "com/comphenix/protocol/reflect/FieldAccessException";
|
||||
|
||||
/**
|
||||
* Construct a structure compiler.
|
||||
* @param loader - main class loader.
|
||||
*/
|
||||
StructureCompiler(ClassLoader loader) {
|
||||
this.loader = loader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compiles the given structure modifier.
|
||||
* <p>
|
||||
* WARNING: Do NOT call this method in the main thread. Compiling may easily take 10 ms, which is already
|
||||
* over 1/4 of a tick (50 ms). Let the background thread automatically compile the structure modifiers instead.
|
||||
* @param source - structure modifier to compile.
|
||||
* @return A compiled structure modifier.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public synchronized <TField> StructureModifier<TField> compile(StructureModifier<TField> source) {
|
||||
|
||||
// We cannot optimize a structure modifier with no public fields
|
||||
if (!isAnyPublic(source.getFields())) {
|
||||
return source;
|
||||
}
|
||||
|
||||
StructureKey key = new StructureKey(source.getTargetType(), source.getFieldType());
|
||||
Class<?> compiledClass = compiledCache.get(key);
|
||||
|
||||
if (!compiledCache.containsKey(key)) {
|
||||
compiledClass = generateClass(source);
|
||||
compiledCache.put(key, compiledClass);
|
||||
}
|
||||
|
||||
// Next, create an instance of this class
|
||||
try {
|
||||
return (StructureModifier<TField>) compiledClass.getConstructor(
|
||||
StructureModifier.class, StructureCompiler.class).
|
||||
newInstance(source, this);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new IllegalStateException("Used invalid parameters in instance creation", e);
|
||||
} catch (SecurityException e) {
|
||||
throw new RuntimeException("Security limitation!", e);
|
||||
} catch (InstantiationException e) {
|
||||
throw new RuntimeException("Error occured while instancing generated class.", e);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException("Security limitation! Cannot create instance of dynamic class.", e);
|
||||
} catch (InvocationTargetException e) {
|
||||
throw new RuntimeException("Error occured while instancing generated class.", e);
|
||||
} catch (NoSuchMethodException e) {
|
||||
throw new IllegalStateException("Cannot happen.", e);
|
||||
}
|
||||
}
|
||||
|
||||
private <TField> Class<?> generateClass(StructureModifier<TField> source) {
|
||||
|
||||
ClassWriter cw = new ClassWriter(0);
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
Class targetType = source.getTargetType();
|
||||
|
||||
String className = "CompiledStructure$" + targetType.getSimpleName() + source.getFieldType().getSimpleName();
|
||||
String targetSignature = Type.getDescriptor(targetType);
|
||||
String targetName = targetType.getName().replace('.', '/');
|
||||
|
||||
try {
|
||||
// This class might have been generated before. Try to load it.
|
||||
Class<?> before = loader.loadClass(PACKAGE_NAME.replace('/', '.') + "." + className);
|
||||
|
||||
if (before != null)
|
||||
return before;
|
||||
} catch (ClassNotFoundException e) {
|
||||
// That's ok.
|
||||
}
|
||||
|
||||
cw.visit(Opcodes.V1_6, Opcodes.ACC_PUBLIC + Opcodes.ACC_SUPER, PACKAGE_NAME + "/" + className,
|
||||
"<TField:Ljava/lang/Object;>L" + COMPILED_CLASS + "<TTField;>;",
|
||||
COMPILED_CLASS, null);
|
||||
|
||||
createFields(cw, targetSignature);
|
||||
createConstructor(cw, className, targetSignature, targetName);
|
||||
createReadMethod(cw, className, source.getFields(), targetSignature, targetName);
|
||||
createWriteMethod(cw, className, source.getFields(), targetSignature, targetName);
|
||||
cw.visitEnd();
|
||||
|
||||
byte[] data = cw.toByteArray();
|
||||
|
||||
// Call the define method
|
||||
try {
|
||||
if (defineMethod == null) {
|
||||
Method defined = ClassLoader.class.getDeclaredMethod("defineClass",
|
||||
new Class<?>[] { String.class, byte[].class, int.class, int.class });
|
||||
|
||||
// Awesome. Now, create and return it.
|
||||
defined.setAccessible(true);
|
||||
defineMethod = defined;
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
Class clazz = (Class) defineMethod.invoke(loader, null, data, 0, data.length);
|
||||
|
||||
// DEBUG CODE: Print the content of the generated class.
|
||||
//org.objectweb.asm.ClassReader cr = new org.objectweb.asm.ClassReader(data);
|
||||
//cr.accept(new ASMifierClassVisitor(new PrintWriter(System.out)), 0);
|
||||
|
||||
return clazz;
|
||||
|
||||
} catch (SecurityException e) {
|
||||
throw new RuntimeException("Cannot use reflection to dynamically load a class.", e);
|
||||
} catch (NoSuchMethodException e) {
|
||||
throw new IllegalStateException("Incompatible JVM.", e);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new IllegalStateException("Cannot call defineMethod - wrong JVM?", e);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException("Security limitation! Cannot dynamically load class.", e);
|
||||
} catch (InvocationTargetException e) {
|
||||
throw new RuntimeException("Error occured in code generator.", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if at least one of the given fields is public.
|
||||
* @param fields - field to test.
|
||||
* @return TRUE if one or more field is publically accessible, FALSE otherwise.
|
||||
*/
|
||||
private boolean isAnyPublic(List<Field> fields) {
|
||||
// Are any of the fields public?
|
||||
for (int i = 0; i < fields.size(); i++) {
|
||||
if (isPublic(fields.get(i))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isPublic(Field field) {
|
||||
return Modifier.isPublic(field.getModifiers());
|
||||
}
|
||||
|
||||
private void createFields(ClassWriter cw, String targetSignature) {
|
||||
FieldVisitor typedField = cw.visitField(Opcodes.ACC_PRIVATE, "typedTarget", targetSignature, null, null);
|
||||
typedField.visitEnd();
|
||||
}
|
||||
|
||||
private void createWriteMethod(ClassWriter cw, String className, List<Field> fields, String targetSignature, String targetName) {
|
||||
|
||||
String methodDescriptor = "(ILjava/lang/Object;)L" + SUPER_CLASS + ";";
|
||||
String methodSignature = "(ITTField;)L" + SUPER_CLASS + "<TTField;>;";
|
||||
MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PROTECTED, "writeGenerated", methodDescriptor, methodSignature,
|
||||
new String[] { FIELD_EXCEPTION_CLASS });
|
||||
BoxingHelper boxingHelper = new BoxingHelper(mv);
|
||||
|
||||
mv.visitCode();
|
||||
mv.visitVarInsn(Opcodes.ALOAD, 0);
|
||||
mv.visitFieldInsn(Opcodes.GETFIELD, PACKAGE_NAME + "/" + className, "typedTarget", targetSignature);
|
||||
mv.visitVarInsn(Opcodes.ASTORE, 3);
|
||||
mv.visitVarInsn(Opcodes.ILOAD, 1);
|
||||
|
||||
// The last label is for the default switch
|
||||
Label[] labels = new Label[fields.size()];
|
||||
Label errorLabel = new Label();
|
||||
Label returnLabel = new Label();
|
||||
|
||||
// Generate labels
|
||||
for (int i = 0; i < fields.size(); i++) {
|
||||
labels[i] = new Label();
|
||||
}
|
||||
|
||||
mv.visitTableSwitchInsn(0, labels.length - 1, errorLabel, labels);
|
||||
|
||||
for (int i = 0; i < fields.size(); i++) {
|
||||
|
||||
Class<?> outputType = fields.get(i).getType();
|
||||
Class<?> inputType = PrimitiveUtils.wrap(outputType);
|
||||
String typeDescriptor = Type.getDescriptor(outputType);
|
||||
String inputPath = inputType.getName().replace('.', '/');
|
||||
|
||||
mv.visitLabel(labels[i]);
|
||||
|
||||
// Push the compare object
|
||||
if (i == 0)
|
||||
mv.visitFrame(Opcodes.F_APPEND, 1, new Object[] { targetName }, 0, null);
|
||||
else
|
||||
mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
|
||||
|
||||
// Only write to public fields
|
||||
if (isPublic(fields.get(i))) {
|
||||
mv.visitVarInsn(Opcodes.ALOAD, 3);
|
||||
mv.visitVarInsn(Opcodes.ALOAD, 2);
|
||||
|
||||
if (!PrimitiveUtils.isPrimitive(outputType))
|
||||
mv.visitTypeInsn(Opcodes.CHECKCAST, inputPath);
|
||||
else
|
||||
boxingHelper.unbox(Type.getType(outputType));
|
||||
|
||||
mv.visitFieldInsn(Opcodes.PUTFIELD, targetName, fields.get(i).getName(), typeDescriptor);
|
||||
|
||||
} else {
|
||||
// Use reflection. We don't have a choice, unfortunately.
|
||||
mv.visitVarInsn(Opcodes.ALOAD, 0);
|
||||
mv.visitVarInsn(Opcodes.ILOAD, 1);
|
||||
mv.visitVarInsn(Opcodes.ALOAD, 2);
|
||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, COMPILED_CLASS, "writeReflected", "(ILjava/lang/Object;)V;");
|
||||
}
|
||||
|
||||
mv.visitJumpInsn(Opcodes.GOTO, returnLabel);
|
||||
}
|
||||
|
||||
mv.visitLabel(errorLabel);
|
||||
mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
|
||||
mv.visitTypeInsn(Opcodes.NEW, FIELD_EXCEPTION_CLASS);
|
||||
mv.visitInsn(Opcodes.DUP);
|
||||
mv.visitTypeInsn(Opcodes.NEW, "java/lang/StringBuilder");
|
||||
mv.visitInsn(Opcodes.DUP);
|
||||
mv.visitLdcInsn("Invalid index ");
|
||||
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "(Ljava/lang/String;)V");
|
||||
mv.visitVarInsn(Opcodes.ILOAD, 1);
|
||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(I)Ljava/lang/StringBuilder;");
|
||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;");
|
||||
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, FIELD_EXCEPTION_CLASS, "<init>", "(Ljava/lang/String;)V");
|
||||
mv.visitInsn(Opcodes.ATHROW);
|
||||
|
||||
mv.visitLabel(returnLabel);
|
||||
mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
|
||||
mv.visitVarInsn(Opcodes.ALOAD, 0);
|
||||
mv.visitInsn(Opcodes.ARETURN);
|
||||
mv.visitMaxs(5, 4);
|
||||
mv.visitEnd();
|
||||
}
|
||||
|
||||
private void createReadMethod(ClassWriter cw, String className, List<Field> fields, String targetSignature, String targetName) {
|
||||
MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PROTECTED, "readGenerated", "(I)Ljava/lang/Object;", null,
|
||||
new String[] { "com/comphenix/protocol/reflect/FieldAccessException" });
|
||||
BoxingHelper boxingHelper = new BoxingHelper(mv);
|
||||
|
||||
mv.visitCode();
|
||||
mv.visitVarInsn(Opcodes.ALOAD, 0);
|
||||
mv.visitFieldInsn(Opcodes.GETFIELD, PACKAGE_NAME + "/" + className, "typedTarget", targetSignature);
|
||||
mv.visitVarInsn(Opcodes.ASTORE, 2);
|
||||
mv.visitVarInsn(Opcodes.ILOAD, 1);
|
||||
|
||||
// The last label is for the default switch
|
||||
Label[] labels = new Label[fields.size()];
|
||||
Label errorLabel = new Label();
|
||||
|
||||
// Generate labels
|
||||
for (int i = 0; i < fields.size(); i++) {
|
||||
labels[i] = new Label();
|
||||
}
|
||||
|
||||
mv.visitTableSwitchInsn(0, fields.size() - 1, errorLabel, labels);
|
||||
|
||||
for (int i = 0; i < fields.size(); i++) {
|
||||
Class<?> outputType = fields.get(i).getType();
|
||||
String typeDescriptor = Type.getDescriptor(outputType);
|
||||
|
||||
mv.visitLabel(labels[i]);
|
||||
|
||||
// Push the compare object
|
||||
if (i == 0)
|
||||
mv.visitFrame(Opcodes.F_APPEND, 1, new Object[] { targetName }, 0, null);
|
||||
else
|
||||
mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
|
||||
|
||||
// Note that byte code cannot access non-public fields
|
||||
if (isPublic(fields.get(i))) {
|
||||
mv.visitVarInsn(Opcodes.ALOAD, 2);
|
||||
mv.visitFieldInsn(Opcodes.GETFIELD, targetName, fields.get(i).getName(), typeDescriptor);
|
||||
|
||||
boxingHelper.box(Type.getType(outputType));
|
||||
} else {
|
||||
// We have to use reflection for private and protected fields.
|
||||
mv.visitVarInsn(Opcodes.ALOAD, 0);
|
||||
mv.visitVarInsn(Opcodes.ILOAD, 1);
|
||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, COMPILED_CLASS, "readReflected", "(I)Ljava/lang/Object;");
|
||||
}
|
||||
|
||||
mv.visitInsn(Opcodes.ARETURN);
|
||||
}
|
||||
|
||||
mv.visitLabel(errorLabel);
|
||||
mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
|
||||
mv.visitTypeInsn(Opcodes.NEW, FIELD_EXCEPTION_CLASS);
|
||||
mv.visitInsn(Opcodes.DUP);
|
||||
mv.visitTypeInsn(Opcodes.NEW, "java/lang/StringBuilder");
|
||||
mv.visitInsn(Opcodes.DUP);
|
||||
mv.visitLdcInsn("Invalid index ");
|
||||
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "(Ljava/lang/String;)V");
|
||||
mv.visitVarInsn(Opcodes.ILOAD, 1);
|
||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(I)Ljava/lang/StringBuilder;");
|
||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;");
|
||||
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, FIELD_EXCEPTION_CLASS, "<init>", "(Ljava/lang/String;)V");
|
||||
mv.visitInsn(Opcodes.ATHROW);
|
||||
mv.visitMaxs(5, 3);
|
||||
mv.visitEnd();
|
||||
}
|
||||
|
||||
private void createConstructor(ClassWriter cw, String className, String targetSignature, String targetName) {
|
||||
MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>",
|
||||
"(L" + SUPER_CLASS + ";L" + PACKAGE_NAME + "/StructureCompiler;)V",
|
||||
"(L" + SUPER_CLASS + "<TTField;>;L" + SUPER_CLASS + ";)V", null);
|
||||
mv.visitCode();
|
||||
mv.visitVarInsn(Opcodes.ALOAD, 0);
|
||||
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, COMPILED_CLASS, "<init>", "()V");
|
||||
mv.visitVarInsn(Opcodes.ALOAD, 0);
|
||||
mv.visitVarInsn(Opcodes.ALOAD, 1);
|
||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, PACKAGE_NAME + "/" + className, "initialize", "(L" + SUPER_CLASS + ";)V");
|
||||
mv.visitVarInsn(Opcodes.ALOAD, 0);
|
||||
mv.visitVarInsn(Opcodes.ALOAD, 1);
|
||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, SUPER_CLASS, "getTarget", "()Ljava/lang/Object;");
|
||||
mv.visitFieldInsn(Opcodes.PUTFIELD, PACKAGE_NAME + "/" + className, "target", "Ljava/lang/Object;");
|
||||
mv.visitVarInsn(Opcodes.ALOAD, 0);
|
||||
mv.visitVarInsn(Opcodes.ALOAD, 0);
|
||||
mv.visitFieldInsn(Opcodes.GETFIELD, PACKAGE_NAME + "/" + className, "target", "Ljava/lang/Object;");
|
||||
mv.visitTypeInsn(Opcodes.CHECKCAST, targetName);
|
||||
mv.visitFieldInsn(Opcodes.PUTFIELD, PACKAGE_NAME + "/" + className, "typedTarget", targetSignature);
|
||||
mv.visitVarInsn(Opcodes.ALOAD, 0);
|
||||
mv.visitVarInsn(Opcodes.ALOAD, 2);
|
||||
mv.visitFieldInsn(Opcodes.PUTFIELD, PACKAGE_NAME + "/" + className, "compiler", "L" + PACKAGE_NAME + "/StructureCompiler;");
|
||||
mv.visitInsn(Opcodes.RETURN);
|
||||
mv.visitMaxs(2, 3);
|
||||
mv.visitEnd();
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
name: ProtocolLib
|
||||
version: 1.5.0
|
||||
description: Provides read/write access to the Minecraft protocol.
|
||||
author: Comphenix
|
||||
website: http://www.comphenix.net/ProtocolLib
|
||||
|
||||
main: com.comphenix.protocol.ProtocolLibrary
|
||||
database: false
|
||||
@@ -1,26 +0,0 @@
|
||||
package com.comphenix.protocol.reflect;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import net.minecraft.server.Packet103SetSlot;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import com.avaje.ebeaninternal.server.cluster.Packet;
|
||||
import com.comphenix.protocol.reflect.StructureModifier;
|
||||
|
||||
public class StructureModifierTest {
|
||||
|
||||
@Test
|
||||
public void test() throws FieldAccessException {
|
||||
|
||||
Packet103SetSlot move = new Packet103SetSlot();
|
||||
StructureModifier<Object> modifier = new StructureModifier<Object>(
|
||||
Packet103SetSlot.class, Packet.class, true);
|
||||
|
||||
move.a = 1;
|
||||
int value = (Integer) modifier.withTarget(move).withType(int.class).read(0);
|
||||
|
||||
assertEquals(1, value);
|
||||
}
|
||||
}
|
||||
@@ -1,139 +1,167 @@
|
||||
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](http://www.wiki.vg/Protocol), or inject custom packets into the
|
||||
stream. This is quite cumbersome to do, however, and most implementations will break
|
||||
Certain tasks are impossible to perform with the standard Bukkit API, and may require
|
||||
working with and even modifying Minecraft directly. A common technique is to modify incoming
|
||||
and outgoing [packets](https://www.wiki.vg/Protocol), or inject custom packets into the
|
||||
stream. This is quite cumbersome to do, however, and most implementations will break
|
||||
as soon as a new version of Minecraft has been released, mostly due to obfuscation.
|
||||
|
||||
Critically, different plugins that use this approach may _hook_ into the same classes,
|
||||
with unpredictable outcomes. More than often this causes plugins to crash, but it may also
|
||||
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
|
||||
|
||||
* [JavaDoc](http://aadnk.github.com/ProtocolLib/Javadoc/)
|
||||
* [Resource Page](https://www.spigotmc.org/resources/protocollib.1997/)
|
||||
* [Dev Builds](https://ci.dmulloy2.net/job/ProtocolLib)
|
||||
* [JavaDoc](https://ci.dmulloy2.net/job/ProtocolLib/javadoc/index.html)
|
||||
|
||||
### Compilation
|
||||
|
||||
Building
|
||||
--------
|
||||
You can compile this project yourself by using the latest version of Maven.
|
||||
ProtocolLib is built with [Maven](https://maven.apache.org/). If you have it installed, just run
|
||||
`mvn package` in the root project folder.
|
||||
|
||||
### A new API
|
||||
|
||||
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.
|
||||
__ProtocolLib__ attempts to solve this problem by providing an event API, much like Bukkit,
|
||||
that allows plugins to monitor, modify, or cancel packets sent and received. But, more importantly,
|
||||
the API also hides all the gritty, obfuscated classes with a simple index based read/write system.
|
||||
You no longer have to reference CraftBukkit!
|
||||
|
||||
|
||||
### Using ProtocolLib
|
||||
|
||||
To use 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:
|
||||
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:
|
||||
|
||||
depends: [ProtocolLib]
|
||||
````yml
|
||||
depend: [ 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.
|
||||
You can also add ProtocolLib as a Maven dependency:
|
||||
|
||||
Then get a reference to ProtocolManager in onLoad() and you're good to go.
|
||||
````xml
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>dmulloy2-repo</id>
|
||||
<url>https://repo.dmulloy2.net/repository/public/</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
private ProtocolManager protocolManager;
|
||||
|
||||
public void onLoad() {
|
||||
protocolManager = ProtocolLibrary.getProtocolManager();
|
||||
}
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.comphenix.protocol</groupId>
|
||||
<artifactId>ProtocolLib</artifactId>
|
||||
<version>4.7.0</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
````
|
||||
|
||||
Or use the maven dependency with gradle:
|
||||
|
||||
```gradle
|
||||
repositories {
|
||||
maven { url "https://repo.dmulloy2.net/repository/public/" }
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly group: "com.comphenix.protocol", name: "ProtocolLib", version: "4.7.0";
|
||||
}
|
||||
```
|
||||
|
||||
Then get a reference to ProtocolManager in onLoad() or onEnable() and you're good to go.
|
||||
|
||||
````java
|
||||
private ProtocolManager protocolManager;
|
||||
|
||||
public void onLoad() {
|
||||
protocolManager = ProtocolLibrary.getProtocolManager();
|
||||
}
|
||||
````
|
||||
|
||||
To listen for packets sent by the server to a client, add a server-side listener:
|
||||
|
||||
// Disable all sound effects
|
||||
protocolManager.addPacketListener(
|
||||
new PacketAdapter(this, ConnectionSide.SERVER_SIDE, ListenerPriority.NORMAL, 0x3E) {
|
||||
@Override
|
||||
public void onPacketSending(PacketEvent event) {
|
||||
// Item packets
|
||||
switch (event.getPacketID()) {
|
||||
case 0x3E: // Sound effect
|
||||
event.setCancelled(true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
````java
|
||||
// Disable all sound effects
|
||||
protocolManager.addPacketListener(new PacketAdapter(
|
||||
this,
|
||||
ListenerPriority.NORMAL,
|
||||
PacketType.Play.Server.NAMED_SOUND_EFFECT
|
||||
) {
|
||||
@Override
|
||||
public void onPacketSending(PacketEvent event) {
|
||||
event.setCancelled(true);
|
||||
}
|
||||
});
|
||||
````
|
||||
|
||||
It's also possible to read and modify the content of these packets. For instance, you can create a global
|
||||
censor by listening for Packet3Chat events:
|
||||
|
||||
// Censor
|
||||
protocolManager.addPacketListener(
|
||||
new PacketAdapter(this, ConnectionSide.CLIENT_SIDE, ListenerPriority.NORMAL, 0x03) {
|
||||
@Override
|
||||
public void onPacketReceiving(PacketEvent event) {
|
||||
if (event.getPacketID() == 0x03) {
|
||||
try {
|
||||
PacketContainer packet = event.getPacket();
|
||||
String message = packet.getSpecificModifier(String.class).read(0);
|
||||
|
||||
if (message.contains("shit") || message.contains("damn")) {
|
||||
event.setCancelled(true);
|
||||
event.getPlayer().sendMessage("Bad manners!");
|
||||
}
|
||||
|
||||
} catch (FieldAccessException e) {
|
||||
getLogger().log(Level.SEVERE, "Couldn't access field.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
````java
|
||||
// Censor
|
||||
protocolManager.addPacketListener(new PacketAdapter(
|
||||
this,
|
||||
ListenerPriority.NORMAL,
|
||||
PacketType.Play.Client.CHAT
|
||||
) {
|
||||
@Override
|
||||
public void onPacketReceiving(PacketEvent event) {
|
||||
PacketContainer packet = event.getPacket();
|
||||
String message = packet.getStrings().read(0);
|
||||
|
||||
if (message.contains("shit") || message.contains("damn")) {
|
||||
event.setCancelled(true);
|
||||
event.getPlayer().sendMessage("Bad manners!");
|
||||
}
|
||||
}
|
||||
});
|
||||
````
|
||||
|
||||
### Sending packets
|
||||
|
||||
Normally, you might have to do something ugly like the following:
|
||||
|
||||
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>();
|
||||
````java
|
||||
PacketPlayOutExplosion fakeExplosion = new PacketPlayOutExplosion(
|
||||
player.getLocation().getX(),
|
||||
player.getLocation().getY(),
|
||||
player.getLocation().getZ(),
|
||||
3.0F,
|
||||
new ArrayList<>(),
|
||||
new Vec3D(
|
||||
player.getVelocity().getX() + 1,
|
||||
player.getVelocity().getY() + 1,
|
||||
player.getVelocity().getZ() + 1
|
||||
)
|
||||
);
|
||||
|
||||
((CraftPlayer) player).getHandle().netServerHandler.sendPacket(fakeExplosion);
|
||||
((CraftPlayer) player).getHandle().b.a(fakeExplosion);
|
||||
````
|
||||
|
||||
But with ProtocolLib, you can turn that into something more manageable. Notice that
|
||||
you don't have to create an ArrayList this version:
|
||||
But with ProtocolLib, you can turn that into something more manageable:
|
||||
|
||||
PacketContainer fakeExplosion = protocolManager.createPacket(60);
|
||||
|
||||
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);
|
||||
````java
|
||||
PacketContainer fakeExplosion = new PacketContainer(PacketType.Play.Server.EXPLOSION);
|
||||
fakeExplosion.getDoubles()
|
||||
.write(0, player.getLocation().getX())
|
||||
.write(1, player.getLocation().getY())
|
||||
.write(2, player.getLocation().getZ());
|
||||
fakeExplosion.getFloat().write(0, 3.0F);
|
||||
fakeExplosion.getBlockPositionCollectionModifier().write(0, new ArrayList<>());
|
||||
fakeExplosion.getVectors().write(0, player.getVelocity().add(new Vector(1, 1, 1)));
|
||||
|
||||
protocolManager.sendServerPacket(player, fakeExplosion);
|
||||
````
|
||||
|
||||
Compatiblity
|
||||
------------
|
||||
### Compatibility
|
||||
|
||||
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.
|
||||
result is quite flexible. It's likely that I won't have to update ProtocolLib for anything but bug fixes
|
||||
and new features.
|
||||
|
||||
How is this possible? It all comes down to reflection in the end. Essentially, no name is hard coded -
|
||||
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:
|
||||
@@ -0,0 +1,137 @@
|
||||
<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.tinyprotocol</groupId>
|
||||
<artifactId>TinyProtocol</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
<name>TinyProtocol</name>
|
||||
<description>Intercept packets without ProtocolLib.</description>
|
||||
|
||||
<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>spigot-repo</id>
|
||||
<url>https://hub.spigotmc.org/nexus/content/groups/public/</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>dmulloy2-repo</id>
|
||||
<url>https://repo.dmulloy2.net/repository/public/</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<build>
|
||||
<sourceDirectory>src/main/java</sourceDirectory>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>src/main/resources</directory>
|
||||
</resource>
|
||||
</resources>
|
||||
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.0</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>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>
|
||||
|
||||
<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>
|
||||
</project>
|
||||
@@ -0,0 +1,117 @@
|
||||
package com.comphenix.tinyprotocol;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import com.comphenix.tinyprotocol.Reflection.ConstructorInvoker;
|
||||
import com.comphenix.tinyprotocol.Reflection.FieldAccessor;
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
// Explosion packet
|
||||
private Class<?> particleClass = Reflection.getClass("{nms}.PacketPlayOutWorldParticles");
|
||||
private FieldAccessor<String> particleName = Reflection.getField(particleClass, String.class, 0);
|
||||
private FieldAccessor<Float> particleX = Reflection.getField(particleClass, float.class, 0);
|
||||
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");
|
||||
private Class<Object> playerSampleClass = Reflection.getUntypedClass("{nms}.ServerPingPlayerSample");
|
||||
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
|
||||
if (CHAT_MESSAGE.hasField(packet)) {
|
||||
if (CHAT_MESSAGE.get(packet).contains("dirty")) {
|
||||
sendExplosion(sender);
|
||||
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));
|
||||
return packet;
|
||||
}
|
||||
|
||||
return super.onPacketOutAsync(reciever, channel, packet);
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
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);
|
||||
sender.sendMessage(ChatColor.YELLOW + "Player " + player + " has been uninjected.");
|
||||
} else {
|
||||
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
|
||||
Object explosionPacket = particleClass.newInstance();
|
||||
Location loc = player.getLocation();
|
||||
particleName.set(explosionPacket, "hugeexplosion");
|
||||
particleX.set(explosionPacket, (float) loc.getX());
|
||||
particleY.set(explosionPacket, (float) loc.getY());
|
||||
particleZ.set(explosionPacket, (float) loc.getZ());
|
||||
particleCount.set(explosionPacket, 1);
|
||||
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,522 @@
|
||||
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;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.HandlerList;
|
||||
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.
|
||||
* <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 = new ArrayList<>();
|
||||
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.
|
||||
* <p>
|
||||
* You can construct multiple instances per plugin.
|
||||
*
|
||||
* @param plugin - the plugin.
|
||||
*/
|
||||
public TinyProtocol(final Plugin plugin) {
|
||||
this.plugin = plugin;
|
||||
|
||||
// Compute handler name
|
||||
this.handlerName = getHandlerName();
|
||||
|
||||
// Prepare existing players
|
||||
registerBukkitEvents();
|
||||
|
||||
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) {
|
||||
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)
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
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))
|
||||
break;
|
||||
|
||||
// Channel future that contains the server connection
|
||||
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);
|
||||
} catch (NoSuchElementException e) {
|
||||
// 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 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 receiver, Channel channel, Object packet) {
|
||||
return packet;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @return The packet to recieve instead, or NULL to cancel.
|
||||
*/
|
||||
public Object onPacketInAsync(Player sender, Channel channel, Object packet) {
|
||||
return packet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a packet to a particular player.
|
||||
* <p>
|
||||
* 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, 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, 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, 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.
|
||||
* <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 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 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;
|
||||
} 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) {
|
||||
// No need to guard against this if we're closing
|
||||
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 {
|
||||
// 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 {
|
||||
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);
|
||||
channelLookup.put(profile.getName(), channel);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
name: TinyProtocol
|
||||
version: 0.1.0
|
||||
description: Intercept packets without ProtocolLib
|
||||
author: Comphenix
|
||||
|
||||
load: startup
|
||||
|
||||
main: com.comphenix.tinyprotocol.ExamplePlugin
|
||||
database: false
|
||||
|
||||
commands:
|
||||
toggleinjection:
|
||||
description: Toggle the TinyProtocol injection of the current player.
|
||||
usage: /<command>
|
||||
@@ -0,0 +1,5 @@
|
||||
before_install:
|
||||
- source "$HOME/.sdkman/bin/sdkman-init.sh"
|
||||
- sdk update
|
||||
- sdk install java 17.0.4-tem
|
||||
- sdk use java 17.0.4-tem
|
||||
@@ -0,0 +1,379 @@
|
||||
<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>5.0.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>
|
||||
|
||||
<junit.version>5.9.0</junit.version>
|
||||
<mockito.version>4.6.1</mockito.version>
|
||||
<netty.version>4.1.77.Final</netty.version>
|
||||
<spigot.version>1.19.3-R0.1-SNAPSHOT</spigot.version>
|
||||
</properties>
|
||||
|
||||
<build>
|
||||
<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.3.0</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.10.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.2</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-M7</version>
|
||||
<dependencies>
|
||||
<!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-engine -->
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-engine</artifactId>
|
||||
<version>${junit.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<configuration>
|
||||
<trimStackTrace>false</trimStackTrace>
|
||||
<useFile>false</useFile>
|
||||
<systemProperties>
|
||||
<property>
|
||||
<name>projectVersion</name>
|
||||
<value>${project.version}</value>
|
||||
</property>
|
||||
</systemProperties>
|
||||
<!-- We need this as we use reflection in tests to ensure equality -->
|
||||
<argLine>--add-opens java.base/java.util=ALL-UNNAMED</argLine>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<version>3.4.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.4.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>https://www.gnu.org/licenses/gpl-2.0.txt</url>
|
||||
<distribution>repo</distribution>
|
||||
</license>
|
||||
</licenses>
|
||||
|
||||
<developers>
|
||||
<developer>
|
||||
<id>dmulloy2</id>
|
||||
<name>Dan Mulloy</name>
|
||||
<url>https://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>https://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>
|
||||
<!-- core minecraft dependencies -->
|
||||
<dependency>
|
||||
<groupId>org.spigotmc</groupId>
|
||||
<artifactId>spigot-api</artifactId>
|
||||
<version>${spigot.version}</version>
|
||||
<scope>provided</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>*</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.spigotmc</groupId>
|
||||
<artifactId>spigot</artifactId>
|
||||
<version>${spigot.version}</version>
|
||||
<scope>provided</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>*</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>*</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<!-- Compile with the old version of Netty for backwards compat -->
|
||||
<!-- https://mvnrepository.com/artifact/io.netty/netty-all -->
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-all</artifactId>
|
||||
<version>4.0.23.Final</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/net.kyori/adventure-text-serializer-gson -->
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-gson</artifactId>
|
||||
<version>4.11.0</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/net.bytebuddy/byte-buddy -->
|
||||
<dependency>
|
||||
<groupId>net.bytebuddy</groupId>
|
||||
<artifactId>byte-buddy</artifactId>
|
||||
<version>1.12.12</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Testing dependencies -->
|
||||
<!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-engine -->
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-api</artifactId>
|
||||
<version>${junit.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/org.mockito/mockito-core -->
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
<version>${mockito.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/org.mockito/mockito-inline -->
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-inline</artifactId>
|
||||
<version>${mockito.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/io.netty/netty-common -->
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-common</artifactId>
|
||||
<version>${netty.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/io.netty/netty-transport -->
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-transport</artifactId>
|
||||
<version>${netty.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
+55
-22
@@ -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
|
||||
*/
|
||||
|
||||
@@ -22,6 +22,7 @@ import java.util.Set;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import com.comphenix.protocol.async.AsyncListenerHandler;
|
||||
import com.comphenix.protocol.async.AsyncMarker;
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.events.PacketListener;
|
||||
@@ -35,61 +36,69 @@ public interface AsynchronousManager {
|
||||
/**
|
||||
* Registers an asynchronous packet handler.
|
||||
* <p>
|
||||
* Use {@link AsyncMarker#incrementProcessingDelay()} to delay a packet until its ready to be transmitted.
|
||||
* <p>
|
||||
* To start listening asynchronously, pass the getListenerLoop() runnable to a different thread.
|
||||
* @param listener - the packet listener that will recieve these asynchronous events.
|
||||
* @return An asynchrouns handler.
|
||||
* @param listener - the packet listener that will receive these asynchronous events.
|
||||
* @return An asynchronous handler.
|
||||
*/
|
||||
public abstract AsyncListenerHandler registerAsyncHandler(PacketListener listener);
|
||||
AsyncListenerHandler registerAsyncHandler(PacketListener listener);
|
||||
|
||||
/**
|
||||
* Unregisters and closes the given asynchronous handler.
|
||||
* @param handler - asynchronous handler.
|
||||
*/
|
||||
public abstract void unregisterAsyncHandler(AsyncListenerHandler handler);
|
||||
void unregisterAsyncHandler(AsyncListenerHandler handler);
|
||||
|
||||
/**
|
||||
* Unregisters and closes the first asynchronous handler associated with the given listener.
|
||||
* @param listener - asynchronous listener
|
||||
*/
|
||||
void unregisterAsyncHandler(PacketListener listener);
|
||||
|
||||
/**
|
||||
* Unregisters every asynchronous handler associated with this plugin.
|
||||
* @param plugin - the original plugin.
|
||||
*/
|
||||
public void unregisterAsyncHandlers(Plugin plugin);
|
||||
|
||||
void unregisterAsyncHandlers(Plugin plugin);
|
||||
|
||||
/**
|
||||
* Retrieves a immutable set containing the ID 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<Integer> getSendingFilters();
|
||||
Set<PacketType> getSendingTypes();
|
||||
|
||||
/**
|
||||
* Retrieves a immutable set containing the ID of the recieved client packets that will be
|
||||
* 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<Integer> getReceivingFilters();
|
||||
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.
|
||||
@@ -98,5 +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.
|
||||
*/
|
||||
void registerTimeoutHandler(PacketListener listener);
|
||||
|
||||
/**
|
||||
* Unregisters a given timeout listener.
|
||||
* @param listener - the timeout listener to unregister.
|
||||
*/
|
||||
void unregisterTimeoutHandler(PacketListener listener);
|
||||
|
||||
/**
|
||||
* Get a immutable set of every registered timeout handler.
|
||||
* @return Set of every registered timeout handler.
|
||||
*/
|
||||
Set<PacketListener> getTimeoutHandlers();
|
||||
|
||||
/**
|
||||
* Get an immutable set of every registered asynchronous packet listener.
|
||||
* @return Set of every asynchronous packet listener.
|
||||
*/
|
||||
Set<PacketListener> getAsyncHandlers();
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Arrays;
|
||||
import java.util.Deque;
|
||||
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.error.Report;
|
||||
import com.comphenix.protocol.error.ReportType;
|
||||
|
||||
/**
|
||||
* Base class for all our commands.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
abstract class CommandBase implements CommandExecutor {
|
||||
public static final ReportType REPORT_COMMAND_ERROR = new ReportType("Cannot execute command %s.");
|
||||
public static final ReportType REPORT_UNEXPECTED_COMMAND = new ReportType("Incorrect command assigned to %s.");
|
||||
|
||||
public static final String PERMISSION_ADMIN = "protocol.admin";
|
||||
|
||||
private final String permission;
|
||||
private final String name;
|
||||
private final int minimumArgumentCount;
|
||||
|
||||
protected ErrorReporter reporter;
|
||||
|
||||
public CommandBase(ErrorReporter reporter, String permission, String name) {
|
||||
this(reporter, permission, name, 0);
|
||||
}
|
||||
|
||||
public CommandBase(ErrorReporter reporter, String permission, String name, int minimumArgumentCount) {
|
||||
this.reporter = reporter;
|
||||
this.name = name;
|
||||
this.permission = permission;
|
||||
this.minimumArgumentCount = minimumArgumentCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
try {
|
||||
// Make sure we're dealing with the correct command
|
||||
if (!command.getName().equalsIgnoreCase(name)) {
|
||||
reporter.reportWarning(this, Report.newBuilder(REPORT_UNEXPECTED_COMMAND).messageParam(this));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (permission != null && !sender.hasPermission(permission)) {
|
||||
sender.sendMessage(ChatColor.RED + "You haven't got permission to run this command.");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check argument length
|
||||
if (args != null && args.length >= minimumArgumentCount) {
|
||||
return handleCommand(sender, args);
|
||||
} else {
|
||||
sender.sendMessage(ChatColor.RED + "Insufficient arguments. You need at least " + minimumArgumentCount);
|
||||
return false;
|
||||
}
|
||||
} catch (Throwable ex) {
|
||||
reporter.reportDetailed(this,
|
||||
Report.newBuilder(REPORT_COMMAND_ERROR).error(ex).messageParam(name).callerParam(sender, label, args)
|
||||
);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a boolean value at the head of the queue.
|
||||
* @param arguments - the queue of arguments.
|
||||
* @param parameterName - the parameter name we will match.
|
||||
* @return The parsed boolean, or NULL if not valid.
|
||||
*/
|
||||
protected Boolean parseBoolean(Deque<String> arguments, String parameterName) {
|
||||
Boolean result = null;
|
||||
|
||||
if (!arguments.isEmpty()) {
|
||||
final String arg = arguments.peek().toLowerCase();
|
||||
switch (arg) {
|
||||
case "true":
|
||||
case "on":
|
||||
result = true;
|
||||
break;
|
||||
case "false":
|
||||
case "off":
|
||||
result = false;
|
||||
break;
|
||||
default:
|
||||
if (arg.equalsIgnoreCase(parameterName)) result = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (result != null) arguments.poll();
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a queue from a sublist of a given array.
|
||||
* @param args - the source array.
|
||||
* @param start - the starting index.
|
||||
* @return A queue that contains every element in the array, starting at the given index.
|
||||
*/
|
||||
protected Deque<String> toQueue(String[] args, int start) {
|
||||
return new ArrayDeque<>(Arrays.asList(args).subList(start, args.length));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the permission necessary to execute this command.
|
||||
* @return The permission, or NULL if not needed.
|
||||
*/
|
||||
public String getPermission() {
|
||||
return permission;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the primary name of this command.
|
||||
* @return Primary name.
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the error reporter.
|
||||
* @return Error reporter.
|
||||
*/
|
||||
protected ErrorReporter getReporter() {
|
||||
return reporter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Main implementation of this command.
|
||||
* @param sender - command sender.
|
||||
* @param args - input arguments.
|
||||
* @return TRUE if the command was successfully handled, FALSE otherwise.
|
||||
*/
|
||||
protected abstract boolean handleCommand(CommandSender sender, String[] args);
|
||||
}
|
||||
@@ -0,0 +1,481 @@
|
||||
package com.comphenix.protocol;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Deque;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.script.Invocable;
|
||||
import javax.script.ScriptEngine;
|
||||
import javax.script.ScriptEngineManager;
|
||||
import javax.script.ScriptException;
|
||||
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.conversations.Conversable;
|
||||
import org.bukkit.conversations.Conversation;
|
||||
import org.bukkit.conversations.ConversationAbandonedEvent;
|
||||
import org.bukkit.conversations.ConversationAbandonedListener;
|
||||
import org.bukkit.conversations.ConversationContext;
|
||||
import org.bukkit.conversations.ConversationFactory;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import com.comphenix.protocol.MultipleLinesPrompt.MultipleConversationCanceller;
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.error.Report;
|
||||
import com.comphenix.protocol.error.ReportType;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
|
||||
/**
|
||||
* A command to apply JavaScript filtering to the packet command.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public class CommandFilter extends CommandBase {
|
||||
public static final ReportType REPORT_FALLBACK_ENGINE = new ReportType("Falling back to the Rhino engine.");
|
||||
public static final ReportType REPORT_CANNOT_LOAD_FALLBACK_ENGINE = new ReportType("Could not load Rhino either. Please upgrade your JVM or OS.");
|
||||
public static final ReportType REPORT_PACKAGES_UNSUPPORTED_IN_ENGINE = new ReportType("Unable to initialize packages for JavaScript engine.");
|
||||
public static final ReportType REPORT_FILTER_REMOVED_FOR_ERROR = new ReportType("Removing filter %s for causing %s.");
|
||||
public static final ReportType REPORT_CANNOT_HANDLE_CONVERSATION = new ReportType("Cannot handle conversation.");
|
||||
|
||||
public interface FilterFailedHandler{
|
||||
/**
|
||||
* Invoked when a given filter has failed.
|
||||
* @param event - the packet event.
|
||||
* @param filter - the filter that failed.
|
||||
* @param ex - the failure.
|
||||
* @return TRUE to keep processing this filter, FALSE to remove it.
|
||||
*/
|
||||
public boolean handle(PacketEvent event, Filter filter, Exception ex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Possible sub commands.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
private enum SubCommand {
|
||||
ADD, REMOVE;
|
||||
}
|
||||
|
||||
/**
|
||||
* A filter that will be used to process a packet event.
|
||||
* @author Kristian
|
||||
*/
|
||||
public static class Filter {
|
||||
private final String name;
|
||||
private final String predicate;
|
||||
|
||||
private final Set<PacketType> packets;
|
||||
|
||||
/**
|
||||
* Construct a new immutable filter.
|
||||
* @param name - the unique name of the filter.
|
||||
* @param predicate - the JavaScript predicate that will be used to filter packet events.
|
||||
* @param packets - a list of packet types this filter applies to.
|
||||
*/
|
||||
public Filter(String name, String predicate, Set<PacketType> packets) {
|
||||
this.name = name;
|
||||
this.predicate = predicate;
|
||||
this.packets = new HashSet<>(packets);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the unique name of the filter.
|
||||
* @return Unique name of the filter.
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the JavaScript predicate that will be used to filter packet events.
|
||||
* @return Predicate itself.
|
||||
*/
|
||||
public String getPredicate() {
|
||||
return predicate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a copy of the set of packets this filter applies to.
|
||||
* @return Set of packets this filter applies to.
|
||||
*/
|
||||
public Set<PacketType> getRanges() {
|
||||
return new HashSet<>(packets);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether or not a packet event needs to be passed to this filter.
|
||||
* @param event - the event to test.
|
||||
* @return TRUE if it does, FALSE otherwise.
|
||||
*/
|
||||
private boolean isApplicable(PacketEvent event) {
|
||||
return packets.contains(event.getPacketType());
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate the current filter using the provided ScriptEngine as context.
|
||||
* <p>
|
||||
* This context may be modified with additional code.
|
||||
* @param context - the current script context.
|
||||
* @param event - the packet event to evaluate.
|
||||
* @return TRUE to pass this packet event on to the debug listeners, FALSE otherwise.
|
||||
* @throws ScriptException If the compilation failed or the filter is not valid.
|
||||
*/
|
||||
public boolean evaluate(ScriptEngine context, PacketEvent event) throws ScriptException {
|
||||
if (!isApplicable(event))
|
||||
return true;
|
||||
// Ensure that the predicate has been compiled
|
||||
compile(context);
|
||||
|
||||
try {
|
||||
Object result = ((Invocable) context).invokeFunction(name, event, event.getPacket().getHandle());
|
||||
|
||||
if (result instanceof Boolean)
|
||||
return (Boolean) result;
|
||||
else
|
||||
throw new ScriptException("Filter result wasn't a boolean: " + result);
|
||||
|
||||
} catch (NoSuchMethodException e) {
|
||||
// Must be a fault with the script engine itself
|
||||
throw new IllegalStateException("Unable to compile " + name + " into current script engine.", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Force the compilation of a specific filter.
|
||||
* @param context - the current script context.
|
||||
* @throws ScriptException If the compilation failed.
|
||||
*/
|
||||
public void compile(ScriptEngine context) throws ScriptException {
|
||||
if (context.get(name) == null) {
|
||||
context.eval("var " + name + " = function(event, packet) {\n" + predicate);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up all associated code from this filter in the provided script engine.
|
||||
* @param context - the current script context.
|
||||
*/
|
||||
public void close(ScriptEngine context) {
|
||||
context.put(name, null);
|
||||
}
|
||||
}
|
||||
|
||||
private class CompilationSuccessCanceller implements MultipleConversationCanceller {
|
||||
@Override
|
||||
public boolean cancelBasedOnInput(ConversationContext context, String in) {
|
||||
throw new UnsupportedOperationException("Cannot cancel on the last line alone.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setConversation(Conversation conversation) {
|
||||
// Ignore
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean cancelBasedOnInput(ConversationContext context, String currentLine, StringBuilder lines, int lineCount) {
|
||||
try {
|
||||
engine.eval("function(event, packet) {\n" + lines.toString());
|
||||
|
||||
// It compiles - accept the filter!
|
||||
return true;
|
||||
} catch (ScriptException e) {
|
||||
// We also have the function() line
|
||||
int realLineCount = lineCount + 1;
|
||||
|
||||
// Only possible to recover from an error on the last line.
|
||||
return e.getLineNumber() < realLineCount;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompilationSuccessCanceller clone() {
|
||||
return new CompilationSuccessCanceller();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Name of this command.
|
||||
*/
|
||||
public static final String NAME = "filter";
|
||||
|
||||
// Default error handler
|
||||
private FilterFailedHandler defaultFailedHandler;
|
||||
|
||||
// Currently registered filters
|
||||
private List<Filter> filters = new ArrayList<Filter>();
|
||||
|
||||
// Owner plugin
|
||||
private final Plugin plugin;
|
||||
|
||||
// Whether or not the command is enabled
|
||||
private ProtocolConfig config;
|
||||
|
||||
// Script engine
|
||||
private ScriptEngine engine;
|
||||
private boolean uninitialized;
|
||||
|
||||
public CommandFilter(ErrorReporter reporter, Plugin plugin, ProtocolConfig config) {
|
||||
super(reporter, CommandBase.PERMISSION_ADMIN, NAME, 2);
|
||||
this.plugin = plugin;
|
||||
this.config = config;
|
||||
|
||||
// Tell the filter system to initialize the script first chance it gets
|
||||
this.uninitialized = true;
|
||||
}
|
||||
|
||||
private void initalizeScript() {
|
||||
try {
|
||||
// First attempt
|
||||
initializeEngine();
|
||||
|
||||
// Oh for ..
|
||||
if (!isInitialized()) {
|
||||
throw new ScriptException("A JavaScript engine could not be found.");
|
||||
} else {
|
||||
plugin.getLogger().info("Loaded command filter engine.");
|
||||
}
|
||||
} catch (ScriptException e1) {
|
||||
// It's not a huge deal
|
||||
printPackageWarning(e1);
|
||||
|
||||
if (!config.getScriptEngineName().equals("rhino")) {
|
||||
reporter.reportWarning(this, Report.newBuilder(REPORT_FALLBACK_ENGINE));
|
||||
config.setScriptEngineName("rhino");
|
||||
config.saveAll();
|
||||
|
||||
try {
|
||||
initializeEngine();
|
||||
|
||||
if (!isInitialized()) {
|
||||
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_LOAD_FALLBACK_ENGINE));
|
||||
}
|
||||
} catch (ScriptException e2) {
|
||||
// And again ..
|
||||
printPackageWarning(e2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void printPackageWarning(ScriptException e) {
|
||||
reporter.reportWarning(this, Report.newBuilder(REPORT_PACKAGES_UNSUPPORTED_IN_ENGINE).error(e));
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the current configured engine.
|
||||
* @throws ScriptException If we are unable to import packages.
|
||||
*/
|
||||
private void initializeEngine() throws ScriptException {
|
||||
ScriptEngineManager manager = new ScriptEngineManager();
|
||||
engine = manager.getEngineByName(config.getScriptEngineName());
|
||||
|
||||
// Import useful packages
|
||||
if (engine != null) {
|
||||
engine.eval("importPackage(org.bukkit);");
|
||||
engine.eval("importPackage(com.comphenix.protocol.reflect);");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the filter engine has been successfully initialized.
|
||||
* @return TRUE if it has, FALSE otherwise.
|
||||
*/
|
||||
public boolean isInitialized() {
|
||||
return engine != null;
|
||||
}
|
||||
|
||||
private FilterFailedHandler getDefaultErrorHandler() {
|
||||
// No need to create a new object every time
|
||||
if (defaultFailedHandler == null) {
|
||||
defaultFailedHandler = new FilterFailedHandler() {
|
||||
@Override
|
||||
public boolean handle(PacketEvent event, Filter filter, Exception ex) {
|
||||
reporter.reportMinimal(plugin, "filterEvent(PacketEvent)", ex, event);
|
||||
reporter.reportWarning(this,
|
||||
Report.newBuilder(REPORT_FILTER_REMOVED_FOR_ERROR).messageParam(filter.getName(), ex.getClass().getSimpleName())
|
||||
);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
}
|
||||
return defaultFailedHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether or not to pass the given packet event to the packet listeners.
|
||||
* <p>
|
||||
* Uses a default filter failure handler that simply prints the error message and removes the filter.
|
||||
* @param event - the event.
|
||||
* @return TRUE if we should, FALSE otherwise.
|
||||
*/
|
||||
public boolean filterEvent(PacketEvent event) {
|
||||
return filterEvent(event, getDefaultErrorHandler());
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether or not to pass the given packet event to the packet listeners.
|
||||
* @param event - the event.
|
||||
* @param handler - failure handler.
|
||||
* @return TRUE if we should, FALSE otherwise.
|
||||
*/
|
||||
public boolean filterEvent(PacketEvent event, FilterFailedHandler handler) {
|
||||
for (Iterator<Filter> it = filters.iterator(); it.hasNext(); ) {
|
||||
Filter filter = it.next();
|
||||
|
||||
try {
|
||||
if (!filter.evaluate(engine, event)) {
|
||||
return false;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
if (!handler.handle(event, filter, ex)) {
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
// Pass!
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the script engine if necessary.
|
||||
*/
|
||||
private void checkScriptStatus() {
|
||||
// Start the engine
|
||||
if (uninitialized) {
|
||||
uninitialized = false;
|
||||
initalizeScript();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Description: Adds or removes a simple packet filter.
|
||||
Usage: /<command> add|remove name [packet IDs]
|
||||
*/
|
||||
@Override
|
||||
protected boolean handleCommand(CommandSender sender, String[] args) {
|
||||
checkScriptStatus();
|
||||
|
||||
if (!config.isDebug()) {
|
||||
sender.sendMessage(ChatColor.RED + "Debug mode must be enabled in the configuration first!");
|
||||
return true;
|
||||
}
|
||||
if (!isInitialized()) {
|
||||
sender.sendMessage(ChatColor.RED + "JavaScript engine was not present. Filter system is disabled.");
|
||||
return true;
|
||||
}
|
||||
|
||||
final SubCommand command = parseCommand(args, 0);
|
||||
final String name = args[1];
|
||||
|
||||
switch (command) {
|
||||
case ADD:
|
||||
// Never overwrite an existing filter
|
||||
if (findFilter(name) != null) {
|
||||
sender.sendMessage(ChatColor.RED + "Filter " + name + " already exists. Remove it first.");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Prepare the input to the packet type parser
|
||||
Deque<String> rangeArguments = toQueue(args, 2);
|
||||
|
||||
final PacketTypeParser parser = new PacketTypeParser();
|
||||
final Set<PacketType> packets = parser.parseTypes(rangeArguments, PacketTypeParser.DEFAULT_MAX_RANGE);
|
||||
sender.sendMessage("Enter filter program ('}' to complete or CANCEL):");
|
||||
|
||||
// Make sure we can use the conversable interface
|
||||
if (sender instanceof Conversable) {
|
||||
final MultipleLinesPrompt prompt =
|
||||
new MultipleLinesPrompt(new CompilationSuccessCanceller(), "function(event, packet) {");
|
||||
|
||||
new ConversationFactory(plugin).
|
||||
withFirstPrompt(prompt).
|
||||
withEscapeSequence("CANCEL").
|
||||
withLocalEcho(false).
|
||||
addConversationAbandonedListener(new ConversationAbandonedListener() {
|
||||
@Override
|
||||
public void conversationAbandoned(ConversationAbandonedEvent event) {
|
||||
try {
|
||||
final Conversable whom = event.getContext().getForWhom();
|
||||
|
||||
if (event.gracefulExit()) {
|
||||
String predicate = prompt.removeAccumulatedInput(event.getContext());
|
||||
Filter filter = new Filter(name, predicate, packets);
|
||||
|
||||
// Print the last line as well
|
||||
whom.sendRawMessage(prompt.getPromptText(event.getContext()));
|
||||
|
||||
try {
|
||||
// Force early compilation
|
||||
filter.compile(engine);
|
||||
|
||||
filters.add(filter);
|
||||
whom.sendRawMessage(ChatColor.GOLD + "Added filter " + name);
|
||||
} catch (ScriptException e) {
|
||||
e.printStackTrace();
|
||||
whom.sendRawMessage(ChatColor.GOLD + "Compilation error: " + e.getMessage());
|
||||
}
|
||||
} else {
|
||||
// Too bad
|
||||
whom.sendRawMessage(ChatColor.RED + "Cancelled filter.");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
reporter.reportDetailed(this,
|
||||
Report.newBuilder(REPORT_CANNOT_HANDLE_CONVERSATION).error(e).callerParam(event)
|
||||
);
|
||||
}
|
||||
}
|
||||
}).
|
||||
buildConversation((Conversable) sender).
|
||||
begin();
|
||||
} else {
|
||||
sender.sendMessage(ChatColor.RED + "Only console and players are supported!");
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case REMOVE:
|
||||
Filter filter = findFilter(name);
|
||||
|
||||
// See if it exists before we remove it
|
||||
if (filter != null) {
|
||||
filter.close(engine);
|
||||
filters.remove(filter);
|
||||
sender.sendMessage(ChatColor.GOLD + "Removed filter " + name);
|
||||
} else {
|
||||
sender.sendMessage(ChatColor.RED + "Unable to find a filter by the name " + name);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup a filter by its name.
|
||||
* @param name - the filter name.
|
||||
* @return The filter, or NULL if not found.
|
||||
*/
|
||||
private Filter findFilter(String name) {
|
||||
// We'll just use a linear scan for now - we don't expect that many filters
|
||||
for (Filter filter : filters) {
|
||||
if (filter.getName().equalsIgnoreCase(name)) {
|
||||
return filter;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private SubCommand parseCommand(String[] args, int index) {
|
||||
String text = args[index].toUpperCase();
|
||||
|
||||
try {
|
||||
return SubCommand.valueOf(text);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new IllegalArgumentException(text + " is not a valid sub command. Must be add or remove.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,462 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Deque;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.WeakHashMap;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import com.comphenix.protocol.PacketType.Sender;
|
||||
import com.comphenix.protocol.concurrency.PacketTypeSet;
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.error.Report;
|
||||
import com.comphenix.protocol.error.ReportType;
|
||||
import com.comphenix.protocol.events.ListeningWhitelist;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.events.PacketListener;
|
||||
import com.comphenix.protocol.utility.ChatExtensions;
|
||||
import com.comphenix.protocol.utility.HexDumper;
|
||||
import com.google.common.collect.MapMaker;
|
||||
|
||||
/**
|
||||
* Handles the "packet" debug command.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
class CommandPacket extends CommandBase {
|
||||
public static final ReportType REPORT_CANNOT_SEND_MESSAGE = new ReportType("Cannot send chat message.");
|
||||
|
||||
private enum SubCommand {
|
||||
ADD, REMOVE, NAMES, PAGE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Name of this command.
|
||||
*/
|
||||
public static final String NAME = "packet";
|
||||
|
||||
/**
|
||||
* Number of lines per page.
|
||||
*/
|
||||
public static final int PAGE_LINE_COUNT = 9;
|
||||
|
||||
private Plugin plugin;
|
||||
private Logger logger;
|
||||
private ProtocolManager manager;
|
||||
|
||||
private ChatExtensions chatter;
|
||||
|
||||
// The main parser
|
||||
private PacketTypeParser typeParser = new PacketTypeParser();
|
||||
|
||||
// Paged message
|
||||
private Map<CommandSender, List<String>> pagedMessage = new WeakHashMap<CommandSender, List<String>>();
|
||||
|
||||
// Current registered packet types
|
||||
private PacketTypeSet packetTypes = new PacketTypeSet();
|
||||
private PacketTypeSet extendedTypes = new PacketTypeSet();
|
||||
|
||||
// Compare listeners
|
||||
private PacketTypeSet compareTypes = new PacketTypeSet();
|
||||
private Map<PacketEvent, String> originalPackets = new MapMaker().weakKeys().makeMap();
|
||||
|
||||
// The packet listener
|
||||
private PacketListener listener;
|
||||
|
||||
// Compare listener
|
||||
private PacketListener compareListener;
|
||||
|
||||
// Filter packet events
|
||||
private CommandFilter filter;
|
||||
|
||||
public CommandPacket(ErrorReporter reporter, Plugin plugin, Logger logger, CommandFilter filter, ProtocolManager manager) {
|
||||
super(reporter, CommandBase.PERMISSION_ADMIN, NAME, 1);
|
||||
this.plugin = plugin;
|
||||
this.logger = logger;
|
||||
this.manager = manager;
|
||||
this.filter = filter;
|
||||
this.chatter = new ChatExtensions(manager);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a message without invoking the packet listeners.
|
||||
* @param receiver - the player to send it to.
|
||||
* @param message - the message to send.
|
||||
* @return TRUE if the message was sent successfully, FALSE otherwise.
|
||||
*/
|
||||
public void sendMessageSilently(CommandSender receiver, String message) {
|
||||
chatter.sendMessageSilently(receiver, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast a message without invoking any packet listeners.
|
||||
* @param message - message to send.
|
||||
* @param permission - permission required to receieve the message. NULL to target everyone.
|
||||
*/
|
||||
public void broadcastMessageSilently(String message, String permission) {
|
||||
chatter.broadcastMessageSilently(message, permission);
|
||||
}
|
||||
|
||||
private void printPage(CommandSender sender, int pageIndex) {
|
||||
List<String> paged = pagedMessage.get(sender);
|
||||
|
||||
// Make sure the player has any pages
|
||||
if (paged != null) {
|
||||
int lastPage = ((paged.size() - 1) / PAGE_LINE_COUNT) + 1;
|
||||
|
||||
for (int i = PAGE_LINE_COUNT * (pageIndex - 1); i < PAGE_LINE_COUNT * pageIndex; i++) {
|
||||
if (i < paged.size()) {
|
||||
sendMessageSilently(sender, " " + paged.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
// More data?
|
||||
if (pageIndex < lastPage) {
|
||||
sendMessageSilently(sender, "Send /packet page " + (pageIndex + 1) + " for the next page.");
|
||||
}
|
||||
|
||||
} else {
|
||||
sendMessageSilently(sender, ChatColor.RED + "No pages found.");
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Description: Adds or removes a simple packet listener.
|
||||
Usage: /<command> add|remove client|server|both [ID start] [ID stop] [detailed]
|
||||
*/
|
||||
@Override
|
||||
protected boolean handleCommand(CommandSender sender, String[] args) {
|
||||
try {
|
||||
Deque<String> arguments = new ArrayDeque<String>(Arrays.asList(args));
|
||||
SubCommand subCommand = parseCommand(arguments);
|
||||
|
||||
// Commands with different parameters
|
||||
if (subCommand == SubCommand.PAGE) {
|
||||
if (args.length <= 1) {
|
||||
sendMessageSilently(sender, ChatColor.RED + "Must specify a page index.");
|
||||
return true;
|
||||
}
|
||||
|
||||
int page = Integer.parseInt(args[1]);
|
||||
|
||||
if (page > 0)
|
||||
printPage(sender, page);
|
||||
else
|
||||
sendMessageSilently(sender, ChatColor.RED + "Page index must be greater than zero.");
|
||||
return true;
|
||||
}
|
||||
|
||||
Set<PacketType> types = typeParser.parseTypes(arguments, PacketTypeParser.DEFAULT_MAX_RANGE);
|
||||
Boolean detailed = parseBoolean(arguments, "detailed");
|
||||
Boolean compare = parseBoolean(arguments, "compare");
|
||||
|
||||
// Notify user
|
||||
if (typeParser.getLastProtocol() == null) {
|
||||
sender.sendMessage(ChatColor.YELLOW + "Warning: Missing protocol (PLAY, etc) - assuming legacy IDs.");
|
||||
}
|
||||
if (arguments.size() > 0) {
|
||||
throw new IllegalArgumentException("Cannot parse " + arguments);
|
||||
}
|
||||
|
||||
// The last elements are optional
|
||||
if (detailed == null) {
|
||||
detailed = false;
|
||||
}
|
||||
if (compare == null) {
|
||||
compare = false;
|
||||
} else {
|
||||
// This is implied - we cannot compare content without going detailed
|
||||
detailed = true;
|
||||
}
|
||||
|
||||
// Perform commands
|
||||
if (subCommand == SubCommand.ADD) {
|
||||
// The add command is dangerous - don't default on the connection side
|
||||
if (args.length == 1) {
|
||||
sender.sendMessage(ChatColor.RED + "Please specify a connection side.");
|
||||
return false;
|
||||
}
|
||||
|
||||
executeAddCommand(sender, types, detailed, compare);
|
||||
} else if (subCommand == SubCommand.REMOVE) {
|
||||
executeRemoveCommand(sender, types);
|
||||
} else if (subCommand == SubCommand.NAMES) {
|
||||
executeNamesCommand(sender, types);
|
||||
}
|
||||
|
||||
} catch (NumberFormatException e) {
|
||||
sendMessageSilently(sender, ChatColor.RED + "Cannot parse number: " + e.getMessage());
|
||||
} catch (IllegalArgumentException e) {
|
||||
sendMessageSilently(sender, ChatColor.RED + e.getMessage());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void executeAddCommand(CommandSender sender, Set<PacketType> addition, boolean detailed, boolean compare) {
|
||||
packetTypes.addAll(addition);
|
||||
|
||||
// Also mark these types as "detailed"
|
||||
if (detailed) {
|
||||
extendedTypes.addAll(addition);
|
||||
}
|
||||
// Whether or not to compare the packet with the initial state
|
||||
if (compare) {
|
||||
compareTypes.addAll(addition);
|
||||
}
|
||||
|
||||
updatePacketListener();
|
||||
sendMessageSilently(sender, ChatColor.YELLOW + "Added listener " + getWhitelistInfo(listener));
|
||||
}
|
||||
|
||||
private void executeRemoveCommand(CommandSender sender, Set<PacketType> removal) {
|
||||
packetTypes.removeAll(removal);
|
||||
extendedTypes.removeAll(removal);
|
||||
compareTypes.removeAll(removal);
|
||||
updatePacketListener();
|
||||
sendMessageSilently(sender, ChatColor.YELLOW + "Removing packet types.");
|
||||
}
|
||||
|
||||
private void executeNamesCommand(CommandSender sender, Set<PacketType> types) {
|
||||
List<String> messages = new ArrayList<String>();
|
||||
|
||||
// Print the equivalent name of every given ID
|
||||
for (PacketType type : types) {
|
||||
messages.add(ChatColor.YELLOW + type.toString());
|
||||
}
|
||||
|
||||
if (sender instanceof Player && messages.size() > 0 && messages.size() > PAGE_LINE_COUNT) {
|
||||
// Divide the messages into chuncks
|
||||
pagedMessage.put(sender, messages);
|
||||
printPage(sender, 1);
|
||||
|
||||
} else {
|
||||
// Just print the damn thing
|
||||
for (String message : messages) {
|
||||
sendMessageSilently(sender, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve whitelist information about a given listener.
|
||||
* @param listener - the given listener.
|
||||
* @return Whitelist information.
|
||||
*/
|
||||
private String getWhitelistInfo(PacketListener listener) {
|
||||
boolean sendingEmpty = ListeningWhitelist.isEmpty(listener.getSendingWhitelist());
|
||||
boolean receivingEmpty = ListeningWhitelist.isEmpty(listener.getReceivingWhitelist());
|
||||
|
||||
if (!sendingEmpty && !receivingEmpty)
|
||||
return String.format("Sending: %s, Receiving: %s", listener.getSendingWhitelist(), listener.getReceivingWhitelist());
|
||||
else if (!sendingEmpty)
|
||||
return listener.getSendingWhitelist().toString();
|
||||
else if (!receivingEmpty)
|
||||
return listener.getReceivingWhitelist().toString();
|
||||
else
|
||||
return "[None]";
|
||||
}
|
||||
|
||||
private Set<PacketType> filterTypes(Set<PacketType> types, Sender sender) {
|
||||
final Set<PacketType> result = new HashSet<>();
|
||||
|
||||
for (PacketType type : types) {
|
||||
if (type.getSender() == sender) {
|
||||
result.add(type);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public PacketListener createPacketListener(Set<PacketType> type) {
|
||||
final ListeningWhitelist serverList = ListeningWhitelist.newBuilder().
|
||||
types(filterTypes(type, Sender.SERVER)).
|
||||
gamePhaseBoth().
|
||||
monitor().
|
||||
build();
|
||||
|
||||
final ListeningWhitelist clientList = ListeningWhitelist.newBuilder(serverList).
|
||||
types(filterTypes(type, Sender.CLIENT)).
|
||||
monitor().
|
||||
build();
|
||||
|
||||
return new PacketListener() {
|
||||
@Override
|
||||
public void onPacketSending(PacketEvent event) {
|
||||
if (filter.filterEvent(event)) {
|
||||
printInformation(event);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketReceiving(PacketEvent event) {
|
||||
if (filter.filterEvent(event)) {
|
||||
printInformation(event);
|
||||
}
|
||||
}
|
||||
|
||||
private void printInformation(PacketEvent event) {
|
||||
String verb = event.isServerPacket() ? "Sent" : "Received";
|
||||
String format = event.isServerPacket() ?
|
||||
"%s %s to %s" :
|
||||
"%s %s from %s";
|
||||
|
||||
String shortDescription = String.format(format,
|
||||
event.isCancelled() ? "Cancelled" : verb,
|
||||
event.getPacketType(),
|
||||
event.getPlayer().getName()
|
||||
);
|
||||
|
||||
// Detailed will print the packet's content too
|
||||
if (extendedTypes.contains(event.getPacketType())) {
|
||||
try {
|
||||
String original = originalPackets.remove(event);
|
||||
|
||||
// Also print original
|
||||
if (original != null) {
|
||||
logger.info("Initial packet:\n" + original + " -> ");
|
||||
}
|
||||
|
||||
logger.info(shortDescription + ":\n" + HexDumper.getPacketDescription(
|
||||
event.getPacket())
|
||||
);
|
||||
} catch (IllegalAccessException e) {
|
||||
logger.log(Level.WARNING, "Unable to use reflection.", e);
|
||||
}
|
||||
} else {
|
||||
logger.info(shortDescription + ".");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListeningWhitelist getSendingWhitelist() {
|
||||
return serverList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListeningWhitelist getReceivingWhitelist() {
|
||||
return clientList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Plugin getPlugin() {
|
||||
return plugin;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public PacketListener createCompareListener(Set<PacketType> type) {
|
||||
final ListeningWhitelist serverList = ListeningWhitelist.newBuilder().
|
||||
types(filterTypes(type, Sender.SERVER)).
|
||||
gamePhaseBoth().
|
||||
lowest().
|
||||
build();
|
||||
|
||||
final ListeningWhitelist clientList = ListeningWhitelist.newBuilder(serverList).
|
||||
types(filterTypes(type, Sender.CLIENT)).
|
||||
lowest().
|
||||
build();
|
||||
|
||||
return new PacketListener() {
|
||||
@Override
|
||||
public void onPacketSending(PacketEvent event) {
|
||||
savePacketState(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketReceiving(PacketEvent event) {
|
||||
savePacketState(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the original value.
|
||||
* @param event - the event with the packet to save.
|
||||
*/
|
||||
private void savePacketState(PacketEvent event) {
|
||||
try {
|
||||
originalPackets.put(event, HexDumper.getPacketDescription(event.getPacket()));
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException("Cannot read packet.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListeningWhitelist getSendingWhitelist() {
|
||||
return serverList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListeningWhitelist getReceivingWhitelist() {
|
||||
return clientList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Plugin getPlugin() {
|
||||
return plugin;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public PacketListener updatePacketListener() {
|
||||
if (listener != null) {
|
||||
manager.removePacketListener(listener);
|
||||
}
|
||||
if (compareListener != null) {
|
||||
manager.removePacketListener(compareListener);
|
||||
}
|
||||
|
||||
// Register the new listeners
|
||||
listener = createPacketListener(packetTypes.values());
|
||||
compareListener = createCompareListener(compareTypes.values());
|
||||
manager.addPacketListener(listener);
|
||||
manager.addPacketListener(compareListener);
|
||||
return listener;
|
||||
}
|
||||
|
||||
private SubCommand parseCommand(Deque<String> arguments)
|
||||
{
|
||||
final String text = arguments.remove().toLowerCase();
|
||||
|
||||
switch (arguments.remove().toLowerCase()) {
|
||||
case "add":
|
||||
return SubCommand.ADD;
|
||||
case "remove":
|
||||
return SubCommand.REMOVE;
|
||||
case "names":
|
||||
return SubCommand.NAMES;
|
||||
case "page":
|
||||
return SubCommand.PAGE;
|
||||
default:
|
||||
throw new IllegalArgumentException(text + " is not a valid sub command. Must be add or remove.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,289 @@
|
||||
/**
|
||||
* 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) {
|
||||
|
||||
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 (Closer closer = Closer.create()) {
|
||||
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!");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
package com.comphenix.protocol;
|
||||
|
||||
import org.bukkit.conversations.Conversation;
|
||||
import org.bukkit.conversations.ConversationCanceller;
|
||||
import org.bukkit.conversations.ConversationContext;
|
||||
import org.bukkit.conversations.ExactMatchConversationCanceller;
|
||||
import org.bukkit.conversations.Prompt;
|
||||
import org.bukkit.conversations.StringPrompt;
|
||||
|
||||
/**
|
||||
* Represents a conversation prompt that accepts a list of lines.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
class MultipleLinesPrompt extends StringPrompt {
|
||||
/**
|
||||
* Represents a canceller that determines if the multiple lines prompt is finished.
|
||||
* @author Kristian
|
||||
*/
|
||||
public static interface MultipleConversationCanceller extends ConversationCanceller {
|
||||
@Override
|
||||
public boolean cancelBasedOnInput(ConversationContext context, String currentLine);
|
||||
|
||||
/**
|
||||
* Determine if the current prompt is done based on the context, last
|
||||
* line and collected lines.
|
||||
*
|
||||
* @param context - current context.
|
||||
* @param currentLine - current (last) line.
|
||||
* @param lines - collected lines.
|
||||
* @param lineCount - number of lines.
|
||||
* @return TRUE if we are done, FALSE otherwise.
|
||||
*/
|
||||
public boolean cancelBasedOnInput(ConversationContext context, String currentLine,
|
||||
StringBuilder lines, int lineCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* A wrapper class for turning a ConversationCanceller into a MultipleConversationCanceller.
|
||||
* @author Kristian
|
||||
*/
|
||||
private static class MultipleWrapper implements MultipleConversationCanceller {
|
||||
private ConversationCanceller canceller;
|
||||
|
||||
public MultipleWrapper(ConversationCanceller canceller) {
|
||||
this.canceller = canceller;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean cancelBasedOnInput(ConversationContext context, String currentLine) {
|
||||
return canceller.cancelBasedOnInput(context, currentLine);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean cancelBasedOnInput(ConversationContext context, String currentLine,
|
||||
StringBuilder lines, int lineCount) {
|
||||
return cancelBasedOnInput(context, currentLine);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setConversation(Conversation conversation) {
|
||||
canceller.setConversation(conversation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MultipleWrapper clone() {
|
||||
return new MultipleWrapper(canceller.clone());
|
||||
}
|
||||
}
|
||||
|
||||
// Feels a bit like Android
|
||||
private static final String KEY = "multiple_lines_prompt";
|
||||
private static final String KEY_LAST = KEY + ".last_line";
|
||||
private static final String KEY_LINES = KEY + ".linecount";
|
||||
|
||||
private final MultipleConversationCanceller endMarker;
|
||||
private final String initialPrompt;
|
||||
|
||||
/**
|
||||
* Retrieve and remove the current accumulated input.
|
||||
*
|
||||
* @param context
|
||||
* - conversation context.
|
||||
* @return The accumulated input, or NULL if not found.
|
||||
*/
|
||||
public String removeAccumulatedInput(ConversationContext context) {
|
||||
Object result = context.getSessionData(KEY);
|
||||
|
||||
if (result instanceof StringBuilder) {
|
||||
context.setSessionData(KEY, null);
|
||||
context.setSessionData(KEY_LINES, null);
|
||||
return ((StringBuilder) result).toString();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a multiple lines input prompt with a specific end marker.
|
||||
* <p>
|
||||
* This is usually an empty string.
|
||||
*
|
||||
* @param endMarker - the end marker.
|
||||
*/
|
||||
public MultipleLinesPrompt(String endMarker, String initialPrompt) {
|
||||
this(new ExactMatchConversationCanceller(endMarker), initialPrompt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a multiple lines input prompt with a specific end marker implementation.
|
||||
* <p>
|
||||
* Note: Use {@link #MultipleLinesPrompt(MultipleConversationCanceller, String)} if implementing a custom canceller.
|
||||
* @param endMarker - the end marker.
|
||||
* @param initialPrompt - the initial prompt text.
|
||||
*/
|
||||
public MultipleLinesPrompt(ConversationCanceller endMarker, String initialPrompt) {
|
||||
this.endMarker = new MultipleWrapper(endMarker);
|
||||
this.initialPrompt = initialPrompt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a multiple lines input prompt with a specific end marker implementation.
|
||||
* @param endMarker - the end marker.
|
||||
* @param initialPrompt - the initial prompt text.
|
||||
*/
|
||||
public MultipleLinesPrompt(MultipleConversationCanceller endMarker, String initialPrompt) {
|
||||
this.endMarker = endMarker;
|
||||
this.initialPrompt = initialPrompt;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Prompt acceptInput(ConversationContext context, String in) {
|
||||
StringBuilder result = (StringBuilder) context.getSessionData(KEY);
|
||||
Integer count = (Integer) context.getSessionData(KEY_LINES);
|
||||
|
||||
// Handle first run
|
||||
if (result == null)
|
||||
context.setSessionData(KEY, result = new StringBuilder());
|
||||
if (count == null)
|
||||
count = 0;
|
||||
|
||||
// Save the last line as well
|
||||
context.setSessionData(KEY_LAST, in);
|
||||
context.setSessionData(KEY_LINES, ++count);
|
||||
result.append(in + "\n");
|
||||
|
||||
// And we're done
|
||||
if (endMarker.cancelBasedOnInput(context, in, result, count))
|
||||
return Prompt.END_OF_CONVERSATION;
|
||||
else
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPromptText(ConversationContext context) {
|
||||
Object last = context.getSessionData(KEY_LAST);
|
||||
|
||||
if (last instanceof String)
|
||||
return (String) last;
|
||||
else
|
||||
return initialPrompt;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,284 @@
|
||||
/**
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2017 Dan Mulloy
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
package com.comphenix.protocol;
|
||||
|
||||
import com.comphenix.protocol.PacketType.Protocol;
|
||||
import com.comphenix.protocol.PacketType.Sender;
|
||||
import com.comphenix.protocol.events.ListeningWhitelist;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.events.PacketListener;
|
||||
import com.comphenix.protocol.injector.netty.WirePacket;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.comphenix.protocol.reflect.accessors.Accessors;
|
||||
import com.comphenix.protocol.reflect.accessors.MethodAccessor;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.text.MessageFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.logging.FileHandler;
|
||||
import java.util.logging.Formatter;
|
||||
import java.util.logging.Handler;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.LogRecord;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* Logs packets to a given stream
|
||||
* @author dmulloy2
|
||||
*/
|
||||
public class PacketLogging implements CommandExecutor, PacketListener {
|
||||
public static final String NAME = "packetlog";
|
||||
|
||||
private static MethodAccessor HEX_DUMP;
|
||||
|
||||
private List<PacketType> sendingTypes = new ArrayList<>();
|
||||
private List<PacketType> receivingTypes = new ArrayList<>();
|
||||
|
||||
private ListeningWhitelist sendingWhitelist;
|
||||
private ListeningWhitelist receivingWhitelist;
|
||||
|
||||
private Logger fileLogger;
|
||||
private LogLocation location = LogLocation.FILE;
|
||||
|
||||
private final ProtocolManager manager;
|
||||
private final Plugin plugin;
|
||||
|
||||
PacketLogging(Plugin plugin, ProtocolManager manager) {
|
||||
this.plugin = plugin;
|
||||
this.manager = manager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
PacketType type = null;
|
||||
|
||||
try {
|
||||
if (args.length > 2) {
|
||||
Protocol protocol;
|
||||
|
||||
try {
|
||||
protocol = Protocol.valueOf(args[0].toUpperCase());
|
||||
} catch (IllegalArgumentException ex) {
|
||||
sender.sendMessage(ChatColor.RED + "Unknown protocol " + args[0]);
|
||||
return true;
|
||||
}
|
||||
|
||||
Sender pSender;
|
||||
|
||||
try {
|
||||
pSender = Sender.valueOf(args[1].toUpperCase());
|
||||
} catch (IllegalArgumentException ex) {
|
||||
sender.sendMessage(ChatColor.RED + "Unknown sender: " + args[1]);
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
try { // Try IDs first
|
||||
int id = Integer.parseInt(args[2]);
|
||||
type = PacketType.findCurrent(protocol, pSender, id);
|
||||
} catch (NumberFormatException ex) { // Check packet names
|
||||
String name = args[2];
|
||||
for (PacketType packet : PacketType.values()) {
|
||||
if (packet.getProtocol() == protocol && packet.getSender() == pSender) {
|
||||
if (packet.name().equalsIgnoreCase(name)) {
|
||||
type = packet;
|
||||
break;
|
||||
}
|
||||
for (String className : packet.getClassNames()) {
|
||||
if (className.equalsIgnoreCase(name)) {
|
||||
type = packet;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IllegalArgumentException ex) { // RIP
|
||||
type = null;
|
||||
}
|
||||
|
||||
if (type == null) {
|
||||
sender.sendMessage(ChatColor.RED + "Unknown packet: " + args[2]);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (args.length > 3) {
|
||||
if (args[3].equalsIgnoreCase("console")) {
|
||||
this.location = LogLocation.CONSOLE;
|
||||
} else {
|
||||
this.location = LogLocation.FILE;
|
||||
}
|
||||
}
|
||||
|
||||
if (pSender == Sender.CLIENT) {
|
||||
if (receivingTypes.contains(type)) {
|
||||
receivingTypes.remove(type);
|
||||
} else {
|
||||
receivingTypes.add(type);
|
||||
}
|
||||
} else {
|
||||
if (sendingTypes.contains(type)) {
|
||||
sendingTypes.remove(type);
|
||||
} else {
|
||||
sendingTypes.add(type);
|
||||
}
|
||||
}
|
||||
|
||||
startLogging();
|
||||
sender.sendMessage(ChatColor.GREEN + "Now logging " + type.getPacketClass().getSimpleName());
|
||||
return true;
|
||||
}
|
||||
|
||||
sender.sendMessage(ChatColor.RED + "Invalid syntax: /packetlog <protocol> <sender> <packet> [location]");
|
||||
return true;
|
||||
} catch (Throwable ex) {
|
||||
sender.sendMessage(ChatColor.RED + "Failed to parse command: " + ex.toString());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private void startLogging() {
|
||||
manager.removePacketListener(this);
|
||||
|
||||
if (sendingTypes.isEmpty() && receivingTypes.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.sendingWhitelist = ListeningWhitelist.newBuilder().types(sendingTypes).build();
|
||||
this.receivingWhitelist = ListeningWhitelist.newBuilder().types(receivingTypes).build();
|
||||
|
||||
// Setup the file logger if it hasn't been already
|
||||
if (location == LogLocation.FILE && fileLogger == null) {
|
||||
fileLogger = Logger.getLogger("ProtocolLib-FileLogging");
|
||||
|
||||
for (Handler handler : fileLogger.getHandlers())
|
||||
fileLogger.removeHandler(handler);
|
||||
fileLogger.setUseParentHandlers(false);
|
||||
|
||||
try {
|
||||
File logFile = new File(plugin.getDataFolder(), "log.log");
|
||||
FileHandler handler = new FileHandler(logFile.getAbsolutePath(), true);
|
||||
handler.setFormatter(new LogFormatter());
|
||||
fileLogger.addHandler(handler);
|
||||
} catch (IOException ex) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Failed to obtain log file:", ex);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
manager.addPacketListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketSending(PacketEvent event) {
|
||||
log(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketReceiving(PacketEvent event) {
|
||||
log(event);
|
||||
}
|
||||
|
||||
// Here's where the magic happens
|
||||
|
||||
private static String hexDump(byte[] bytes) throws IOException {
|
||||
try (ByteArrayOutputStream output = new ByteArrayOutputStream()) {
|
||||
if (HEX_DUMP == null) {
|
||||
Class<?> hexDumpClass = MinecraftReflection.getLibraryClass("org.apache.commons.io.HexDump");
|
||||
HEX_DUMP = Accessors.getMethodAccessor(FuzzyReflection.fromClass(hexDumpClass)
|
||||
.getMethodByParameters("dump", byte[].class, long.class, OutputStream.class, int.class));
|
||||
}
|
||||
|
||||
HEX_DUMP.invoke(null, bytes, 0, output, 0);
|
||||
return new String(output.toByteArray(), StandardCharsets.UTF_8);
|
||||
}
|
||||
}
|
||||
|
||||
private void log(PacketEvent event) {
|
||||
try {
|
||||
byte[] bytes = WirePacket.bytesFromPacket(event.getPacket());
|
||||
String hexDump = hexDump(bytes);
|
||||
|
||||
if (location == LogLocation.FILE) {
|
||||
fileLogger.log(Level.INFO, event.getPacketType() + ":");
|
||||
fileLogger.log(Level.INFO, hexDump);
|
||||
fileLogger.log(Level.INFO, "");
|
||||
} else {
|
||||
System.out.println(event.getPacketType() + ":");
|
||||
System.out.println(hexDump);
|
||||
System.out.println();
|
||||
}
|
||||
} catch (Throwable ex) {
|
||||
plugin.getLogger().log(Level.WARNING, "Failed to log packet " + event.getPacketType() + ":", ex);
|
||||
plugin.getLogger().log(Level.WARNING, "Clearing packet logger...");
|
||||
|
||||
sendingTypes.clear();
|
||||
receivingTypes.clear();
|
||||
startLogging();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListeningWhitelist getSendingWhitelist() {
|
||||
return sendingWhitelist;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListeningWhitelist getReceivingWhitelist() {
|
||||
return receivingWhitelist;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Plugin getPlugin() {
|
||||
return plugin;
|
||||
}
|
||||
|
||||
private enum LogLocation {
|
||||
CONSOLE, FILE
|
||||
}
|
||||
|
||||
private static class LogFormatter extends Formatter {
|
||||
private static final SimpleDateFormat DATE = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
private static final String LINE_SEPARATOR = System.getProperty("line.separator");
|
||||
private static final String FORMAT = "[{0}] {1}";
|
||||
|
||||
@Override
|
||||
public String format(LogRecord record) {
|
||||
String string = formatMessage(record);
|
||||
if (string.isEmpty()) {
|
||||
return LINE_SEPARATOR;
|
||||
}
|
||||
|
||||
StringBuilder message = new StringBuilder();
|
||||
message.append(MessageFormat.format(FORMAT, DATE.format(record.getMillis()), string));
|
||||
message.append(LINE_SEPARATOR);
|
||||
return message.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol;
|
||||
|
||||
import com.comphenix.protocol.events.ListenerPriority;
|
||||
import com.comphenix.protocol.events.NetworkMarker;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.injector.netty.WirePacket;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
/**
|
||||
* Represents a object capable of sending or receiving packets.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public interface PacketStream {
|
||||
|
||||
/**
|
||||
* Send a packet to the given player.
|
||||
*
|
||||
* @param receiver - the reciever.
|
||||
* @param packet - packet to send.
|
||||
*/
|
||||
void sendServerPacket(Player receiver, PacketContainer packet);
|
||||
|
||||
/**
|
||||
* Send a packet to the given player.
|
||||
*
|
||||
* @param receiver - the reciever.
|
||||
* @param packet - packet to send.
|
||||
* @param filters - whether or not to invoke any packet filters below {@link ListenerPriority#MONITOR}.
|
||||
*/
|
||||
void sendServerPacket(Player receiver, PacketContainer packet, boolean filters);
|
||||
|
||||
/**
|
||||
* Send a packet to the given player.
|
||||
*
|
||||
* @param receiver - the receiver.
|
||||
* @param packet - packet to send.
|
||||
* @param marker - the network marker to use.
|
||||
* @param filters - whether or not to invoke any packet filters below {@link ListenerPriority#MONITOR}.
|
||||
*/
|
||||
void sendServerPacket(Player receiver, PacketContainer packet, NetworkMarker marker, boolean filters);
|
||||
|
||||
/**
|
||||
* Send a wire packet to the given player.
|
||||
*
|
||||
* @param receiver - the receiver.
|
||||
* @param id - packet id.
|
||||
* @param bytes - packet bytes.
|
||||
*/
|
||||
void sendWirePacket(Player receiver, int id, byte[] bytes);
|
||||
|
||||
/**
|
||||
* Send a wire packet to the given player.
|
||||
*
|
||||
* @param receiver - the receiver.
|
||||
* @param packet - packet to send.
|
||||
*/
|
||||
void sendWirePacket(Player receiver, WirePacket packet);
|
||||
|
||||
/**
|
||||
* Simulate recieving a certain packet from a given player.
|
||||
*
|
||||
* @param sender - the sender.
|
||||
* @param packet - the packet that was sent.
|
||||
*/
|
||||
void receiveClientPacket(Player sender, PacketContainer packet);
|
||||
|
||||
/**
|
||||
* Simulate recieving a certain packet from a given player.
|
||||
*
|
||||
* @param sender - the sender.
|
||||
* @param packet - the packet that was sent.
|
||||
* @param filters - whether or not to invoke any packet filters below {@link ListenerPriority#MONITOR}.
|
||||
*/
|
||||
void receiveClientPacket(Player sender, PacketContainer packet, boolean filters);
|
||||
|
||||
/**
|
||||
* Simulate recieving a certain packet from a given player.
|
||||
*
|
||||
* @param sender - the sender.
|
||||
* @param packet - the packet that was sent.
|
||||
* @param marker - the network marker to use.
|
||||
* @param filters - whether or not to invoke any packet filters below {@link ListenerPriority#MONITOR}.
|
||||
*/
|
||||
void receiveClientPacket(Player sender, PacketContainer packet, NetworkMarker marker, boolean filters);
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
Loaded 100 of 381 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user