Compare commits
No files matched your search
+174
@@ -0,0 +1,174 @@
|
||||
#################
|
||||
## Eclipse
|
||||
#################
|
||||
|
||||
# Generic project files
|
||||
.project
|
||||
.classpath
|
||||
.settings/
|
||||
bin/
|
||||
|
||||
*.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/
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
language: java
|
||||
jdk:
|
||||
- oraclejdk8
|
||||
- oraclejdk7
|
||||
script: mvn clean test -U
|
||||
before_install: cd ProtocolLib
|
||||
install: true
|
||||
notifications:
|
||||
email:
|
||||
recipients:
|
||||
- "dmulloy2@live.com"
|
||||
on_success: change
|
||||
on_failure: always
|
||||
+339
@@ -0,0 +1,339 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change free
|
||||
software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Lesser General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. You must make sure that they, too, receive or can get the
|
||||
source code. And you must show them these terms so they know their
|
||||
rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's
|
||||
source code as you receive it, in any medium, provided that you
|
||||
conspicuously and appropriately publish on each copy an appropriate
|
||||
copyright notice and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
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.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program is interactive, make it output a short notice like this
|
||||
when it starts in an interactive mode:
|
||||
|
||||
Gnomovision version 69, Copyright (C) year name of author
|
||||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, the commands you use may
|
||||
be called something other than `show w' and `show c'; they could even be
|
||||
mouse-clicks or menu items--whatever suits your program.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1989
|
||||
Ty Coon, President of Vice
|
||||
|
||||
This General Public License does not permit incorporating your program into
|
||||
proprietary programs. If your program is a subroutine library, you may
|
||||
consider it more useful to permit linking proprietary applications with the
|
||||
library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License.
|
||||
@@ -1,14 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="src" path="src"/>
|
||||
<classpathentry kind="src" output="class_test" path="test"/>
|
||||
<classpathentry kind="lib" path="D:/Games/Minecraft/Server Mods/Server/craftbukkit-1.3.1-R2.0.jar"/>
|
||||
<classpathentry kind="lib" path="lib/cglib-nodep-2.2.3.jar">
|
||||
<attributes>
|
||||
<attribute name="javadoc_location" value="jar:file:/D:/Kristian/Programmer/Binaries/cglib.2.2.3/cglib-docs-2.2.3.jar!/"/>
|
||||
</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/jre6"/>
|
||||
<classpathentry kind="output" path="class"/>
|
||||
</classpath>
|
||||
@@ -0,0 +1,167 @@
|
||||
#################
|
||||
## Eclipse
|
||||
#################
|
||||
|
||||
*.pydevproject
|
||||
.project
|
||||
.metadata
|
||||
bin/
|
||||
tmp/
|
||||
*.tmp
|
||||
*.bak
|
||||
*.swp
|
||||
*~.nib
|
||||
local.properties
|
||||
.classpath
|
||||
.settings/
|
||||
.loadpath
|
||||
target/
|
||||
.gitignore
|
||||
|
||||
# External tool builders
|
||||
.externalToolBuilders/
|
||||
|
||||
# Locally stored "Eclipse launch configurations"
|
||||
*.launch
|
||||
|
||||
# CDT-specific
|
||||
.cproject
|
||||
|
||||
# PDT-specific
|
||||
.buildpath
|
||||
|
||||
|
||||
#################
|
||||
## Visual Studio
|
||||
#################
|
||||
|
||||
## Ignore Visual Studio temporary files, build results, and
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
|
||||
# User-specific files
|
||||
*.suo
|
||||
*.user
|
||||
*.sln.docstates
|
||||
|
||||
# Build results
|
||||
[Dd]ebug/
|
||||
[Rr]elease/
|
||||
*_i.c
|
||||
*_p.c
|
||||
*.ilk
|
||||
*.meta
|
||||
*.obj
|
||||
*.pch
|
||||
*.pdb
|
||||
*.pgc
|
||||
*.pgd
|
||||
*.rsp
|
||||
*.sbr
|
||||
*.tlb
|
||||
*.tli
|
||||
*.tlh
|
||||
*.tmp
|
||||
*.vspscc
|
||||
.builds
|
||||
*.dotCover
|
||||
|
||||
## TODO: If you have NuGet Package Restore enabled, uncomment this
|
||||
#packages/
|
||||
|
||||
# Visual C++ cache files
|
||||
ipch/
|
||||
*.aps
|
||||
*.ncb
|
||||
*.opensdf
|
||||
*.sdf
|
||||
|
||||
# Visual Studio profiler
|
||||
*.psess
|
||||
*.vsp
|
||||
|
||||
# ReSharper is a .NET coding add-in
|
||||
_ReSharper*
|
||||
|
||||
# Installshield output folder
|
||||
[Ee]xpress
|
||||
|
||||
# DocProject is a documentation generator add-in
|
||||
DocProject/buildhelp/
|
||||
DocProject/Help/*.HxT
|
||||
DocProject/Help/*.HxC
|
||||
DocProject/Help/*.hhc
|
||||
DocProject/Help/*.hhk
|
||||
DocProject/Help/*.hhp
|
||||
DocProject/Help/Html2
|
||||
DocProject/Help/html
|
||||
|
||||
# Click-Once directory
|
||||
publish
|
||||
|
||||
# Others
|
||||
[Bb]in
|
||||
[Oo]bj
|
||||
sql
|
||||
TestResults
|
||||
*.Cache
|
||||
ClientBin
|
||||
stylecop.*
|
||||
~$*
|
||||
*.dbmdl
|
||||
Generated_Code #added for RIA/Silverlight projects
|
||||
|
||||
# Backup & report files from converting an old project file to a newer
|
||||
# Visual Studio version. Backup files are not needed, because we have git ;-)
|
||||
_UpgradeReport_Files/
|
||||
Backup*/
|
||||
UpgradeLog*.XML
|
||||
|
||||
|
||||
|
||||
############
|
||||
## Windows
|
||||
############
|
||||
|
||||
# Windows image file caches
|
||||
Thumbs.db
|
||||
|
||||
# Folder config file
|
||||
Desktop.ini
|
||||
|
||||
|
||||
#############
|
||||
## Python
|
||||
#############
|
||||
|
||||
*.py[co]
|
||||
|
||||
# Packages
|
||||
*.egg
|
||||
*.egg-info
|
||||
dist
|
||||
build
|
||||
eggs
|
||||
parts
|
||||
bin
|
||||
var
|
||||
sdist
|
||||
develop-eggs
|
||||
.installed.cfg
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
.coverage
|
||||
.tox
|
||||
|
||||
#Translations
|
||||
*.mo
|
||||
|
||||
#Mr Developer
|
||||
.mr.developer.cfg
|
||||
|
||||
# Mac crap
|
||||
.DS_Store
|
||||
/target
|
||||
/target
|
||||
@@ -1,17 +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>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
||||
@@ -1,11 +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.source=1.6
|
||||
@@ -1,14 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<project default="compile" name="Project ItemDisguise" basedir=".">
|
||||
<!--ANT 1.7 is required -->
|
||||
<target name="compile">
|
||||
<jar destfile="bin/ProtocolLib.jar" filesetmanifest="mergewithoutmain" excludes="test/*">
|
||||
<manifest>
|
||||
<attribute name="Main-Class" value="com.comphenix.itemdisguise.Application"/>
|
||||
<attribute name="Class-Path" value="."/>
|
||||
</manifest>
|
||||
<fileset dir="class"/>
|
||||
<zipfileset excludes="META-INF/*.SF" src="lib/cglib-nodep-2.2.3.jar"/>
|
||||
</jar>
|
||||
</target>
|
||||
</project>
|
||||
Binary file not shown.
@@ -0,0 +1,300 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>ProtocolLib</artifactId>
|
||||
<name>ProtocolLib</name>
|
||||
<version>3.6.5</version>
|
||||
|
||||
<description>Provides read/write access to the Minecraft protocol.</description>
|
||||
<url>http://www.spigotmc.org/resources/protocollib.1997/</url>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<parent>
|
||||
<groupId>com.comphenix.protocol</groupId>
|
||||
<artifactId>ProtocolLib-Parent</artifactId>
|
||||
<version>v3-SNAPSHOT</version>
|
||||
<relativePath>../</relativePath>
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<project.build.number></project.build.number>
|
||||
</properties>
|
||||
|
||||
<build>
|
||||
<defaultGoal>clean install</defaultGoal>
|
||||
<sourceDirectory>src/main/java</sourceDirectory>
|
||||
<testSourceDirectory>src/test/java</testSourceDirectory>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>src/main/resources</directory>
|
||||
<filtering>true</filtering>
|
||||
<excludes>
|
||||
<exclude>**/*.java</exclude>
|
||||
</excludes>
|
||||
</resource>
|
||||
</resources>
|
||||
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>2.3</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<shadedArtifactAttached>false</shadedArtifactAttached>
|
||||
<createDependencyReducedPom>false</createDependencyReducedPom>
|
||||
|
||||
<relocations>
|
||||
<relocation>
|
||||
<pattern>net.sf</pattern>
|
||||
<shadedPattern>com.comphenix.net.sf</shadedPattern>
|
||||
</relocation>
|
||||
</relocations>
|
||||
|
||||
<artifactSet>
|
||||
<excludes>
|
||||
<exclude>org.spigotmc:spigot</exclude>
|
||||
<exclude>org.spigotmc:spigot-api</exclude>
|
||||
<exclude>junit:junit</exclude>
|
||||
</excludes>
|
||||
</artifactSet>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.2</version>
|
||||
<configuration>
|
||||
<source>1.6</source>
|
||||
<target>1.6</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<version>2.5</version>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifestEntries>
|
||||
<Main-Class>com.comphenix.protocol.Application</Main-Class>
|
||||
<Implementation-Title>ProtocolLib</Implementation-Title>
|
||||
<Implementation-Version>${project.version}${project.build.number}</Implementation-Version>
|
||||
<Implementation-Vendor>dmulloy2</Implementation-Vendor>
|
||||
</manifestEntries>
|
||||
</archive>
|
||||
<finalName>ProtocolLib</finalName>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<!--<plugin>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<version>2.7</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<id>attach-javadocs</id>
|
||||
<goals>
|
||||
<goal>jar</goal>
|
||||
<goal>javadoc</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
|
||||
</executions>
|
||||
</plugin>-->
|
||||
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<!--<reporting>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>findbugs-maven-plugin</artifactId>
|
||||
<version>2.5.2</version>
|
||||
<configuration>
|
||||
<threshold>High</threshold>
|
||||
<effort>Default</effort>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<version>2.7</version>
|
||||
<configuration>
|
||||
<failOnError>false</failOnError>
|
||||
<links>
|
||||
<link>http://download.oracle.com/javase/6/docs/api/</link>
|
||||
</links>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
|
||||
</reporting>-->
|
||||
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>jenkins</id>
|
||||
<activation>
|
||||
<property>
|
||||
<name>env.BUILD_NUMBER</name>
|
||||
</property>
|
||||
</activation>
|
||||
<properties>
|
||||
<project.build.number>-b${env.BUILD_NUMBER}</project.build.number>
|
||||
</properties>
|
||||
</profile>
|
||||
|
||||
<profile>
|
||||
<id>release-sign-artifacts</id>
|
||||
<activation>
|
||||
<property>
|
||||
<name>performRelease</name>
|
||||
<value>true</value>
|
||||
</property>
|
||||
</activation>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-source-plugin</artifactId>
|
||||
<version>2.4</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-sources</id>
|
||||
<goals>
|
||||
<goal>jar</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<version>2.10.1</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-javadocs</id>
|
||||
<goals>
|
||||
<goal>jar</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-gpg-plugin</artifactId>
|
||||
<version>1.5</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>sign-artifacts</id>
|
||||
<phase>verify</phase>
|
||||
<goals>
|
||||
<goal>sign</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
</profiles>
|
||||
|
||||
<scm>
|
||||
<connection>scm:git:git://github.com/dmulloy2/ProtocolLib.git</connection>
|
||||
<developerConnection>scm:git:git@github.com:dmulloy2/ProtocolLib.git</developerConnection>
|
||||
<url>https://github.com/dmulloy2/ProtocolLib</url>
|
||||
</scm>
|
||||
|
||||
<licenses>
|
||||
<license>
|
||||
<name>GNU GENERAL PUBLIC LICENSE - Version 2, June 1991</name>
|
||||
<url>http://www.gnu.org/licenses/gpl-2.0.txt</url>
|
||||
<distribution>repo</distribution>
|
||||
</license>
|
||||
</licenses>
|
||||
|
||||
<developers>
|
||||
<developer>
|
||||
<id>dmulloy2</id>
|
||||
<name>Dan Mulloy</name>
|
||||
<url>http://shadowvolt.com/</url>
|
||||
<roles>
|
||||
<role>developer</role>
|
||||
<role>maintainer</role>
|
||||
</roles>
|
||||
</developer>
|
||||
<developer>
|
||||
<id>aadnk</id>
|
||||
<name>Kristian S. Stangeland</name>
|
||||
<email>kr_stang@hotmail.com</email>
|
||||
<url>http://comphenix.net/</url>
|
||||
<roles>
|
||||
<role>former author</role>
|
||||
</roles>
|
||||
<timezone>1</timezone>
|
||||
</developer>
|
||||
</developers>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.spigotmc</groupId>
|
||||
<artifactId>spigot-api</artifactId>
|
||||
<version>1.8.8-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.spigotmc</groupId>
|
||||
<artifactId>spigot</artifactId>
|
||||
<version>1.8.8-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cglib</groupId>
|
||||
<artifactId>cglib-nodep</artifactId>
|
||||
<version>2.2.2</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.comphenix.executors</groupId>
|
||||
<artifactId>BukkitExecutors</artifactId>
|
||||
<version>1.1-SNAPSHOT</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>4.10</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-all</artifactId>
|
||||
<version>1.8.4</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.powermock</groupId>
|
||||
<artifactId>powermock-module-junit4</artifactId>
|
||||
<version>1.5</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.powermock</groupId>
|
||||
<artifactId>powermock-api-mockito</artifactId>
|
||||
<version>1.5</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -1,8 +0,0 @@
|
||||
package com.comphenix.protocol;
|
||||
|
||||
public class Application {
|
||||
public static void main(String[] args) {
|
||||
// For now, though we might consider making a proper application
|
||||
System.out.println("This is a Bukkit library. Place it in the plugin-folder and restart the server!");
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
package com.comphenix.protocol;
|
||||
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.plugin.PluginManager;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import com.comphenix.protocol.injector.PacketFilterManager;
|
||||
|
||||
public class ProtocolLibrary extends JavaPlugin {
|
||||
|
||||
// There should only be one protocol manager, so we'll make it static
|
||||
private static PacketFilterManager protocolManager;
|
||||
|
||||
// Error logger
|
||||
private Logger logger;
|
||||
|
||||
@Override
|
||||
public void onLoad() {
|
||||
logger = getLoggerSafely();
|
||||
protocolManager = new PacketFilterManager(getClassLoader(), logger);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
Server server = getServer();
|
||||
PluginManager manager = server.getPluginManager();
|
||||
|
||||
// Player login and logout events
|
||||
protocolManager.registerEvents(manager, this);
|
||||
protocolManager.initializePlayers(server.getOnlinePlayers());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
protocolManager.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the packet protocol manager.
|
||||
* @return Packet protocol manager.
|
||||
*/
|
||||
public static ProtocolManager getProtocolManager() {
|
||||
return protocolManager;
|
||||
}
|
||||
|
||||
// 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,90 +0,0 @@
|
||||
package com.comphenix.protocol;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.Set;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.events.PacketListener;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
|
||||
public interface ProtocolManager {
|
||||
|
||||
/**
|
||||
* Retrieves a list of every registered packet listener.
|
||||
* @return Every registered packet listener.
|
||||
*/
|
||||
public abstract ImmutableSet<PacketListener> getPacketListeners();
|
||||
|
||||
/**
|
||||
* Adds a packet listener.
|
||||
* @param listener - new packet listener.
|
||||
*/
|
||||
public abstract void addPacketListener(PacketListener listener);
|
||||
|
||||
/**
|
||||
* Removes a given packet listener.
|
||||
* @param listener - the packet listener to remove.
|
||||
*/
|
||||
public abstract void removePacketListener(PacketListener listener);
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Constructs a new encapsulated Minecraft packet with the given ID.
|
||||
* @param id - packet ID.
|
||||
* @return New encapsulated Minecraft packet.
|
||||
*/
|
||||
public PacketContainer createPacket(int id);
|
||||
|
||||
/**
|
||||
* Retieves a set of every enabled packet.
|
||||
* @return Every packet filter.
|
||||
*/
|
||||
public Set<Integer> getPacketFilters();
|
||||
|
||||
/**
|
||||
* Determines whether or not is protocol mananger has been disabled.
|
||||
* @return TRUE if it has, FALSE otherwise.
|
||||
*/
|
||||
public boolean isClosed();
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
package com.comphenix.protocol.events;
|
||||
|
||||
/**
|
||||
* Used to set a packet filter.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public enum ConnectionSide {
|
||||
/**
|
||||
* Listen for server side packets that will invoke onPacketSending().
|
||||
*/
|
||||
SERVER_SIDE,
|
||||
|
||||
/**
|
||||
* Listen for client side packets that will invoke onPacketReceiving().
|
||||
*/
|
||||
CLIENT_SIDE,
|
||||
|
||||
/**
|
||||
* Listen for both client and server side packets.
|
||||
*/
|
||||
BOTH;
|
||||
|
||||
public boolean isForClient() {
|
||||
return this == CLIENT_SIDE || this == BOTH;
|
||||
}
|
||||
|
||||
public boolean isForServer() {
|
||||
return this == SERVER_SIDE || this == BOTH;
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
package com.comphenix.protocol.events;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import com.google.common.collect.Sets;
|
||||
|
||||
/**
|
||||
* Represents a packet listener with useful constructors.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public abstract class PacketAdapter implements PacketListener {
|
||||
|
||||
protected JavaPlugin plugin;
|
||||
protected Set<Integer> packetsID;
|
||||
protected ConnectionSide connectionSide;
|
||||
|
||||
/**
|
||||
* Initialize a packet listener.
|
||||
* @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(JavaPlugin plugin, ConnectionSide connectionSide, Integer... packets) {
|
||||
this.plugin = plugin;
|
||||
this.connectionSide = connectionSide;
|
||||
this.packetsID = Sets.newHashSet(packets);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketReceiving(PacketEvent event) {
|
||||
// Default is to do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketSending(PacketEvent event) {
|
||||
// And here too
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConnectionSide getConnectionSide() {
|
||||
return connectionSide;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Integer> getPacketsID() {
|
||||
return packetsID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
// This is used by the error reporter
|
||||
return String.format("PacketAdapter[plugin=%s, side=%s, packets=%s]",
|
||||
plugin.getName(), getConnectionSide().name(),
|
||||
StringUtils.join(packetsID, ", "));
|
||||
}
|
||||
}
|
||||
@@ -1,126 +0,0 @@
|
||||
package com.comphenix.protocol.events;
|
||||
|
||||
import org.apache.commons.lang.NullArgumentException;
|
||||
import org.bukkit.craftbukkit.inventory.CraftItemStack;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import com.comphenix.protocol.injector.StructureCache;
|
||||
import com.comphenix.protocol.reflect.EquivalentConverter;
|
||||
import com.comphenix.protocol.reflect.StructureModifier;
|
||||
|
||||
import net.minecraft.server.Packet;
|
||||
|
||||
/**
|
||||
* Represents a Minecraft packet indirectly.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public class PacketContainer {
|
||||
|
||||
protected Packet handle;
|
||||
protected int id;
|
||||
|
||||
// Current structure modifier
|
||||
protected StructureModifier<Object> structureModifier;
|
||||
|
||||
/**
|
||||
* 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 NullArgumentException("handle");
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public <T> StructureModifier<T> getPrimitiveModifier(Class<T> primitiveType) {
|
||||
return structureModifier.withType(primitiveType);
|
||||
}
|
||||
|
||||
public StructureModifier<ItemStack> getItemModifier() {
|
||||
// Convert from and to the Bukkit wrapper
|
||||
return structureModifier.<ItemStack>withType(net.minecraft.server.ItemStack.class, new EquivalentConverter<ItemStack>() {
|
||||
public Object getGeneric(ItemStack specific) {
|
||||
return ((CraftItemStack) specific).getHandle();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack getSpecific(Object generic) {
|
||||
return new CraftItemStack((net.minecraft.server.ItemStack) generic);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public StructureModifier<ItemStack[]> getItemArrayModifier() {
|
||||
// Convert to and from the Bukkit wrapper
|
||||
return structureModifier.<ItemStack[]>withType(net.minecraft.server.ItemStack[].class, 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] = ((CraftItemStack) specific[i]).getHandle();
|
||||
}
|
||||
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;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the ID of this packet.
|
||||
* @return Packet ID.
|
||||
*/
|
||||
public int getID() {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
package com.comphenix.protocol.events;
|
||||
|
||||
import java.util.EventObject;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
public class PacketEvent extends EventObject {
|
||||
/**
|
||||
* Automatically generated by Eclipse.
|
||||
*/
|
||||
private static final long serialVersionUID = -5360289379097430620L;
|
||||
|
||||
private PacketContainer packet;
|
||||
private Player sender;
|
||||
private Player reciever;
|
||||
private boolean cancel;
|
||||
|
||||
/**
|
||||
* 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 sender, Player reciever) {
|
||||
super(source);
|
||||
this.packet = packet;
|
||||
this.sender = sender;
|
||||
this.reciever = reciever;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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, null, recipient);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @return The sender, or NULL if the server is sending the packet.
|
||||
*/
|
||||
public Player getSender() {
|
||||
return sender;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the player that will recieve the packet.
|
||||
* @return The reciever, or NULL if the server is recieving the packet.
|
||||
*/
|
||||
public Player getReciever() {
|
||||
return reciever;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Whether or not this packet was created by the server.
|
||||
* @return TRUE if the packet was created by the server, FALSE if it was created by a client.
|
||||
*/
|
||||
public boolean isServerPacket() {
|
||||
return getReciever() != null;
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
package com.comphenix.protocol.events;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
|
||||
public interface PacketListener {
|
||||
|
||||
/**
|
||||
* Invoked right before a packet is transmitted from the server to the client.
|
||||
* <p>
|
||||
* Note that the packet may be replaced, if needed.
|
||||
*
|
||||
* @param event - the packet that should be sent.
|
||||
*/
|
||||
public void onPacketSending(PacketEvent event);
|
||||
|
||||
/**
|
||||
* Invoked right before a recieved packet from a client is being processed.
|
||||
* @param event - the packet that has been recieved.
|
||||
*/
|
||||
public void onPacketReceiving(PacketEvent event);
|
||||
|
||||
/**
|
||||
* Retrieve whether or not we're listening for client or server packets.
|
||||
* @return The type of packets we expect.
|
||||
*/
|
||||
public ConnectionSide getConnectionSide();
|
||||
|
||||
/**
|
||||
* Set of packet ids we expect to recieve.
|
||||
* @return Packets IDs.
|
||||
*/
|
||||
public Set<Integer> getPacketsID();
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
package com.comphenix.protocol.injector;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.lang.ObjectUtils;
|
||||
|
||||
import net.minecraft.server.Packet;
|
||||
|
||||
import com.comphenix.protocol.reflect.FieldUtils;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
|
||||
/**
|
||||
* Static registries in Minecraft.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
class MinecraftRegistry {
|
||||
|
||||
// The packet class to packet ID translator
|
||||
private static Map<Class, Integer> packetToID;
|
||||
|
||||
// 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 = FuzzyReflection.fromClass(Packet.class, true).getFieldByType("java\\.util\\.Map");
|
||||
packetToID = (Map<Class, Integer>) FieldUtils.readStaticField(packetsField, true);
|
||||
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException("Unable to retrieve the packetClassToIdMap", e);
|
||||
}
|
||||
}
|
||||
|
||||
return packetToID;
|
||||
}
|
||||
|
||||
public static Map<Integer, Class> getOverwrittenPackets() {
|
||||
return overwrittenPackets;
|
||||
}
|
||||
|
||||
public static Map<Integer, Class> getPreviousPackets() {
|
||||
return previousValues;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 vanilla - 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(packetToID)) {
|
||||
return lookup.get(packetToID);
|
||||
}
|
||||
|
||||
// Will most likely not be used
|
||||
for (Map.Entry<Class, Integer> entry : getPacketToID().entrySet()) {
|
||||
if (ObjectUtils.equals(entry.getValue(), packetID)) {
|
||||
return entry.getKey();
|
||||
}
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("The packet ID " + packetID + " is not registered.");
|
||||
}
|
||||
}
|
||||
@@ -1,336 +0,0 @@
|
||||
package com.comphenix.protocol.injector;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import net.minecraft.server.Packet;
|
||||
|
||||
import org.apache.commons.lang.NullArgumentException;
|
||||
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.plugin.Plugin;
|
||||
import org.bukkit.plugin.PluginManager;
|
||||
|
||||
import com.comphenix.protocol.ProtocolManager;
|
||||
import com.comphenix.protocol.events.ConnectionSide;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.events.PacketListener;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Sets;
|
||||
|
||||
public final class PacketFilterManager implements ProtocolManager {
|
||||
|
||||
private Set<PacketListener> packetListeners = new CopyOnWriteArraySet<PacketListener>();
|
||||
|
||||
// Player injection
|
||||
private Map<DataInputStream, Player> connectionLookup = new HashMap<DataInputStream, Player>();
|
||||
private Map<Player, PlayerInjector> playerInjection = new HashMap<Player, PlayerInjector>();
|
||||
|
||||
// Packet injection
|
||||
private PacketInjector packetInjector;
|
||||
|
||||
// Enabled packet filters
|
||||
private Set<Integer> packetFilters = new HashSet<Integer>();
|
||||
|
||||
// Whether or not this class has been closed
|
||||
private boolean hasClosed;
|
||||
|
||||
// Error logger
|
||||
private Logger logger;
|
||||
|
||||
/**
|
||||
* Only create instances of this class if protocol lib is disabled.
|
||||
*/
|
||||
public PacketFilterManager(ClassLoader classLoader, Logger logger) {
|
||||
if (logger == null)
|
||||
throw new NullArgumentException("logger");
|
||||
if (classLoader == null)
|
||||
throw new NullArgumentException("classLoader");
|
||||
|
||||
try {
|
||||
// Initialize values
|
||||
this.logger = logger;
|
||||
this.packetInjector = new PacketInjector(classLoader, this, connectionLookup);
|
||||
} catch (IllegalAccessException e) {
|
||||
logger.log(Level.SEVERE, "Unable to initialize packet injector.", e);
|
||||
}
|
||||
}
|
||||
|
||||
public Logger getLogger() {
|
||||
return logger;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImmutableSet<PacketListener> getPacketListeners() {
|
||||
return ImmutableSet.copyOf(packetListeners);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addPacketListener(PacketListener listener) {
|
||||
if (listener == null)
|
||||
throw new NullArgumentException("listener");
|
||||
|
||||
packetListeners.add(listener);
|
||||
enablePacketFilters(listener.getConnectionSide(),
|
||||
listener.getPacketsID());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removePacketListener(PacketListener listener) {
|
||||
if (listener == null)
|
||||
throw new NullArgumentException("listener");
|
||||
|
||||
packetListeners.remove(listener);
|
||||
disablePacketFilters(listener.getConnectionSide(),
|
||||
listener.getPacketsID());
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the given packet event for every registered listener.
|
||||
* @param event - the packet event to invoke.
|
||||
*/
|
||||
public void invokePacketRecieving(PacketEvent event) {
|
||||
for (PacketListener listener : packetListeners) {
|
||||
try {
|
||||
if (canHandlePacket(listener, event))
|
||||
listener.onPacketReceiving(event);
|
||||
} catch (Exception e) {
|
||||
// Minecraft doesn't want your Exception.
|
||||
logger.log(Level.SEVERE, "Exception occured in onPacketReceiving() for " + listener.toString(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the given packet event for every registered listener.
|
||||
* @param event - the packet event to invoke.
|
||||
*/
|
||||
public void invokePacketSending(PacketEvent event) {
|
||||
for (PacketListener listener : packetListeners) {
|
||||
try {
|
||||
if (canHandlePacket(listener, event))
|
||||
listener.onPacketSending(event);
|
||||
} catch (Exception e) {
|
||||
logger.log(Level.SEVERE, "Exception occured in onPacketReceiving() for " + listener.toString(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean canHandlePacket(PacketListener listener, PacketEvent event) {
|
||||
// Make sure the listener is looking for this packet
|
||||
if (!listener.getPacketsID().contains(event.getPacket().getID()))
|
||||
return false;
|
||||
|
||||
// And this type of packet
|
||||
if (event.isServerPacket())
|
||||
return listener.getConnectionSide().isForServer();
|
||||
else
|
||||
return listener.getConnectionSide().isForClient();
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables packet events for a given packet ID.
|
||||
* <p>
|
||||
* Note that all packets are disabled by default.
|
||||
*
|
||||
* @param side - which side the event will arrive from.
|
||||
* @param packets - the packet id(s).
|
||||
*/
|
||||
private void enablePacketFilters(ConnectionSide side, Set<Integer> packets) {
|
||||
if (side == null)
|
||||
throw new NullArgumentException("side");
|
||||
|
||||
for (int packetID : packets) {
|
||||
if (side.isForServer())
|
||||
packetFilters.add(packetID);
|
||||
if (side.isForClient() && packetInjector != null)
|
||||
packetInjector.addPacketHandler(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, Set<Integer> packets) {
|
||||
if (side == null)
|
||||
throw new NullArgumentException("side");
|
||||
|
||||
for (int packetID : packets) {
|
||||
if (side.isForServer())
|
||||
packetFilters.remove(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 NullArgumentException("reciever");
|
||||
if (packet == null)
|
||||
throw new NullArgumentException("packet");
|
||||
|
||||
getInjector(reciever).sendServerPacket(packet.getHandle(), 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 NullArgumentException("sender");
|
||||
if (packet == null)
|
||||
throw new NullArgumentException("packet");
|
||||
|
||||
PlayerInjector injector = getInjector(sender);
|
||||
Packet mcPacket = packet.getHandle();
|
||||
|
||||
if (filters) {
|
||||
mcPacket = injector.handlePacketRecieved(mcPacket);
|
||||
}
|
||||
|
||||
injector.processPacket(mcPacket);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketContainer createPacket(int id) {
|
||||
return new PacketContainer(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Integer> getPacketFilters() {
|
||||
if (packetInjector != null)
|
||||
return Sets.union(packetFilters, packetInjector.getPacketHandlers());
|
||||
else
|
||||
return packetFilters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the packet injection for every player.
|
||||
* @param players - list of players to inject.
|
||||
*/
|
||||
public void initializePlayers(Player[] players) {
|
||||
for (Player player : players)
|
||||
injectPlayer(player);
|
||||
}
|
||||
|
||||
private void injectPlayer(Player player) {
|
||||
// Don't inject if the class has closed
|
||||
if (!hasClosed && player != null && !playerInjection.containsKey(player)) {
|
||||
try {
|
||||
PlayerInjector injector = new PlayerInjector(player, this, packetFilters);
|
||||
|
||||
injector.injectManager();
|
||||
playerInjection.put(player, injector);
|
||||
connectionLookup.put(injector.getInputStream(false), player);
|
||||
|
||||
} catch (IllegalAccessException e) {
|
||||
// Mark this injection attempt as a failure
|
||||
playerInjection.put(player, null);
|
||||
logger.log(Level.SEVERE, "Unable to access fields.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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, Plugin plugin) {
|
||||
manager.registerEvents(new Listener() {
|
||||
|
||||
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
|
||||
public void onPlayerJoin(PlayerJoinEvent event) {
|
||||
injectPlayer(event.getPlayer());
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
|
||||
public void onPlayerQuit(PlayerQuitEvent event) {
|
||||
uninjectPlayer(event.getPlayer());
|
||||
}
|
||||
}, plugin);
|
||||
}
|
||||
|
||||
private void uninjectPlayer(Player player) {
|
||||
if (!hasClosed && player != null) {
|
||||
|
||||
PlayerInjector injector = playerInjection.get(player);
|
||||
DataInputStream input = injector.getInputStream(true);
|
||||
|
||||
if (injector != null) {
|
||||
injector.cleanupAll();
|
||||
|
||||
playerInjection.remove(injector);
|
||||
connectionLookup.remove(input);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private PlayerInjector getInjector(Player player) {
|
||||
if (!playerInjection.containsKey(player)) {
|
||||
// What? Try to inject again.
|
||||
injectPlayer(player);
|
||||
}
|
||||
|
||||
PlayerInjector injector = playerInjection.get(player);
|
||||
|
||||
// Check that the injector was sucessfully added
|
||||
if (injector != null)
|
||||
return injector;
|
||||
else
|
||||
throw new IllegalArgumentException("Player has no injected handler.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isClosed() {
|
||||
return hasClosed;
|
||||
}
|
||||
|
||||
public void close() {
|
||||
// Guard
|
||||
if (hasClosed)
|
||||
return;
|
||||
|
||||
// Remove everything
|
||||
for (PlayerInjector injection : playerInjection.values()) {
|
||||
injection.cleanupAll();
|
||||
}
|
||||
|
||||
// Remove packet handlers
|
||||
if (packetInjector != null)
|
||||
packetInjector.cleanupAll();
|
||||
|
||||
playerInjection.clear();
|
||||
connectionLookup.clear();
|
||||
hasClosed = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void finalize() throws Throwable {
|
||||
close();
|
||||
}
|
||||
}
|
||||
@@ -1,177 +0,0 @@
|
||||
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 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.events.PacketContainer;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
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 PacketFilterManager manager;
|
||||
|
||||
// Allows us to determine the sender
|
||||
private Map<DataInputStream, Player> playerLookup;
|
||||
|
||||
// Class loader
|
||||
private ClassLoader classLoader;
|
||||
|
||||
public PacketInjector(ClassLoader classLoader, PacketFilterManager manager,
|
||||
Map<DataInputStream, Player> playerLookup) throws IllegalAccessException {
|
||||
|
||||
this.classLoader = classLoader;
|
||||
this.manager = manager;
|
||||
this.playerLookup = playerLookup;
|
||||
initialize();
|
||||
}
|
||||
|
||||
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).getFieldByType(FuzzyReflection.MINECRAFT_OBJECT);
|
||||
|
||||
try {
|
||||
intHashMap = intHashMapField.get(null);
|
||||
} 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);
|
||||
|
||||
// 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.setUseCache(false);
|
||||
ex.setClassLoader(classLoader);
|
||||
Class proxy = ex.createClass();
|
||||
|
||||
// Add a static reference
|
||||
Enhancer.registerStaticCallbacks(proxy, new Callback[] {
|
||||
new ReadPacketModifier(packetID, this)
|
||||
});
|
||||
|
||||
try {
|
||||
// Override values
|
||||
putMethod.invoke(intHashMap, packetID, proxy);
|
||||
previous.put(packetID, old);
|
||||
registry.put(proxy, packetID);
|
||||
overwritten.put(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);
|
||||
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 = playerLookup.get(input);
|
||||
PacketEvent event = PacketEvent.fromClient((Object) manager, packet, client);
|
||||
|
||||
manager.invokePacketRecieving(event);
|
||||
return event;
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
public void cleanupAll() {
|
||||
Map<Integer, Class> overwritten = MinecraftRegistry.getOverwrittenPackets();
|
||||
Map<Integer, Class> previous = MinecraftRegistry.getPreviousPackets();
|
||||
|
||||
// Remove every packet handler
|
||||
for (Integer id : previous.keySet()) {
|
||||
removePacketHandler(id);
|
||||
}
|
||||
|
||||
overwritten.clear();
|
||||
previous.clear();
|
||||
}
|
||||
}
|
||||
@@ -1,261 +0,0 @@
|
||||
package com.comphenix.protocol.injector;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.Set;
|
||||
|
||||
import net.minecraft.server.EntityPlayer;
|
||||
import net.minecraft.server.Packet;
|
||||
|
||||
import org.bukkit.craftbukkit.entity.CraftPlayer;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.reflect.FieldUtils;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.comphenix.protocol.reflect.VolatileField;
|
||||
|
||||
class PlayerInjector {
|
||||
|
||||
// Cache previously retrieved fields
|
||||
private static Field serverHandlerField;
|
||||
private static Field networkManagerField;
|
||||
private static Field inputField;
|
||||
private static Field netHandlerField;
|
||||
|
||||
// And methods
|
||||
private static Method queueMethod;
|
||||
private static Method processMethod;
|
||||
|
||||
private Player player;
|
||||
private boolean hasInitialized;
|
||||
|
||||
// Reference to the player's network manager
|
||||
private VolatileField networkManager;
|
||||
|
||||
// The packet manager and filters
|
||||
private PacketFilterManager manager;
|
||||
private Set<Integer> packetFilters;
|
||||
|
||||
// Previous data input
|
||||
private DataInputStream cachedInput;
|
||||
|
||||
// Current net handler
|
||||
private Object netHandler;
|
||||
|
||||
public PlayerInjector(Player player, PacketFilterManager manager, Set<Integer> packetFilters) throws IllegalAccessException {
|
||||
this.player = player;
|
||||
this.manager = manager;
|
||||
this.packetFilters = packetFilters;
|
||||
initialize();
|
||||
}
|
||||
|
||||
private void initialize() throws IllegalAccessException {
|
||||
|
||||
CraftPlayer craft = (CraftPlayer) player;
|
||||
EntityPlayer notchEntity = craft.getHandle();
|
||||
|
||||
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");
|
||||
Object serverHandler = FieldUtils.readField(serverHandlerField, notchEntity);
|
||||
|
||||
// Next, get the network manager
|
||||
if (networkManagerField == null)
|
||||
networkManagerField = FuzzyReflection.fromObject(serverHandler).getFieldByType(".*NetworkManager");
|
||||
networkManager = new VolatileField(networkManagerField, serverHandler);
|
||||
|
||||
// And the queue method
|
||||
if (queueMethod == null)
|
||||
queueMethod = FuzzyReflection.fromClass(networkManagerField.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.getOldValue(), true).
|
||||
getFieldByType("java\\.io\\.DataInputStream");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the current net handler for this player.
|
||||
* @return Current net handler.
|
||||
* @throws IllegalAccessException Unable to find or retrieve net handler.
|
||||
*/
|
||||
private Object getNetHandler() throws IllegalAccessException {
|
||||
|
||||
// What a mess
|
||||
try {
|
||||
if (netHandlerField == null)
|
||||
netHandlerField = FuzzyReflection.fromClass(networkManagerField.getType(), true).
|
||||
getFieldByType("net\\.minecraft\\.NetHandler");
|
||||
} catch (RuntimeException e1) {
|
||||
try {
|
||||
// Well, that sucks. Try just Minecraft objects then.
|
||||
netHandlerField = FuzzyReflection.fromClass(networkManagerField.getType(), true).
|
||||
getFieldByType(FuzzyReflection.MINECRAFT_OBJECT);
|
||||
|
||||
} catch (RuntimeException e2) {
|
||||
return new IllegalAccessException("Cannot locate net handler. " + e2.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// Get the handler
|
||||
if (netHandler != null)
|
||||
netHandler = FieldUtils.readField(netHandlerField, networkManager.getOldValue(), true);
|
||||
return 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 void sendServerPacket(Packet packet, boolean filtered) throws InvocationTargetException {
|
||||
Object networkDelegate = filtered ? networkManager.getValue() : networkManager.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.");
|
||||
}
|
||||
}
|
||||
|
||||
public void injectManager() {
|
||||
|
||||
if (networkManager != null) {
|
||||
final Class<?> networkInterface = networkManagerField.getType();
|
||||
final Object networkDelegate = networkManager.getOldValue();
|
||||
|
||||
// Create our proxy object
|
||||
Object networkProxy = Proxy.newProxyInstance(networkInterface.getClassLoader(),
|
||||
new Class<?>[] { networkInterface }, new InvocationHandler() {
|
||||
|
||||
@Override
|
||||
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
|
||||
// OH OH! The queue method!
|
||||
if (method.equals(queueMethod)) {
|
||||
Packet packet = (Packet) args[0];
|
||||
|
||||
if (packet != null) {
|
||||
packet = handlePacketRecieved(packet);
|
||||
|
||||
// A NULL packet indicate cancelling
|
||||
if (packet != null)
|
||||
args[0] = packet;
|
||||
else
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Delegate to our underlying class
|
||||
try {
|
||||
return method.invoke(networkDelegate, args);
|
||||
} catch (InvocationTargetException e) {
|
||||
throw e.getCause();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Inject it, if we can.
|
||||
networkManager.setValue(networkProxy);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows a packet to be recieved by the listeners.
|
||||
* @param packet - packet to recieve.
|
||||
* @return The given packet, or the packet replaced by the listeners.
|
||||
*/
|
||||
Packet handlePacketRecieved(Packet packet) {
|
||||
// Get the packet ID too
|
||||
Integer id = MinecraftRegistry.getPacketToID().get(packet.getClass());
|
||||
|
||||
// Make sure we're listening
|
||||
if (packetFilters.contains(id)) {
|
||||
// A packet has been sent guys!
|
||||
PacketContainer container = new PacketContainer(id, packet);
|
||||
PacketEvent event = PacketEvent.fromServer(manager, container, player);
|
||||
manager.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();
|
||||
}
|
||||
|
||||
return packet;
|
||||
}
|
||||
|
||||
public DataInputStream getInputStream(boolean cache) {
|
||||
// Get the associated input stream
|
||||
try {
|
||||
if (cache && cachedInput != null)
|
||||
return cachedInput;
|
||||
|
||||
// Save to cache
|
||||
cachedInput = (DataInputStream) FieldUtils.readField(inputField, networkManager.getOldValue(), true);
|
||||
return cachedInput;
|
||||
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException("Unable to read input stream.", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void cleanupAll() {
|
||||
// Clean up
|
||||
networkManager.revertValue();
|
||||
}
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
package com.comphenix.protocol.injector;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.WeakHashMap;
|
||||
|
||||
import org.apache.commons.lang.ArrayUtils;
|
||||
|
||||
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 };
|
||||
|
||||
// Common for all packets of the same type
|
||||
private PacketInjector packetInjector;
|
||||
private int packetID;
|
||||
|
||||
// Whether or not a packet has been cancelled
|
||||
private static WeakHashMap<Object, Object> override = new WeakHashMap<Object, Object>();
|
||||
|
||||
public ReadPacketModifier(int packetID, PacketInjector packetInjector) {
|
||||
this.packetID = packetID;
|
||||
this.packetInjector = packetInjector;
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
if (override.containsKey(thisObj)) {
|
||||
Object overridenObject = override.get(thisObj);
|
||||
|
||||
// Cancel EVERYTHING, including "processPacket"
|
||||
if (overridenObject == null)
|
||||
return null;
|
||||
|
||||
returnValue = proxy.invokeSuper(overridenObject, args);
|
||||
} else {
|
||||
returnValue = proxy.invokeSuper(thisObj, args);
|
||||
}
|
||||
|
||||
// Is this a readPacketData method?
|
||||
if (returnValue == null &&
|
||||
ArrayUtils.isEquals(method.getParameterTypes(), parameters)) {
|
||||
|
||||
// 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);
|
||||
Packet result = event.getPacket().getHandle();
|
||||
|
||||
// Handle override
|
||||
if (event != null) {
|
||||
if (event.isCancelled()) {
|
||||
override.put(thisObj, null);
|
||||
} else if (!objectEquals(thisObj, result)) {
|
||||
override.put(thisObj, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
private boolean objectEquals(Object a, Object b) {
|
||||
return System.identityHashCode(a) != System.identityHashCode(b);
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
package com.comphenix.protocol.injector;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import net.minecraft.server.Packet;
|
||||
|
||||
import com.comphenix.protocol.reflect.StructureModifier;
|
||||
|
||||
public class StructureCache {
|
||||
// Structure modifiers
|
||||
private static Map<Integer, StructureModifier<Object>> structureModifiers = new HashMap<Integer, StructureModifier<Object>>();
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
|
||||
StructureModifier<Object> result = structureModifiers.get(id);
|
||||
|
||||
// Use the vanilla class definition
|
||||
if (result == null) {
|
||||
result = new StructureModifier<Object>(MinecraftRegistry.getPacketClassFromID(id, true));
|
||||
structureModifiers.put(id, result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
package com.comphenix.protocol.reflect;
|
||||
|
||||
/**
|
||||
* Interface that converts generic objects into types and back.
|
||||
*
|
||||
* @author Kristian
|
||||
* @param <TType> The specific type.
|
||||
*/
|
||||
public interface EquivalentConverter<TType> {
|
||||
public TType getSpecific(Object generic);
|
||||
public Object getGeneric(TType specific);
|
||||
}
|
||||
@@ -1,218 +0,0 @@
|
||||
package com.comphenix.protocol.reflect;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.apache.commons.lang.ArrayUtils;
|
||||
|
||||
/**
|
||||
* Retrieves fields and methods by signature, not just name.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public class FuzzyReflection {
|
||||
|
||||
/**
|
||||
* Matches a Minecraft object.
|
||||
*/
|
||||
public static 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.
|
||||
*/
|
||||
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 RuntimeException("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.
|
||||
*/
|
||||
public Method getMethodByParameters(String name, Class<?>... args) {
|
||||
|
||||
// Find the correct method to call
|
||||
for (Method method : getMethods()) {
|
||||
if (ArrayUtils.isEquals(method.getParameterTypes(), args)) {
|
||||
return method;
|
||||
}
|
||||
}
|
||||
|
||||
// That sucks
|
||||
throw new RuntimeException("Unable to find " + name + " in " + source.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a field by name.
|
||||
* @param nameRegex - regular expression that will match a field name.
|
||||
* @return The first field to match the given expression.
|
||||
*/
|
||||
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 RuntimeException("Unable to find a field with the pattern " +
|
||||
nameRegex + " 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.
|
||||
* @return The first field with a type that matches the given regular expression.
|
||||
*/
|
||||
public Field getFieldByType(String typeRegex) {
|
||||
|
||||
Pattern match = Pattern.compile(typeRegex);
|
||||
|
||||
// Like above, only here we test the field type
|
||||
for (Field field : getFields()) {
|
||||
if (match.matcher(field.getType().getName()).matches()) {
|
||||
return field;
|
||||
}
|
||||
}
|
||||
|
||||
// Looks like we're outdated. Too bad.
|
||||
throw new RuntimeException("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,218 +0,0 @@
|
||||
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 com.google.common.base.Function;
|
||||
|
||||
import net.minecraft.server.Packet;
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
public class StructureModifier<TField> {
|
||||
|
||||
// Object and its type
|
||||
private Class targetType;
|
||||
private Object target;
|
||||
|
||||
// Converter. May be NULL.
|
||||
private EquivalentConverter<TField> converter;
|
||||
|
||||
// The fields to read in order
|
||||
private Class fieldType;
|
||||
private List<Field> data = new ArrayList<Field>();
|
||||
|
||||
// Cache of previous types
|
||||
private Map<Class, StructureModifier> subtypeCache;
|
||||
|
||||
public StructureModifier(Class targetType) {
|
||||
this(targetType, Object.class, getFields(targetType), null, new HashMap<Class, StructureModifier>());
|
||||
}
|
||||
|
||||
private StructureModifier(Class targetType, Class fieldType, List<Field> data,
|
||||
EquivalentConverter<TField> converter, Map<Class, StructureModifier> subTypeCache) {
|
||||
this.targetType = targetType;
|
||||
this.fieldType = fieldType;
|
||||
this.data = data;
|
||||
this.converter = converter;
|
||||
this.subtypeCache = subTypeCache;
|
||||
}
|
||||
|
||||
private StructureModifier(StructureModifier<TField> other, Object target) {
|
||||
this(other.targetType, other.fieldType, other.data, other.converter, other.subtypeCache);
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the value of a field given its index.
|
||||
* @param fieldIndex - index of the field.
|
||||
* @return Value of the field.
|
||||
* @throws IllegalAccessException If we're unable to read the field due to a security limitation.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public TField read(int fieldIndex) throws IllegalAccessException {
|
||||
if (fieldIndex < 0 || fieldIndex >= data.size())
|
||||
throw new IllegalArgumentException("Field index must be within 0 - count");
|
||||
if (target == null)
|
||||
throw new IllegalStateException("Cannot read from a NULL target.");
|
||||
|
||||
Object result = FieldUtils.readField(data.get(fieldIndex), target, true);
|
||||
|
||||
// Use the converter, if we have it
|
||||
if (converter != null)
|
||||
return converter.getSpecific(result);
|
||||
else
|
||||
return (TField) result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 IllegalAccessException If we're unable to write to the field due to a security limitation.
|
||||
*/
|
||||
public StructureModifier<TField> write(int fieldIndex, TField value) throws IllegalAccessException {
|
||||
if (fieldIndex < 0 || fieldIndex >= data.size())
|
||||
throw new IllegalArgumentException("Field index must be within 0 - count");
|
||||
if (target == null)
|
||||
throw new IllegalStateException("Cannot write to a NULL target.");
|
||||
|
||||
// Use the converter, if it exists
|
||||
Object obj = converter != null ? converter.getGeneric(value) : value;
|
||||
FieldUtils.writeField(data.get(fieldIndex), target, obj, true);
|
||||
|
||||
// Make this method chainable
|
||||
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 IllegalAccessException
|
||||
*/
|
||||
public StructureModifier<TField> modify(int fieldIndex, Function<TField, TField> select) throws IllegalAccessException {
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
if (fieldType.equals(this.fieldType)) {
|
||||
|
||||
// We're dealing with the exact field type.
|
||||
return withConverter(converter);
|
||||
|
||||
} else if (result == null) {
|
||||
List<Field> filtered = new ArrayList<Field>();
|
||||
|
||||
for (Field field : data) {
|
||||
if (fieldType.isAssignableFrom(field.getType())) {
|
||||
filtered.add(field);
|
||||
}
|
||||
}
|
||||
|
||||
// Cache structure modifiers
|
||||
result = new StructureModifier<T>(targetType, fieldType, filtered,
|
||||
converter, new HashMap<Class, StructureModifier>());
|
||||
subtypeCache.put(fieldType, result);
|
||||
}
|
||||
|
||||
// Add the target too
|
||||
return result.withTarget(target);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
return new StructureModifier<TField>(this, target);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 = new StructureModifier(this, target);
|
||||
|
||||
copy.converter = converter;
|
||||
return copy;
|
||||
}
|
||||
|
||||
// Used to filter out irrelevant fields
|
||||
private static List<Field> getFields(Class type) {
|
||||
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) && !field.getDeclaringClass().equals(Packet.class)) {
|
||||
result.add(field);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -1,158 +0,0 @@
|
||||
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 (currentSet) {
|
||||
setValue(previous);
|
||||
currentSet = false;
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
|
||||
/**
|
||||
* Ignore this class.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public class Application {
|
||||
private static Thread mainThread;
|
||||
private static boolean primaryMethod = true;
|
||||
|
||||
public static void main(String[] args) {
|
||||
// For now, though we might consider making a proper application
|
||||
System.out.println("This is a Bukkit library. Place it in the plugin-folder and restart the server!");
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if we are running on the main thread.
|
||||
* @return TRUE if we are, FALSE otherwise.
|
||||
*/
|
||||
public static boolean isPrimaryThread() {
|
||||
if (primaryMethod) {
|
||||
try {
|
||||
return Bukkit.isPrimaryThread();
|
||||
} catch (LinkageError e) {
|
||||
primaryMethod = false;
|
||||
}
|
||||
}
|
||||
// Fallback method
|
||||
return Thread.currentThread().equals(mainThread);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the calling thread as the primary thread.
|
||||
*/
|
||||
static void registerPrimaryThread() {
|
||||
mainThread = Thread.currentThread();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import com.comphenix.protocol.async.AsyncListenerHandler;
|
||||
import com.comphenix.protocol.async.AsyncMarker;
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.events.PacketListener;
|
||||
|
||||
/**
|
||||
* Represents a asynchronous packet handler.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public interface AsynchronousManager {
|
||||
/**
|
||||
* Registers an asynchronous packet handler.
|
||||
* <p>
|
||||
* Use {@link AsyncMarker#incrementProcessingDelay()} to delay a packet until its ready to be transmitted.
|
||||
* <p>
|
||||
* To start listening asynchronously, pass the getListenerLoop() runnable to a different thread.
|
||||
* @param listener - the packet listener that will receive these asynchronous events.
|
||||
* @return An asynchronous handler.
|
||||
*/
|
||||
public abstract AsyncListenerHandler registerAsyncHandler(PacketListener listener);
|
||||
|
||||
/**
|
||||
* Unregisters and closes the given asynchronous handler.
|
||||
* @param handler - asynchronous handler.
|
||||
*/
|
||||
public abstract void unregisterAsyncHandler(AsyncListenerHandler handler);
|
||||
|
||||
/**
|
||||
* Unregisters and closes the first asynchronous handler associated with the given listener.
|
||||
* @param listener - asynchronous listener
|
||||
*/
|
||||
public abstract void unregisterAsyncHandler(PacketListener listener);
|
||||
|
||||
/**
|
||||
* Unregisters every asynchronous handler associated with this plugin.
|
||||
* @param plugin - the original plugin.
|
||||
*/
|
||||
public void unregisterAsyncHandlers(Plugin plugin);
|
||||
|
||||
/**
|
||||
* Retrieves a immutable set containing the ID of the sent server packets that will be
|
||||
* observed by the asynchronous listeners.
|
||||
* <p>
|
||||
* Deprecated: Use {@link #getSendingTypes()} instead.
|
||||
* @return Every filtered server packet.
|
||||
*/
|
||||
@Deprecated
|
||||
public abstract Set<Integer> getSendingFilters();
|
||||
|
||||
/**
|
||||
* Retrieves a immutable set containing the types of the sent server packets that will be
|
||||
* observed by the asynchronous listeners.
|
||||
* @return Every filtered server packet.
|
||||
*/
|
||||
public abstract Set<PacketType> getSendingTypes();
|
||||
|
||||
/**
|
||||
* Retrieves a immutable set containing the ID of the recieved client packets that will be
|
||||
* <p>
|
||||
* Deprecated: Use {@link #getReceivingTypes()} instead.
|
||||
* observed by the asynchronous listeners.
|
||||
* @return Every filtered client packet.
|
||||
*/
|
||||
@Deprecated
|
||||
public abstract Set<Integer> getReceivingFilters();
|
||||
|
||||
/**
|
||||
* Retrieves a immutable set containing the types of the received client packets that will be
|
||||
* observed by the asynchronous listeners.
|
||||
* @return Every filtered client packet.
|
||||
*/
|
||||
public abstract Set<PacketType> getReceivingTypes();
|
||||
|
||||
/**
|
||||
* Determine if a given synchronous packet has asynchronous listeners.
|
||||
* @param packet - packet to test.
|
||||
* @return TRUE if it does, FALSE otherwise.
|
||||
*/
|
||||
public abstract boolean hasAsynchronousListeners(PacketEvent packet);
|
||||
|
||||
/**
|
||||
* Retrieve the default packet stream.
|
||||
* @return Default packet stream.
|
||||
*/
|
||||
public abstract PacketStream getPacketStream();
|
||||
|
||||
/**
|
||||
* Retrieve the default error reporter.
|
||||
* @return Default reporter.
|
||||
*/
|
||||
public abstract ErrorReporter getErrorReporter();
|
||||
|
||||
/**
|
||||
* Remove listeners, close threads and transmit every delayed packet.
|
||||
*/
|
||||
public abstract void cleanupAll();
|
||||
|
||||
/**
|
||||
* Signal that a packet is ready to be transmitted.
|
||||
* <p>
|
||||
* This should only be called if {@link com.comphenix.protocol.async.AsyncMarker#incrementProcessingDelay() AsyncMarker.incrementProcessingDelay()}
|
||||
* has been called previously.
|
||||
* @param packet - packet to signal.
|
||||
*/
|
||||
public abstract void signalPacketTransmission(PacketEvent packet);
|
||||
|
||||
/**
|
||||
* Register a synchronous listener that handles packets when they time out.
|
||||
* @param listener - synchronous listener that will handle timed out packets.
|
||||
*/
|
||||
public abstract void registerTimeoutHandler(PacketListener listener);
|
||||
|
||||
/**
|
||||
* Unregisters a given timeout listener.
|
||||
* @param listener - the timeout listener to unregister.
|
||||
*/
|
||||
public abstract void unregisterTimeoutHandler(PacketListener listener);
|
||||
|
||||
/**
|
||||
* Get a immutable set of every registered timeout handler.
|
||||
* @return Set of every registered timeout handler.
|
||||
*/
|
||||
public abstract Set<PacketListener> getTimeoutHandlers();
|
||||
|
||||
/**
|
||||
* Get an immutable set of every registered asynchronous packet listener.
|
||||
* @return Set of every asynchronous packet listener.
|
||||
*/
|
||||
public abstract Set<PacketListener> getAsyncHandlers();
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.comphenix.protocol.async.AsyncListenerHandler;
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.error.Report;
|
||||
import com.comphenix.protocol.error.ReportType;
|
||||
import com.comphenix.protocol.events.ListeningWhitelist;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.injector.BukkitUnwrapper;
|
||||
import com.comphenix.protocol.injector.server.AbstractInputStreamLookup;
|
||||
import com.comphenix.protocol.injector.server.TemporaryPlayerFactory;
|
||||
import com.comphenix.protocol.injector.spigot.SpigotPacketInjector;
|
||||
import com.comphenix.protocol.reflect.FieldUtils;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.comphenix.protocol.reflect.MethodUtils;
|
||||
import com.comphenix.protocol.reflect.ObjectWriter;
|
||||
import com.comphenix.protocol.reflect.compiler.BackgroundCompiler;
|
||||
import com.comphenix.protocol.reflect.compiler.StructureCompiler;
|
||||
import com.comphenix.protocol.reflect.instances.CollectionGenerator;
|
||||
import com.comphenix.protocol.reflect.instances.DefaultInstances;
|
||||
import com.comphenix.protocol.reflect.instances.PrimitiveGenerator;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
import com.comphenix.protocol.wrappers.ChunkPosition;
|
||||
import com.comphenix.protocol.wrappers.WrappedDataWatcher;
|
||||
import com.comphenix.protocol.wrappers.WrappedWatchableObject;
|
||||
import com.comphenix.protocol.wrappers.nbt.io.NbtBinarySerializer;
|
||||
|
||||
/**
|
||||
* Used to fix ClassLoader leaks that may lead to filling up the permanent generation.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
class CleanupStaticMembers {
|
||||
// Reports
|
||||
public final static ReportType REPORT_CANNOT_RESET_FIELD = new ReportType("Unable to reset field %s: %s");
|
||||
public final static ReportType REPORT_CANNOT_UNLOAD_CLASS = new ReportType("Unable to unload class %s.");
|
||||
|
||||
private ClassLoader loader;
|
||||
private ErrorReporter reporter;
|
||||
|
||||
public CleanupStaticMembers(ClassLoader loader, ErrorReporter reporter) {
|
||||
this.loader = loader;
|
||||
this.reporter = reporter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that the previous ClassLoader is not leaking.
|
||||
*/
|
||||
public void resetAll() {
|
||||
// This list must always be updated
|
||||
@SuppressWarnings("deprecation")
|
||||
Class<?>[] publicClasses = {
|
||||
AsyncListenerHandler.class, ListeningWhitelist.class, PacketContainer.class,
|
||||
BukkitUnwrapper.class, DefaultInstances.class, CollectionGenerator.class,
|
||||
PrimitiveGenerator.class, FuzzyReflection.class, MethodUtils.class,
|
||||
BackgroundCompiler.class, StructureCompiler.class,
|
||||
ObjectWriter.class, Packets.Server.class, Packets.Client.class,
|
||||
ChunkPosition.class, WrappedDataWatcher.class, WrappedWatchableObject.class,
|
||||
AbstractInputStreamLookup.class, TemporaryPlayerFactory.class, SpigotPacketInjector.class,
|
||||
MinecraftReflection.class, NbtBinarySerializer.class
|
||||
};
|
||||
|
||||
String[] internalClasses = {
|
||||
"com.comphenix.protocol.events.SerializedOfflinePlayer",
|
||||
"com.comphenix.protocol.injector.player.InjectedServerConnection",
|
||||
"com.comphenix.protocol.injector.player.NetworkFieldInjector",
|
||||
"com.comphenix.protocol.injector.player.NetworkObjectInjector",
|
||||
"com.comphenix.protocol.injector.player.NetworkServerInjector",
|
||||
"com.comphenix.protocol.injector.player.PlayerInjector",
|
||||
"com.comphenix.protocol.injector.EntityUtilities",
|
||||
"com.comphenix.protocol.injector.packet.PacketRegistry",
|
||||
"com.comphenix.protocol.injector.packet.PacketInjector",
|
||||
"com.comphenix.protocol.injector.packet.ReadPacketModifier",
|
||||
"com.comphenix.protocol.injector.StructureCache",
|
||||
"com.comphenix.protocol.reflect.compiler.BoxingHelper",
|
||||
"com.comphenix.protocol.reflect.compiler.MethodDescriptor",
|
||||
"com.comphenix.protocol.wrappers.nbt.WrappedElement",
|
||||
};
|
||||
|
||||
resetClasses(publicClasses);
|
||||
resetClasses(getClasses(loader, internalClasses));
|
||||
}
|
||||
|
||||
private void resetClasses(Class<?>[] classes) {
|
||||
// Reset each class one by one
|
||||
for (Class<?> clazz : classes) {
|
||||
resetClass(clazz);
|
||||
}
|
||||
}
|
||||
|
||||
private void resetClass(Class<?> clazz) {
|
||||
for (Field field : clazz.getFields()) {
|
||||
Class<?> type = field.getType();
|
||||
|
||||
// Only check static non-primitive fields. We also skip strings.
|
||||
if (Modifier.isStatic(field.getModifiers()) &&
|
||||
!type.isPrimitive() && !type.equals(String.class) &&
|
||||
!type.equals(ReportType.class)) {
|
||||
|
||||
try {
|
||||
setFinalStatic(field, null);
|
||||
} catch (IllegalAccessException e) {
|
||||
// Just inform the player
|
||||
reporter.reportWarning(this,
|
||||
Report.newBuilder(REPORT_CANNOT_RESET_FIELD).error(e).messageParam(field.getName(), e.getMessage())
|
||||
);
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// HACK! HAACK!
|
||||
private static void setFinalStatic(Field field, Object newValue) throws IllegalAccessException {
|
||||
int modifier = field.getModifiers();
|
||||
boolean isFinal = Modifier.isFinal(modifier);
|
||||
|
||||
Field modifiersField = isFinal ? FieldUtils.getField(Field.class, "modifiers", true) : null;
|
||||
|
||||
// We have to remove the final field first
|
||||
if (isFinal) {
|
||||
FieldUtils.writeField(modifiersField, field, modifier & ~Modifier.FINAL, true);
|
||||
}
|
||||
|
||||
// Now we can safely modify the field
|
||||
FieldUtils.writeStaticField(field, newValue, true);
|
||||
|
||||
// Revert modifier
|
||||
if (isFinal) {
|
||||
FieldUtils.writeField(modifiersField, field, modifier, true);
|
||||
}
|
||||
}
|
||||
|
||||
private Class<?>[] getClasses(ClassLoader loader, String[] names) {
|
||||
List<Class<?>> output = new ArrayList<Class<?>>();
|
||||
|
||||
for (String name : names) {
|
||||
try {
|
||||
output.add(loader.loadClass(name));
|
||||
} catch (ClassNotFoundException e) {
|
||||
// Warn the user
|
||||
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_UNLOAD_CLASS).error(e).messageParam(name));
|
||||
}
|
||||
}
|
||||
|
||||
return output.toArray(new Class<?>[0]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Arrays;
|
||||
import java.util.Deque;
|
||||
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.error.Report;
|
||||
import com.comphenix.protocol.error.ReportType;
|
||||
|
||||
/**
|
||||
* Base class for all our commands.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
abstract class CommandBase implements CommandExecutor {
|
||||
public static final ReportType REPORT_COMMAND_ERROR = new ReportType("Cannot execute command %s.");
|
||||
public static final ReportType REPORT_UNEXPECTED_COMMAND = new ReportType("Incorrect command assigned to %s.");
|
||||
|
||||
public static final String PERMISSION_ADMIN = "protocol.admin";
|
||||
|
||||
private String permission;
|
||||
private String name;
|
||||
private int minimumArgumentCount;
|
||||
|
||||
protected ErrorReporter reporter;
|
||||
|
||||
public CommandBase(ErrorReporter reporter, String permission, String name) {
|
||||
this(reporter, permission, name, 0);
|
||||
}
|
||||
|
||||
public CommandBase(ErrorReporter reporter, String permission, String name, int minimumArgumentCount) {
|
||||
this.reporter = reporter;
|
||||
this.name = name;
|
||||
this.permission = permission;
|
||||
this.minimumArgumentCount = minimumArgumentCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
try {
|
||||
// Make sure we're dealing with the correct command
|
||||
if (!command.getName().equalsIgnoreCase(name)) {
|
||||
reporter.reportWarning(this, Report.newBuilder(REPORT_UNEXPECTED_COMMAND).messageParam(this));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (permission != null && !sender.hasPermission(permission)) {
|
||||
sender.sendMessage(ChatColor.RED + "You haven't got permission to run this command.");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check argument length
|
||||
if (args != null && args.length >= minimumArgumentCount) {
|
||||
return handleCommand(sender, args);
|
||||
} else {
|
||||
sender.sendMessage(ChatColor.RED + "Insufficient arguments. You need at least " + minimumArgumentCount);
|
||||
return false;
|
||||
}
|
||||
} catch (Throwable ex) {
|
||||
reporter.reportDetailed(this,
|
||||
Report.newBuilder(REPORT_COMMAND_ERROR).error(ex).messageParam(name).callerParam(sender, label, args)
|
||||
);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a boolean value at the head of the queue.
|
||||
* @param arguments - the queue of arguments.
|
||||
* @param parameterName - the parameter name we will match.
|
||||
* @return The parsed boolean, or NULL if not valid.
|
||||
*/
|
||||
protected Boolean parseBoolean(Deque<String> arguments, String parameterName) {
|
||||
Boolean result = null;
|
||||
|
||||
if (!arguments.isEmpty()) {
|
||||
String arg = arguments.peek();
|
||||
|
||||
if (arg.equalsIgnoreCase("true") || arg.equalsIgnoreCase("on"))
|
||||
result = true;
|
||||
else if (arg.equalsIgnoreCase(parameterName))
|
||||
result = true;
|
||||
else if (arg.equalsIgnoreCase("false") || arg.equalsIgnoreCase("off"))
|
||||
result = false;
|
||||
}
|
||||
|
||||
if (result != null)
|
||||
arguments.poll();
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a queue from a sublist of a given array.
|
||||
* @param args - the source array.
|
||||
* @param start - the starting index.
|
||||
* @return A queue that contains every element in the array, starting at the given index.
|
||||
*/
|
||||
protected Deque<String> toQueue(String[] args, int start) {
|
||||
return new ArrayDeque<String>(Arrays.asList(args).subList(start, args.length));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the permission necessary to execute this command.
|
||||
* @return The permission, or NULL if not needed.
|
||||
*/
|
||||
public String getPermission() {
|
||||
return permission;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the primary name of this command.
|
||||
* @return Primary name.
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the error reporter.
|
||||
* @return Error reporter.
|
||||
*/
|
||||
protected ErrorReporter getReporter() {
|
||||
return reporter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Main implementation of this command.
|
||||
* @param sender - command sender.
|
||||
* @param args - input arguments.
|
||||
* @return TRUE if the command was successfully handled, FALSE otherwise.
|
||||
*/
|
||||
protected abstract boolean handleCommand(CommandSender sender, String[] args);
|
||||
}
|
||||
@@ -0,0 +1,481 @@
|
||||
package com.comphenix.protocol;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Deque;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.script.Invocable;
|
||||
import javax.script.ScriptEngine;
|
||||
import javax.script.ScriptEngineManager;
|
||||
import javax.script.ScriptException;
|
||||
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.conversations.Conversable;
|
||||
import org.bukkit.conversations.Conversation;
|
||||
import org.bukkit.conversations.ConversationAbandonedEvent;
|
||||
import org.bukkit.conversations.ConversationAbandonedListener;
|
||||
import org.bukkit.conversations.ConversationContext;
|
||||
import org.bukkit.conversations.ConversationFactory;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import com.comphenix.protocol.MultipleLinesPrompt.MultipleConversationCanceller;
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.error.Report;
|
||||
import com.comphenix.protocol.error.ReportType;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.google.common.collect.Sets;
|
||||
|
||||
/**
|
||||
* A command to apply JavaScript filtering to the packet command.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public class CommandFilter extends CommandBase {
|
||||
public static final ReportType REPORT_FALLBACK_ENGINE = new ReportType("Falling back to the Rhino engine.");
|
||||
public static final ReportType REPORT_CANNOT_LOAD_FALLBACK_ENGINE = new ReportType("Could not load Rhino either. Please upgrade your JVM or OS.");
|
||||
public static final ReportType REPORT_PACKAGES_UNSUPPORTED_IN_ENGINE = new ReportType("Unable to initialize packages for JavaScript engine.");
|
||||
public static final ReportType REPORT_FILTER_REMOVED_FOR_ERROR = new ReportType("Removing filter %s for causing %s.");
|
||||
public static final ReportType REPORT_CANNOT_HANDLE_CONVERSATION = new ReportType("Cannot handle conversation.");
|
||||
|
||||
public interface FilterFailedHandler{
|
||||
/**
|
||||
* Invoked when a given filter has failed.
|
||||
* @param event - the packet event.
|
||||
* @param filter - the filter that failed.
|
||||
* @param ex - the failure.
|
||||
* @return TRUE to keep processing this filter, FALSE to remove it.
|
||||
*/
|
||||
public boolean handle(PacketEvent event, Filter filter, Exception ex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Possible sub commands.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
private enum SubCommand {
|
||||
ADD, REMOVE;
|
||||
}
|
||||
|
||||
/**
|
||||
* A filter that will be used to process a packet event.
|
||||
* @author Kristian
|
||||
*/
|
||||
public static class Filter {
|
||||
private final String name;
|
||||
private final String predicate;
|
||||
|
||||
private final Set<PacketType> packets;
|
||||
|
||||
/**
|
||||
* Construct a new immutable filter.
|
||||
* @param name - the unique name of the filter.
|
||||
* @param predicate - the JavaScript predicate that will be used to filter packet events.
|
||||
* @param packets - a list of packet types this filter applies to.
|
||||
*/
|
||||
public Filter(String name, String predicate, Set<PacketType> packets) {
|
||||
this.name = name;
|
||||
this.predicate = predicate;
|
||||
this.packets = Sets.newHashSet(packets);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the unique name of the filter.
|
||||
* @return Unique name of the filter.
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the JavaScript predicate that will be used to filter packet events.
|
||||
* @return Predicate itself.
|
||||
*/
|
||||
public String getPredicate() {
|
||||
return predicate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a copy of the set of packets this filter applies to.
|
||||
* @return Set of packets this filter applies to.
|
||||
*/
|
||||
public Set<PacketType> getRanges() {
|
||||
return Sets.newHashSet(packets);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether or not a packet event needs to be passed to this filter.
|
||||
* @param event - the event to test.
|
||||
* @return TRUE if it does, FALSE otherwise.
|
||||
*/
|
||||
private boolean isApplicable(PacketEvent event) {
|
||||
return packets.contains(event.getPacketType());
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate the current filter using the provided ScriptEngine as context.
|
||||
* <p>
|
||||
* This context may be modified with additional code.
|
||||
* @param context - the current script context.
|
||||
* @param event - the packet event to evaluate.
|
||||
* @return TRUE to pass this packet event on to the debug listeners, FALSE otherwise.
|
||||
* @throws ScriptException If the compilation failed or the filter is not valid.
|
||||
*/
|
||||
public boolean evaluate(ScriptEngine context, PacketEvent event) throws ScriptException {
|
||||
if (!isApplicable(event))
|
||||
return true;
|
||||
// Ensure that the predicate has been compiled
|
||||
compile(context);
|
||||
|
||||
try {
|
||||
Object result = ((Invocable) context).invokeFunction(name, event, event.getPacket().getHandle());
|
||||
|
||||
if (result instanceof Boolean)
|
||||
return (Boolean) result;
|
||||
else
|
||||
throw new ScriptException("Filter result wasn't a boolean: " + result);
|
||||
|
||||
} catch (NoSuchMethodException e) {
|
||||
// Must be a fault with the script engine itself
|
||||
throw new IllegalStateException("Unable to compile " + name + " into current script engine.", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Force the compilation of a specific filter.
|
||||
* @param context - the current script context.
|
||||
* @throws ScriptException If the compilation failed.
|
||||
*/
|
||||
public void compile(ScriptEngine context) throws ScriptException {
|
||||
if (context.get(name) == null) {
|
||||
context.eval("var " + name + " = function(event, packet) {\n" + predicate);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up all associated code from this filter in the provided script engine.
|
||||
* @param context - the current script context.
|
||||
*/
|
||||
public void close(ScriptEngine context) {
|
||||
context.put(name, null);
|
||||
}
|
||||
}
|
||||
|
||||
private class CompilationSuccessCanceller implements MultipleConversationCanceller {
|
||||
@Override
|
||||
public boolean cancelBasedOnInput(ConversationContext context, String in) {
|
||||
throw new UnsupportedOperationException("Cannot cancel on the last line alone.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setConversation(Conversation conversation) {
|
||||
// Ignore
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean cancelBasedOnInput(ConversationContext context, String currentLine, StringBuilder lines, int lineCount) {
|
||||
try {
|
||||
engine.eval("function(event, packet) {\n" + lines.toString());
|
||||
|
||||
// It compiles - accept the filter!
|
||||
return true;
|
||||
} catch (ScriptException e) {
|
||||
// We also have the function() line
|
||||
int realLineCount = lineCount + 1;
|
||||
|
||||
// Only possible to recover from an error on the last line.
|
||||
return e.getLineNumber() < realLineCount;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompilationSuccessCanceller clone() {
|
||||
return new CompilationSuccessCanceller();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Name of this command.
|
||||
*/
|
||||
public static final String NAME = "filter";
|
||||
|
||||
// Default error handler
|
||||
private FilterFailedHandler defaultFailedHandler;
|
||||
|
||||
// Currently registered filters
|
||||
private List<Filter> filters = new ArrayList<Filter>();
|
||||
|
||||
// Owner plugin
|
||||
private final Plugin plugin;
|
||||
|
||||
// Whether or not the command is enabled
|
||||
private ProtocolConfig config;
|
||||
|
||||
// Script engine
|
||||
private ScriptEngine engine;
|
||||
private boolean uninitialized;
|
||||
|
||||
public CommandFilter(ErrorReporter reporter, Plugin plugin, ProtocolConfig config) {
|
||||
super(reporter, CommandBase.PERMISSION_ADMIN, NAME, 2);
|
||||
this.plugin = plugin;
|
||||
this.config = config;
|
||||
|
||||
// Tell the filter system to initialize the script first chance it gets
|
||||
this.uninitialized = true;
|
||||
}
|
||||
|
||||
private void initalizeScript() {
|
||||
try {
|
||||
// First attempt
|
||||
initializeEngine();
|
||||
|
||||
// Oh for ..
|
||||
if (!isInitialized()) {
|
||||
throw new ScriptException("A JavaScript engine could not be found.");
|
||||
} else {
|
||||
plugin.getLogger().info("Loaded command filter engine.");
|
||||
}
|
||||
} catch (ScriptException e1) {
|
||||
// It's not a huge deal
|
||||
printPackageWarning(e1);
|
||||
|
||||
if (!config.getScriptEngineName().equals("rhino")) {
|
||||
reporter.reportWarning(this, Report.newBuilder(REPORT_FALLBACK_ENGINE));
|
||||
config.setScriptEngineName("rhino");
|
||||
config.saveAll();
|
||||
|
||||
try {
|
||||
initializeEngine();
|
||||
|
||||
if (!isInitialized()) {
|
||||
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_LOAD_FALLBACK_ENGINE));
|
||||
}
|
||||
} catch (ScriptException e2) {
|
||||
// And again ..
|
||||
printPackageWarning(e2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void printPackageWarning(ScriptException e) {
|
||||
reporter.reportWarning(this, Report.newBuilder(REPORT_PACKAGES_UNSUPPORTED_IN_ENGINE).error(e));
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the current configured engine.
|
||||
* @throws ScriptException If we are unable to import packages.
|
||||
*/
|
||||
private void initializeEngine() throws ScriptException {
|
||||
ScriptEngineManager manager = new ScriptEngineManager();
|
||||
engine = manager.getEngineByName(config.getScriptEngineName());
|
||||
|
||||
// Import useful packages
|
||||
if (engine != null) {
|
||||
engine.eval("importPackage(org.bukkit);");
|
||||
engine.eval("importPackage(com.comphenix.protocol.reflect);");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the filter engine has been successfully initialized.
|
||||
* @return TRUE if it has, FALSE otherwise.
|
||||
*/
|
||||
public boolean isInitialized() {
|
||||
return engine != null;
|
||||
}
|
||||
|
||||
private FilterFailedHandler getDefaultErrorHandler() {
|
||||
// No need to create a new object every time
|
||||
if (defaultFailedHandler == null) {
|
||||
defaultFailedHandler = new FilterFailedHandler() {
|
||||
@Override
|
||||
public boolean handle(PacketEvent event, Filter filter, Exception ex) {
|
||||
reporter.reportMinimal(plugin, "filterEvent(PacketEvent)", ex, event);
|
||||
reporter.reportWarning(this,
|
||||
Report.newBuilder(REPORT_FILTER_REMOVED_FOR_ERROR).messageParam(filter.getName(), ex.getClass().getSimpleName())
|
||||
);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
}
|
||||
return defaultFailedHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether or not to pass the given packet event to the packet listeners.
|
||||
* <p>
|
||||
* Uses a default filter failure handler that simply prints the error message and removes the filter.
|
||||
* @param event - the event.
|
||||
* @return TRUE if we should, FALSE otherwise.
|
||||
*/
|
||||
public boolean filterEvent(PacketEvent event) {
|
||||
return filterEvent(event, getDefaultErrorHandler());
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether or not to pass the given packet event to the packet listeners.
|
||||
* @param event - the event.
|
||||
* @param handler - failure handler.
|
||||
* @return TRUE if we should, FALSE otherwise.
|
||||
*/
|
||||
public boolean filterEvent(PacketEvent event, FilterFailedHandler handler) {
|
||||
for (Iterator<Filter> it = filters.iterator(); it.hasNext(); ) {
|
||||
Filter filter = it.next();
|
||||
|
||||
try {
|
||||
if (!filter.evaluate(engine, event)) {
|
||||
return false;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
if (!handler.handle(event, filter, ex)) {
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
// Pass!
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the script engine if necessary.
|
||||
*/
|
||||
private void checkScriptStatus() {
|
||||
// Start the engine
|
||||
if (uninitialized) {
|
||||
uninitialized = false;
|
||||
initalizeScript();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Description: Adds or removes a simple packet filter.
|
||||
Usage: /<command> add|remove name [packet IDs]
|
||||
*/
|
||||
@Override
|
||||
protected boolean handleCommand(CommandSender sender, String[] args) {
|
||||
checkScriptStatus();
|
||||
|
||||
if (!config.isDebug()) {
|
||||
sender.sendMessage(ChatColor.RED + "Debug mode must be enabled in the configuration first!");
|
||||
return true;
|
||||
}
|
||||
if (!isInitialized()) {
|
||||
sender.sendMessage(ChatColor.RED + "JavaScript engine was not present. Filter system is disabled.");
|
||||
return true;
|
||||
}
|
||||
|
||||
final SubCommand command = parseCommand(args, 0);
|
||||
final String name = args[1];
|
||||
|
||||
switch (command) {
|
||||
case ADD:
|
||||
// Never overwrite an existing filter
|
||||
if (findFilter(name) != null) {
|
||||
sender.sendMessage(ChatColor.RED + "Filter " + name + " already exists. Remove it first.");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Prepare the input to the packet type parser
|
||||
Deque<String> rangeArguments = toQueue(args, 2);
|
||||
|
||||
final PacketTypeParser parser = new PacketTypeParser();
|
||||
final Set<PacketType> packets = parser.parseTypes(rangeArguments, PacketTypeParser.DEFAULT_MAX_RANGE);
|
||||
sender.sendMessage("Enter filter program ('}' to complete or CANCEL):");
|
||||
|
||||
// Make sure we can use the conversable interface
|
||||
if (sender instanceof Conversable) {
|
||||
final MultipleLinesPrompt prompt =
|
||||
new MultipleLinesPrompt(new CompilationSuccessCanceller(), "function(event, packet) {");
|
||||
|
||||
new ConversationFactory(plugin).
|
||||
withFirstPrompt(prompt).
|
||||
withEscapeSequence("CANCEL").
|
||||
withLocalEcho(false).
|
||||
addConversationAbandonedListener(new ConversationAbandonedListener() {
|
||||
@Override
|
||||
public void conversationAbandoned(ConversationAbandonedEvent event) {
|
||||
try {
|
||||
final Conversable whom = event.getContext().getForWhom();
|
||||
|
||||
if (event.gracefulExit()) {
|
||||
String predicate = prompt.removeAccumulatedInput(event.getContext());
|
||||
Filter filter = new Filter(name, predicate, packets);
|
||||
|
||||
// Print the last line as well
|
||||
whom.sendRawMessage(prompt.getPromptText(event.getContext()));
|
||||
|
||||
try {
|
||||
// Force early compilation
|
||||
filter.compile(engine);
|
||||
|
||||
filters.add(filter);
|
||||
whom.sendRawMessage(ChatColor.GOLD + "Added filter " + name);
|
||||
} catch (ScriptException e) {
|
||||
e.printStackTrace();
|
||||
whom.sendRawMessage(ChatColor.GOLD + "Compilation error: " + e.getMessage());
|
||||
}
|
||||
} else {
|
||||
// Too bad
|
||||
whom.sendRawMessage(ChatColor.RED + "Cancelled filter.");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
reporter.reportDetailed(this,
|
||||
Report.newBuilder(REPORT_CANNOT_HANDLE_CONVERSATION).error(e).callerParam(event)
|
||||
);
|
||||
}
|
||||
}
|
||||
}).
|
||||
buildConversation((Conversable) sender).
|
||||
begin();
|
||||
} else {
|
||||
sender.sendMessage(ChatColor.RED + "Only console and players are supported!");
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case REMOVE:
|
||||
Filter filter = findFilter(name);
|
||||
|
||||
// See if it exists before we remove it
|
||||
if (filter != null) {
|
||||
filter.close(engine);
|
||||
filters.remove(filter);
|
||||
sender.sendMessage(ChatColor.GOLD + "Removed filter " + name);
|
||||
} else {
|
||||
sender.sendMessage(ChatColor.RED + "Unable to find a filter by the name " + name);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup a filter by its name.
|
||||
* @param name - the filter name.
|
||||
* @return The filter, or NULL if not found.
|
||||
*/
|
||||
private Filter findFilter(String name) {
|
||||
// We'll just use a linear scan for now - we don't expect that many filters
|
||||
for (Filter filter : filters) {
|
||||
if (filter.getName().equalsIgnoreCase(name)) {
|
||||
return filter;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private SubCommand parseCommand(String[] args, int index) {
|
||||
String text = args[index].toUpperCase();
|
||||
|
||||
try {
|
||||
return SubCommand.valueOf(text);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new IllegalArgumentException(text + " is not a valid sub command. Must be add or remove.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,547 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Deque;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.WeakHashMap;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import net.sf.cglib.proxy.Factory;
|
||||
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import com.comphenix.protocol.PacketType.Sender;
|
||||
import com.comphenix.protocol.concurrency.PacketTypeSet;
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.error.Report;
|
||||
import com.comphenix.protocol.error.ReportType;
|
||||
import com.comphenix.protocol.events.ListeningWhitelist;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.events.PacketListener;
|
||||
import com.comphenix.protocol.reflect.EquivalentConverter;
|
||||
import com.comphenix.protocol.reflect.PrettyPrinter;
|
||||
import com.comphenix.protocol.reflect.PrettyPrinter.ObjectPrinter;
|
||||
import com.comphenix.protocol.utility.ChatExtensions;
|
||||
import com.comphenix.protocol.utility.HexDumper;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
import com.comphenix.protocol.wrappers.BukkitConverters;
|
||||
import com.google.common.collect.MapMaker;
|
||||
import com.google.common.collect.Sets;
|
||||
|
||||
/**
|
||||
* Handles the "packet" debug command.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
class CommandPacket extends CommandBase {
|
||||
public static final ReportType REPORT_CANNOT_SEND_MESSAGE = new ReportType("Cannot send chat message.");
|
||||
|
||||
private enum SubCommand {
|
||||
ADD, REMOVE, NAMES, PAGE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Name of this command.
|
||||
*/
|
||||
public static final String NAME = "packet";
|
||||
|
||||
/**
|
||||
* Number of lines per page.
|
||||
*/
|
||||
public static final int PAGE_LINE_COUNT = 9;
|
||||
|
||||
/**
|
||||
* Number of bytes before we do a hex dump.
|
||||
*/
|
||||
private static final int HEX_DUMP_THRESHOLD = 256;
|
||||
|
||||
private Plugin plugin;
|
||||
private Logger logger;
|
||||
private ProtocolManager manager;
|
||||
|
||||
private ChatExtensions chatter;
|
||||
|
||||
// The main parser
|
||||
private PacketTypeParser typeParser = new PacketTypeParser();
|
||||
|
||||
// Paged message
|
||||
private Map<CommandSender, List<String>> pagedMessage = new WeakHashMap<CommandSender, List<String>>();
|
||||
|
||||
// Current registered packet types
|
||||
private PacketTypeSet packetTypes = new PacketTypeSet();
|
||||
private PacketTypeSet extendedTypes = new PacketTypeSet();
|
||||
|
||||
// Compare listeners
|
||||
private PacketTypeSet compareTypes = new PacketTypeSet();
|
||||
private Map<PacketEvent, String> originalPackets = new MapMaker().weakKeys().makeMap();
|
||||
|
||||
// The packet listener
|
||||
private PacketListener listener;
|
||||
|
||||
// Compare listener
|
||||
private PacketListener compareListener;
|
||||
|
||||
// Filter packet events
|
||||
private CommandFilter filter;
|
||||
|
||||
public CommandPacket(ErrorReporter reporter, Plugin plugin, Logger logger, CommandFilter filter, ProtocolManager manager) {
|
||||
super(reporter, CommandBase.PERMISSION_ADMIN, NAME, 1);
|
||||
this.plugin = plugin;
|
||||
this.logger = logger;
|
||||
this.manager = manager;
|
||||
this.filter = filter;
|
||||
this.chatter = new ChatExtensions(manager);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a message without invoking the packet listeners.
|
||||
* @param receiver - the player to send it to.
|
||||
* @param message - the message to send.
|
||||
* @return TRUE if the message was sent successfully, FALSE otherwise.
|
||||
*/
|
||||
public void sendMessageSilently(CommandSender receiver, String message) {
|
||||
try {
|
||||
chatter.sendMessageSilently(receiver, message);
|
||||
} catch (InvocationTargetException e) {
|
||||
reporter.reportDetailed(this,
|
||||
Report.newBuilder(REPORT_CANNOT_SEND_MESSAGE).error(e).callerParam(receiver, message)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast a message without invoking any packet listeners.
|
||||
* @param message - message to send.
|
||||
* @param permission - permission required to receieve the message. NULL to target everyone.
|
||||
*/
|
||||
public void broadcastMessageSilently(String message, String permission) {
|
||||
try {
|
||||
chatter.broadcastMessageSilently(message, permission);
|
||||
} catch (InvocationTargetException e) {
|
||||
reporter.reportDetailed(this,
|
||||
Report.newBuilder(REPORT_CANNOT_SEND_MESSAGE).error(e).callerParam(message, permission)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private void printPage(CommandSender sender, int pageIndex) {
|
||||
List<String> paged = pagedMessage.get(sender);
|
||||
|
||||
// Make sure the player has any pages
|
||||
if (paged != null) {
|
||||
int lastPage = ((paged.size() - 1) / PAGE_LINE_COUNT) + 1;
|
||||
|
||||
for (int i = PAGE_LINE_COUNT * (pageIndex - 1); i < PAGE_LINE_COUNT * pageIndex; i++) {
|
||||
if (i < paged.size()) {
|
||||
sendMessageSilently(sender, " " + paged.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
// More data?
|
||||
if (pageIndex < lastPage) {
|
||||
sendMessageSilently(sender, "Send /packet page " + (pageIndex + 1) + " for the next page.");
|
||||
}
|
||||
|
||||
} else {
|
||||
sendMessageSilently(sender, ChatColor.RED + "No pages found.");
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Description: Adds or removes a simple packet listener.
|
||||
Usage: /<command> add|remove client|server|both [ID start] [ID stop] [detailed]
|
||||
*/
|
||||
@Override
|
||||
protected boolean handleCommand(CommandSender sender, String[] args) {
|
||||
try {
|
||||
Deque<String> arguments = new ArrayDeque<String>(Arrays.asList(args));
|
||||
SubCommand subCommand = parseCommand(arguments);
|
||||
|
||||
// Commands with different parameters
|
||||
if (subCommand == SubCommand.PAGE) {
|
||||
if (args.length <= 1) {
|
||||
sendMessageSilently(sender, ChatColor.RED + "Must specify a page index.");
|
||||
return true;
|
||||
}
|
||||
|
||||
int page = Integer.parseInt(args[1]);
|
||||
|
||||
if (page > 0)
|
||||
printPage(sender, page);
|
||||
else
|
||||
sendMessageSilently(sender, ChatColor.RED + "Page index must be greater than zero.");
|
||||
return true;
|
||||
}
|
||||
|
||||
Set<PacketType> types = typeParser.parseTypes(arguments, PacketTypeParser.DEFAULT_MAX_RANGE);
|
||||
Boolean detailed = parseBoolean(arguments, "detailed");
|
||||
Boolean compare = parseBoolean(arguments, "compare");
|
||||
|
||||
// Notify user
|
||||
if (typeParser.getLastProtocol() == null) {
|
||||
sender.sendMessage(ChatColor.YELLOW + "Warning: Missing protocol (PLAY, etc) - assuming legacy IDs.");
|
||||
}
|
||||
if (arguments.size() > 0) {
|
||||
throw new IllegalArgumentException("Cannot parse " + arguments);
|
||||
}
|
||||
|
||||
// The last elements are optional
|
||||
if (detailed == null) {
|
||||
detailed = false;
|
||||
}
|
||||
if (compare == null) {
|
||||
compare = false;
|
||||
} else {
|
||||
// This is implied - we cannot compare content without going detailed
|
||||
detailed = true;
|
||||
}
|
||||
|
||||
// Perform commands
|
||||
if (subCommand == SubCommand.ADD) {
|
||||
// The add command is dangerous - don't default on the connection side
|
||||
if (args.length == 1) {
|
||||
sender.sendMessage(ChatColor.RED + "Please specify a connection side.");
|
||||
return false;
|
||||
}
|
||||
|
||||
executeAddCommand(sender, types, detailed, compare);
|
||||
} else if (subCommand == SubCommand.REMOVE) {
|
||||
executeRemoveCommand(sender, types);
|
||||
} else if (subCommand == SubCommand.NAMES) {
|
||||
executeNamesCommand(sender, types);
|
||||
}
|
||||
|
||||
} catch (NumberFormatException e) {
|
||||
sendMessageSilently(sender, ChatColor.RED + "Cannot parse number: " + e.getMessage());
|
||||
} catch (IllegalArgumentException e) {
|
||||
sendMessageSilently(sender, ChatColor.RED + e.getMessage());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void executeAddCommand(CommandSender sender, Set<PacketType> addition, boolean detailed, boolean compare) {
|
||||
packetTypes.addAll(addition);
|
||||
|
||||
// Also mark these types as "detailed"
|
||||
if (detailed) {
|
||||
extendedTypes.addAll(addition);
|
||||
}
|
||||
// Whether or not to compare the packet with the initial state
|
||||
if (compare) {
|
||||
compareTypes.addAll(addition);
|
||||
}
|
||||
|
||||
updatePacketListener();
|
||||
sendMessageSilently(sender, ChatColor.YELLOW + "Added listener " + getWhitelistInfo(listener));
|
||||
}
|
||||
|
||||
private void executeRemoveCommand(CommandSender sender, Set<PacketType> removal) {
|
||||
packetTypes.removeAll(removal);
|
||||
extendedTypes.removeAll(removal);
|
||||
compareTypes.removeAll(removal);
|
||||
updatePacketListener();
|
||||
sendMessageSilently(sender, ChatColor.YELLOW + "Removing packet types.");
|
||||
}
|
||||
|
||||
private void executeNamesCommand(CommandSender sender, Set<PacketType> types) {
|
||||
List<String> messages = new ArrayList<String>();
|
||||
|
||||
// Print the equivalent name of every given ID
|
||||
for (PacketType type : types) {
|
||||
messages.add(ChatColor.YELLOW + type.toString());
|
||||
}
|
||||
|
||||
if (sender instanceof Player && messages.size() > 0 && messages.size() > PAGE_LINE_COUNT) {
|
||||
// Divide the messages into chuncks
|
||||
pagedMessage.put(sender, messages);
|
||||
printPage(sender, 1);
|
||||
|
||||
} else {
|
||||
// Just print the damn thing
|
||||
for (String message : messages) {
|
||||
sendMessageSilently(sender, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve whitelist information about a given listener.
|
||||
* @param listener - the given listener.
|
||||
* @return Whitelist information.
|
||||
*/
|
||||
private String getWhitelistInfo(PacketListener listener) {
|
||||
boolean sendingEmpty = ListeningWhitelist.isEmpty(listener.getSendingWhitelist());
|
||||
boolean receivingEmpty = ListeningWhitelist.isEmpty(listener.getReceivingWhitelist());
|
||||
|
||||
if (!sendingEmpty && !receivingEmpty)
|
||||
return String.format("Sending: %s, Receiving: %s", listener.getSendingWhitelist(), listener.getReceivingWhitelist());
|
||||
else if (!sendingEmpty)
|
||||
return listener.getSendingWhitelist().toString();
|
||||
else if (!receivingEmpty)
|
||||
return listener.getReceivingWhitelist().toString();
|
||||
else
|
||||
return "[None]";
|
||||
}
|
||||
|
||||
private Set<PacketType> filterTypes(Set<PacketType> types, Sender sender) {
|
||||
Set<PacketType> result = Sets.newHashSet();
|
||||
|
||||
for (PacketType type : types) {
|
||||
if (type.getSender() == sender) {
|
||||
result.add(type);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public PacketListener createPacketListener(Set<PacketType> type) {
|
||||
final ListeningWhitelist serverList = ListeningWhitelist.newBuilder().
|
||||
types(filterTypes(type, Sender.SERVER)).
|
||||
gamePhaseBoth().
|
||||
monitor().
|
||||
build();
|
||||
|
||||
final ListeningWhitelist clientList = ListeningWhitelist.newBuilder(serverList).
|
||||
types(filterTypes(type, Sender.CLIENT)).
|
||||
monitor().
|
||||
build();
|
||||
|
||||
return new PacketListener() {
|
||||
@Override
|
||||
public void onPacketSending(PacketEvent event) {
|
||||
if (filter.filterEvent(event)) {
|
||||
printInformation(event);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketReceiving(PacketEvent event) {
|
||||
if (filter.filterEvent(event)) {
|
||||
printInformation(event);
|
||||
}
|
||||
}
|
||||
|
||||
private void printInformation(PacketEvent event) {
|
||||
String verb = event.isServerPacket() ? "Sent" : "Received";
|
||||
String format = event.isServerPacket() ?
|
||||
"%s %s to %s" :
|
||||
"%s %s from %s";
|
||||
|
||||
String shortDescription = String.format(format,
|
||||
event.isCancelled() ? "Cancelled" : verb,
|
||||
event.getPacketType(),
|
||||
event.getPlayer().getName()
|
||||
);
|
||||
|
||||
// Detailed will print the packet's content too
|
||||
if (extendedTypes.contains(event.getPacketType())) {
|
||||
try {
|
||||
String original = originalPackets.remove(event);
|
||||
|
||||
// Also print original
|
||||
if (original != null) {
|
||||
logger.info("Initial packet:\n" + original + " -> ");
|
||||
}
|
||||
|
||||
logger.info(shortDescription + ":\n" + getPacketDescription(
|
||||
event.getPacket())
|
||||
);
|
||||
} catch (IllegalAccessException e) {
|
||||
logger.log(Level.WARNING, "Unable to use reflection.", e);
|
||||
}
|
||||
} else {
|
||||
logger.info(shortDescription + ".");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListeningWhitelist getSendingWhitelist() {
|
||||
return serverList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListeningWhitelist getReceivingWhitelist() {
|
||||
return clientList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Plugin getPlugin() {
|
||||
return plugin;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public PacketListener createCompareListener(Set<PacketType> type) {
|
||||
final ListeningWhitelist serverList = ListeningWhitelist.newBuilder().
|
||||
types(filterTypes(type, Sender.SERVER)).
|
||||
gamePhaseBoth().
|
||||
lowest().
|
||||
build();
|
||||
|
||||
final ListeningWhitelist clientList = ListeningWhitelist.newBuilder(serverList).
|
||||
types(filterTypes(type, Sender.CLIENT)).
|
||||
lowest().
|
||||
build();
|
||||
|
||||
return new PacketListener() {
|
||||
@Override
|
||||
public void onPacketSending(PacketEvent event) {
|
||||
savePacketState(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketReceiving(PacketEvent event) {
|
||||
savePacketState(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the original value.
|
||||
* @param event - the event with the packet to save.
|
||||
*/
|
||||
private void savePacketState(PacketEvent event) {
|
||||
try {
|
||||
originalPackets.put(event, getPacketDescription(event.getPacket()));
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException("Cannot read packet.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListeningWhitelist getSendingWhitelist() {
|
||||
return serverList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListeningWhitelist getReceivingWhitelist() {
|
||||
return clientList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Plugin getPlugin() {
|
||||
return plugin;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a detailed string representation of the given packet.
|
||||
* @param packetContainer - the packet to describe.
|
||||
* @return The detailed description.
|
||||
* @throws IllegalAccessException An error occured.
|
||||
*/
|
||||
public String getPacketDescription(PacketContainer packetContainer) throws IllegalAccessException {
|
||||
Object packet = packetContainer.getHandle();
|
||||
Class<?> clazz = packet.getClass();
|
||||
|
||||
// Get the first Minecraft super class
|
||||
while (clazz != null && clazz != Object.class &&
|
||||
(!MinecraftReflection.isMinecraftClass(clazz) ||
|
||||
Factory.class.isAssignableFrom(clazz))) {
|
||||
clazz = clazz.getSuperclass();
|
||||
}
|
||||
|
||||
return PrettyPrinter.printObject(packet, clazz, MinecraftReflection.getPacketClass(), PrettyPrinter.RECURSE_DEPTH, new ObjectPrinter() {
|
||||
@Override
|
||||
public boolean print(StringBuilder output, Object value) {
|
||||
// Special case
|
||||
if (value instanceof byte[]) {
|
||||
byte[] data = (byte[]) value;
|
||||
|
||||
if (data.length > HEX_DUMP_THRESHOLD) {
|
||||
output.append("[");
|
||||
HexDumper.defaultDumper().appendTo(output, data);
|
||||
output.append("]");
|
||||
return true;
|
||||
}
|
||||
} else if (value != null) {
|
||||
EquivalentConverter<Object> converter = findConverter(value.getClass());
|
||||
|
||||
if (converter != null) {
|
||||
output.append(converter.getSpecific(value));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the closest equivalent converter to a specific class.
|
||||
* @param clazz - the class.
|
||||
* @return The closest converter, or NULL if not found,
|
||||
*/
|
||||
private EquivalentConverter<Object> findConverter(Class<?> clazz) {
|
||||
Map<Class<?>, EquivalentConverter<Object>> converters = BukkitConverters.getConvertersForGeneric();
|
||||
|
||||
while (clazz != null) {
|
||||
EquivalentConverter<Object> result = converters.get(clazz);
|
||||
|
||||
if (result != null)
|
||||
return result;
|
||||
else
|
||||
clazz = clazz.getSuperclass();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public PacketListener updatePacketListener() {
|
||||
if (listener != null) {
|
||||
manager.removePacketListener(listener);
|
||||
}
|
||||
if (compareListener != null) {
|
||||
manager.removePacketListener(compareListener);
|
||||
}
|
||||
|
||||
// Register the new listeners
|
||||
listener = createPacketListener(packetTypes.values());
|
||||
compareListener = createCompareListener(compareTypes.values());
|
||||
manager.addPacketListener(listener);
|
||||
manager.addPacketListener(compareListener);
|
||||
return listener;
|
||||
}
|
||||
|
||||
private SubCommand parseCommand(Deque<String> arguments) {
|
||||
String text = arguments.poll().toLowerCase();
|
||||
|
||||
// Parse this too
|
||||
if ("add".startsWith(text))
|
||||
return SubCommand.ADD;
|
||||
else if ("remove".startsWith(text))
|
||||
return SubCommand.REMOVE;
|
||||
else if ("names".startsWith(text))
|
||||
return SubCommand.NAMES;
|
||||
else if ("page".startsWith(text))
|
||||
return SubCommand.PAGE;
|
||||
else
|
||||
throw new IllegalArgumentException(text + " is not a valid sub command. Must be add or remove.");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,272 @@
|
||||
/**
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
package com.comphenix.protocol;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.bukkit.plugin.PluginDescriptionFile;
|
||||
|
||||
import com.comphenix.protocol.error.DetailedErrorReporter;
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.events.PacketListener;
|
||||
import com.comphenix.protocol.timing.TimedListenerManager;
|
||||
import com.comphenix.protocol.timing.TimingReportGenerator;
|
||||
import com.comphenix.protocol.updater.Updater;
|
||||
import com.comphenix.protocol.updater.Updater.UpdateType;
|
||||
import com.comphenix.protocol.utility.Closer;
|
||||
|
||||
/**
|
||||
* Handles the "protocol" administration command.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
class CommandProtocol extends CommandBase {
|
||||
/**
|
||||
* Name of this command.
|
||||
*/
|
||||
public static final String NAME = "protocol";
|
||||
|
||||
private Plugin plugin;
|
||||
private Updater updater;
|
||||
private ProtocolConfig config;
|
||||
|
||||
public CommandProtocol(ErrorReporter reporter, Plugin plugin, Updater updater, ProtocolConfig config) {
|
||||
super(reporter, CommandBase.PERMISSION_ADMIN, NAME, 1);
|
||||
this.plugin = plugin;
|
||||
this.updater = updater;
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean handleCommand(CommandSender sender, String[] args) {
|
||||
String subCommand = args[0];
|
||||
|
||||
// Only return TRUE if we executed the correct command
|
||||
if (subCommand.equalsIgnoreCase("config") || subCommand.equalsIgnoreCase("reload")) {
|
||||
reloadConfiguration(sender);
|
||||
} else if (subCommand.equalsIgnoreCase("check")) {
|
||||
checkVersion(sender, true);
|
||||
} else if (subCommand.equalsIgnoreCase("update")) {
|
||||
updateVersion(sender, true);
|
||||
} else if (subCommand.equalsIgnoreCase("timings")) {
|
||||
toggleTimings(sender, args);
|
||||
} else if (subCommand.equalsIgnoreCase("listeners")) {
|
||||
printListeners(sender);
|
||||
} else if (subCommand.equalsIgnoreCase("version")) {
|
||||
printVersion(sender);
|
||||
} else if (subCommand.equalsIgnoreCase("dump")) {
|
||||
dump(sender);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void checkVersion(final CommandSender sender, boolean command) {
|
||||
performUpdate(sender, UpdateType.NO_DOWNLOAD, command);
|
||||
}
|
||||
|
||||
public void updateVersion(final CommandSender sender, boolean command) {
|
||||
performUpdate(sender, UpdateType.DEFAULT, command);
|
||||
}
|
||||
|
||||
// Display every listener on the server
|
||||
private void printListeners(final CommandSender sender) {
|
||||
ProtocolManager manager = ProtocolLibrary.getProtocolManager();
|
||||
|
||||
sender.sendMessage(ChatColor.GOLD + "Packet listeners:");
|
||||
for (PacketListener listener : manager.getPacketListeners()) {
|
||||
sender.sendMessage(ChatColor.GOLD + " - " + listener);
|
||||
}
|
||||
|
||||
// Along with every asynchronous listener
|
||||
sender.sendMessage(ChatColor.GOLD + "Asynchronous listeners:");
|
||||
for (PacketListener listener : manager.getAsynchronousManager().getAsyncHandlers()) {
|
||||
sender.sendMessage(ChatColor.GOLD + " - " + listener);
|
||||
}
|
||||
}
|
||||
|
||||
private void performUpdate(final CommandSender sender, UpdateType type, final boolean command) {
|
||||
if (updater.isChecking()) {
|
||||
sender.sendMessage(ChatColor.RED + "Already checking for an update.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Perform on an async thread
|
||||
Runnable notify = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (command) {
|
||||
sender.sendMessage(ChatColor.YELLOW + "[ProtocolLib] " + updater.getResult());
|
||||
String remoteVersion = updater.getRemoteVersion();
|
||||
if (remoteVersion != null) {
|
||||
sender.sendMessage(ChatColor.YELLOW + "Remote version: " + remoteVersion);
|
||||
sender.sendMessage(ChatColor.YELLOW + "Current version: " + plugin.getDescription().getVersion());
|
||||
}
|
||||
} else if (updater.shouldNotify() || config.isDebug()) {
|
||||
sender.sendMessage(ChatColor.YELLOW + "[ProtocolLib] " + updater.getResult());
|
||||
}
|
||||
|
||||
updater.removeListener(this);
|
||||
updateFinished();
|
||||
}
|
||||
};
|
||||
updater.start(type);
|
||||
updater.addListener(notify);
|
||||
}
|
||||
|
||||
private void toggleTimings(CommandSender sender, String[] args) {
|
||||
TimedListenerManager manager = TimedListenerManager.getInstance();
|
||||
boolean state = !manager.isTiming(); // toggle
|
||||
|
||||
// Parse the boolean parameter
|
||||
if (args.length == 2) {
|
||||
Boolean parsed = parseBoolean(toQueue(args, 2), "start");
|
||||
|
||||
if (parsed != null) {
|
||||
state = parsed;
|
||||
} else {
|
||||
sender.sendMessage(ChatColor.RED + "Specify a state: ON or OFF.");
|
||||
return;
|
||||
}
|
||||
} else if (args.length > 2) {
|
||||
sender.sendMessage(ChatColor.RED + "Too many parameters.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Now change the state
|
||||
if (state) {
|
||||
if (manager.startTiming())
|
||||
sender.sendMessage(ChatColor.GOLD + "Started timing packet listeners.");
|
||||
else
|
||||
sender.sendMessage(ChatColor.RED + "Packet timing already started.");
|
||||
} else {
|
||||
if (manager.stopTiming()) {
|
||||
saveTimings(manager);
|
||||
sender.sendMessage(ChatColor.GOLD + "Stopped and saved result in plugin folder.");
|
||||
} else {
|
||||
sender.sendMessage(ChatColor.RED + "Packet timing already stopped.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void saveTimings(TimedListenerManager manager) {
|
||||
try {
|
||||
File destination = new File(plugin.getDataFolder(), "Timings - " + System.currentTimeMillis() + ".txt");
|
||||
TimingReportGenerator generator = new TimingReportGenerator();
|
||||
|
||||
// Print to a text file
|
||||
generator.saveTo(destination, manager);
|
||||
manager.clear();
|
||||
|
||||
} catch (IOException e) {
|
||||
reporter.reportMinimal(plugin, "saveTimings()", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent further automatic updates until the next delay.
|
||||
*/
|
||||
public void updateFinished() {
|
||||
long currentTime = System.currentTimeMillis() / ProtocolLibrary.MILLI_PER_SECOND;
|
||||
|
||||
config.setAutoLastTime(currentTime);
|
||||
config.saveAll();
|
||||
}
|
||||
|
||||
public void reloadConfiguration(CommandSender sender) {
|
||||
plugin.reloadConfig();
|
||||
sender.sendMessage(ChatColor.YELLOW + "Reloaded configuration!");
|
||||
}
|
||||
|
||||
private void printVersion(CommandSender sender) {
|
||||
PluginDescriptionFile desc = plugin.getDescription();
|
||||
|
||||
sender.sendMessage(ChatColor.GREEN + desc.getName() + ChatColor.WHITE + " v" + ChatColor.GREEN + desc.getVersion());
|
||||
sender.sendMessage(ChatColor.WHITE + "Authors: " + ChatColor.GREEN + "dmulloy2" + ChatColor.WHITE + " and " + ChatColor.GREEN + "Comphenix");
|
||||
sender.sendMessage(ChatColor.WHITE + "Issues: " + ChatColor.GREEN + "https://github.com/dmulloy2/ProtocolLib/issues");
|
||||
}
|
||||
|
||||
private static SimpleDateFormat FILE_FORMAT;
|
||||
private static SimpleDateFormat TIMESTAMP_FORMAT;
|
||||
|
||||
private void dump(CommandSender sender) {
|
||||
Closer closer = Closer.create();
|
||||
|
||||
if (FILE_FORMAT == null)
|
||||
FILE_FORMAT = new SimpleDateFormat("yyyy-MM-dd_HH.mm.ss");
|
||||
if (TIMESTAMP_FORMAT == null)
|
||||
TIMESTAMP_FORMAT = new SimpleDateFormat("MM/dd/yy HH:mm:ss");
|
||||
|
||||
try {
|
||||
Date date = new Date();
|
||||
File file = new File(plugin.getDataFolder(), "dump-" + FILE_FORMAT.format(date) + ".txt");
|
||||
if (file.exists()) {
|
||||
file.delete();
|
||||
}
|
||||
|
||||
file.createNewFile();
|
||||
|
||||
FileWriter fw = closer.register(new FileWriter(file));
|
||||
PrintWriter pw = closer.register(new PrintWriter(fw));
|
||||
|
||||
pw.println("ProtocolLib Dump");
|
||||
pw.println("Timestamp: " + TIMESTAMP_FORMAT.format(date));
|
||||
pw.println();
|
||||
|
||||
pw.println("ProtocolLib Version: " + plugin.toString());
|
||||
pw.println("Bukkit Version: " + plugin.getServer().getBukkitVersion());
|
||||
pw.println("Server Version: " + plugin.getServer().getVersion());
|
||||
pw.println("Java Version: " + System.getProperty("java.version"));
|
||||
pw.println();
|
||||
|
||||
ProtocolManager manager = ProtocolLibrary.getProtocolManager();
|
||||
pw.println("ProtocolLib: " + DetailedErrorReporter.getStringDescription(plugin));
|
||||
pw.println("Manager: " + DetailedErrorReporter.getStringDescription(manager));
|
||||
pw.println();
|
||||
|
||||
Set<PacketListener> listeners = manager.getPacketListeners();
|
||||
if (listeners.size() > 0) {
|
||||
pw.println("Listeners:");
|
||||
|
||||
for (PacketListener listener : listeners) {
|
||||
pw.println(DetailedErrorReporter.getStringDescription(listener));
|
||||
}
|
||||
} else {
|
||||
pw.println("No listeners");
|
||||
}
|
||||
|
||||
sender.sendMessage("Data dump written to " + file.getAbsolutePath());
|
||||
} catch (IOException ex) {
|
||||
ProtocolLibrary.log(Level.SEVERE, "Failed to create dump:", ex);
|
||||
sender.sendMessage(ChatColor.RED + "Failed to create dump! Check console!");
|
||||
} finally {
|
||||
closer.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,114 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import com.comphenix.protocol.events.ListenerPriority;
|
||||
import com.comphenix.protocol.events.NetworkMarker;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.injector.netty.WirePacket;
|
||||
|
||||
/**
|
||||
* Represents a object capable of sending or receiving packets.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public interface PacketStream {
|
||||
/**
|
||||
* Send a packet to the given player.
|
||||
* @param receiver - the reciever.
|
||||
* @param packet - packet to send.
|
||||
* @throws InvocationTargetException - if an error occured when sending the packet.
|
||||
*/
|
||||
public void sendServerPacket(Player receiver, PacketContainer packet)
|
||||
throws InvocationTargetException;
|
||||
|
||||
/**
|
||||
* Send a packet to the given player.
|
||||
* @param receiver - the reciever.
|
||||
* @param packet - packet to send.
|
||||
* @param filters - whether or not to invoke any packet filters below {@link ListenerPriority#MONITOR}.
|
||||
* @throws InvocationTargetException - if an error occured when sending the packet.
|
||||
*/
|
||||
public void sendServerPacket(Player receiver, PacketContainer packet, boolean filters)
|
||||
throws InvocationTargetException;
|
||||
|
||||
/**
|
||||
* Send a packet to the given player.
|
||||
* @param receiver - the receiver.
|
||||
* @param packet - packet to send.
|
||||
* @param marker - the network marker to use.
|
||||
* @param filters - whether or not to invoke any packet filters below {@link ListenerPriority#MONITOR}.
|
||||
* @throws InvocationTargetException - if an error occured when sending the packet.
|
||||
*/
|
||||
public void sendServerPacket(Player receiver, PacketContainer packet, NetworkMarker marker, boolean filters)
|
||||
throws InvocationTargetException;
|
||||
|
||||
/**
|
||||
* Send a wire packet to the given player.
|
||||
* @param receiver - the receiver.
|
||||
* @param id - packet id.
|
||||
* @param bytes - packet bytes.
|
||||
* @throws InvocationTargetException if an error occured when sending the packet.
|
||||
*/
|
||||
public void sendWirePacket(Player receiver, int id, byte[] bytes) throws InvocationTargetException;
|
||||
|
||||
/**
|
||||
* Send a wire packet to the given player.
|
||||
* @param receiver - the receiver.
|
||||
* @param packet - packet to send.
|
||||
* @throws InvocationTargetException if an error occured when sending the packet.
|
||||
*/
|
||||
public void sendWirePacket(Player receiver, WirePacket packet) throws InvocationTargetException;
|
||||
|
||||
/**
|
||||
* Simulate recieving a certain packet from a given player.
|
||||
* @param sender - the sender.
|
||||
* @param packet - the packet that was sent.
|
||||
* @throws InvocationTargetException If the reflection machinery failed.
|
||||
* @throws IllegalAccessException If the underlying method caused an error.
|
||||
*/
|
||||
public void recieveClientPacket(Player sender, PacketContainer packet)
|
||||
throws IllegalAccessException, InvocationTargetException;
|
||||
|
||||
/**
|
||||
* Simulate recieving a certain packet from a given player.
|
||||
* @param sender - the sender.
|
||||
* @param packet - the packet that was sent.
|
||||
* @param filters - whether or not to invoke any packet filters below {@link ListenerPriority#MONITOR}.
|
||||
* @throws InvocationTargetException If the reflection machinery failed.
|
||||
* @throws IllegalAccessException If the underlying method caused an error.
|
||||
*/
|
||||
public void recieveClientPacket(Player sender, PacketContainer packet, boolean filters)
|
||||
throws IllegalAccessException, InvocationTargetException;
|
||||
|
||||
/**
|
||||
* Simulate recieving a certain packet from a given player.
|
||||
* @param sender - the sender.
|
||||
* @param packet - the packet that was sent.
|
||||
* @param marker - the network marker to use.
|
||||
* @param filters - whether or not to invoke any packet filters below {@link ListenerPriority#MONITOR}.
|
||||
* @throws InvocationTargetException If the reflection machinery failed.
|
||||
* @throws IllegalAccessException If the underlying method caused an error.
|
||||
*/
|
||||
public void recieveClientPacket(Player sender, PacketContainer packet, NetworkMarker marker, boolean filters)
|
||||
throws IllegalAccessException, InvocationTargetException;
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,187 @@
|
||||
package com.comphenix.protocol;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import com.comphenix.protocol.PacketType.Protocol;
|
||||
import com.comphenix.protocol.PacketType.Sender;
|
||||
import com.comphenix.protocol.collections.IntegerMap;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.HashMultimap;
|
||||
import com.google.common.collect.Multimap;
|
||||
|
||||
/**
|
||||
* Retrieve a packet type based on its version and ID, optionally with protocol and sender too.
|
||||
* @author Kristian
|
||||
*/
|
||||
class PacketTypeLookup {
|
||||
public static class ProtocolSenderLookup {
|
||||
// Unroll lookup for performance reasons
|
||||
public final IntegerMap<PacketType> HANDSHAKE_CLIENT = IntegerMap.newMap();
|
||||
public final IntegerMap<PacketType> HANDSHAKE_SERVER = IntegerMap.newMap();
|
||||
public final IntegerMap<PacketType> GAME_CLIENT = IntegerMap.newMap();
|
||||
public final IntegerMap<PacketType> GAME_SERVER = IntegerMap.newMap();
|
||||
public final IntegerMap<PacketType> STATUS_CLIENT = IntegerMap.newMap();
|
||||
public final IntegerMap<PacketType> STATUS_SERVER = IntegerMap.newMap();
|
||||
public final IntegerMap<PacketType> LOGIN_CLIENT = IntegerMap.newMap();
|
||||
public final IntegerMap<PacketType> LOGIN_SERVER = IntegerMap.newMap();
|
||||
|
||||
/**
|
||||
* Retrieve the correct integer map for a specific protocol and sender.
|
||||
* @param protocol - the protocol.
|
||||
* @param sender - the sender.
|
||||
* @return The integer map of packets.
|
||||
*/
|
||||
public IntegerMap<PacketType> getMap(Protocol protocol, Sender sender) {
|
||||
switch (protocol) {
|
||||
case HANDSHAKING:
|
||||
return sender == Sender.CLIENT ? HANDSHAKE_CLIENT : HANDSHAKE_SERVER;
|
||||
case PLAY:
|
||||
return sender == Sender.CLIENT ? GAME_CLIENT : GAME_SERVER;
|
||||
case STATUS:
|
||||
return sender == Sender.CLIENT ? STATUS_CLIENT : STATUS_SERVER;
|
||||
case LOGIN:
|
||||
return sender == Sender.CLIENT ? LOGIN_CLIENT : LOGIN_SERVER;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unable to find protocol " + protocol);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class ClassLookup {
|
||||
// Unroll lookup for performance reasons
|
||||
public final Map<String, PacketType> HANDSHAKE_CLIENT = new ConcurrentHashMap<String, PacketType>();
|
||||
public final Map<String, PacketType> HANDSHAKE_SERVER = new ConcurrentHashMap<String, PacketType>();
|
||||
public final Map<String, PacketType> GAME_CLIENT = new ConcurrentHashMap<String, PacketType>();
|
||||
public final Map<String, PacketType> GAME_SERVER = new ConcurrentHashMap<String, PacketType>();
|
||||
public final Map<String, PacketType> STATUS_CLIENT = new ConcurrentHashMap<String, PacketType>();
|
||||
public final Map<String, PacketType> STATUS_SERVER = new ConcurrentHashMap<String, PacketType>();
|
||||
public final Map<String, PacketType> LOGIN_CLIENT = new ConcurrentHashMap<String, PacketType>();
|
||||
public final Map<String, PacketType> LOGIN_SERVER = new ConcurrentHashMap<String, PacketType>();
|
||||
|
||||
/**
|
||||
* Retrieve the correct integer map for a specific protocol and sender.
|
||||
* @param protocol - the protocol.
|
||||
* @param sender - the sender.
|
||||
* @return The integer map of packets.
|
||||
*/
|
||||
public Map<String, PacketType> getMap(Protocol protocol, Sender sender) {
|
||||
switch (protocol) {
|
||||
case HANDSHAKING:
|
||||
return sender == Sender.CLIENT ? HANDSHAKE_CLIENT : HANDSHAKE_SERVER;
|
||||
case PLAY:
|
||||
return sender == Sender.CLIENT ? GAME_CLIENT : GAME_SERVER;
|
||||
case STATUS:
|
||||
return sender == Sender.CLIENT ? STATUS_CLIENT : STATUS_SERVER;
|
||||
case LOGIN:
|
||||
return sender == Sender.CLIENT ? LOGIN_CLIENT : LOGIN_SERVER;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unable to find protocol " + protocol);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Packet IDs from 1.6.4 and below
|
||||
private final IntegerMap<PacketType> legacyLookup = new IntegerMap<PacketType>();
|
||||
private final IntegerMap<PacketType> serverLookup = new IntegerMap<PacketType>();
|
||||
private final IntegerMap<PacketType> clientLookup = new IntegerMap<PacketType>();
|
||||
|
||||
// Packets for 1.7.2
|
||||
private final ProtocolSenderLookup idLookup = new ProtocolSenderLookup();
|
||||
|
||||
// Packets for 1.8+
|
||||
private final ClassLookup classLookup = new ClassLookup();
|
||||
|
||||
// Packets based on name
|
||||
private final Multimap<String, PacketType> nameLookup = HashMultimap.create();
|
||||
|
||||
/**
|
||||
* Add a collection of packet types to the lookup.
|
||||
* @param types - the types to add.
|
||||
*/
|
||||
public PacketTypeLookup addPacketTypes(Iterable<? extends PacketType> types) {
|
||||
Preconditions.checkNotNull(types, "types cannot be NULL");
|
||||
|
||||
for (PacketType type : types) {
|
||||
int legacy = type.getLegacyId();
|
||||
|
||||
// Skip unknown legacy packets
|
||||
if (legacy != PacketType.UNKNOWN_PACKET) {
|
||||
if (type.isServer())
|
||||
serverLookup.put(type.getLegacyId(), type);
|
||||
if (type.isClient())
|
||||
clientLookup.put(type.getLegacyId(), type);
|
||||
legacyLookup.put(type.getLegacyId(), type);
|
||||
}
|
||||
// Skip unknown current packets
|
||||
if (type.getCurrentId() != PacketType.UNKNOWN_PACKET) {
|
||||
idLookup.getMap(type.getProtocol(), type.getSender()).put(type.getCurrentId(), type);
|
||||
classLookup.getMap(type.getProtocol(), type.getSender()).put(type.getClassNames()[0], type);
|
||||
}
|
||||
nameLookup.put(type.name(), type);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a packet type from a legacy (1.6.4 and below) packet ID.
|
||||
* @param packetId - the legacy packet ID.
|
||||
* @return The corresponding packet type, or NULL if not found.
|
||||
*/
|
||||
public PacketType getFromLegacy(int packetId) {
|
||||
return legacyLookup.get(packetId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve an unmodifiable view of all the packet types with this name.
|
||||
* @param name - the name.
|
||||
* @return The packet types, usually one.
|
||||
*/
|
||||
public Collection<PacketType> getFromName(String name) {
|
||||
return Collections.unmodifiableCollection(nameLookup.get(name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a packet type from a legacy (1.6.4 and below) packet ID.
|
||||
* @param packetId - the legacy packet ID.
|
||||
* @param preference - which packet type to look for first.
|
||||
* @return The corresponding packet type, or NULL if not found.
|
||||
*/
|
||||
public PacketType getFromLegacy(int packetId, Sender preference) {
|
||||
if (preference == Sender.CLIENT)
|
||||
return getFirst(packetId, clientLookup, serverLookup);
|
||||
else
|
||||
return getFirst(packetId, serverLookup, clientLookup);
|
||||
}
|
||||
|
||||
// Helper method for looking up in two sets
|
||||
private <T> T getFirst(int packetId, IntegerMap<T> first, IntegerMap<T> second) {
|
||||
if (first.containsKey(packetId))
|
||||
return first.get(packetId);
|
||||
else
|
||||
return second.get(packetId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a packet type from a protocol, sender and packet ID.
|
||||
* @param protocol - the current protocol.
|
||||
* @param sender - the sender.
|
||||
* @param packetId - the packet ID.
|
||||
* @return The corresponding packet type, or NULL if not found.
|
||||
* @deprecated IDs are no longer reliable
|
||||
*/
|
||||
@Deprecated
|
||||
public PacketType getFromCurrent(Protocol protocol, Sender sender, int packetId) {
|
||||
return idLookup.getMap(protocol, sender).get(packetId);
|
||||
}
|
||||
|
||||
public PacketType getFromCurrent(Protocol protocol, Sender sender, String name) {
|
||||
return classLookup.getMap(protocol, sender).get(name);
|
||||
}
|
||||
|
||||
public ClassLookup getClassLookup() {
|
||||
return classLookup;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
package com.comphenix.protocol;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Deque;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import com.comphenix.protocol.PacketType.Protocol;
|
||||
import com.comphenix.protocol.PacketType.Sender;
|
||||
import com.comphenix.protocol.compat.guava.Guava;
|
||||
import com.comphenix.protocol.events.ConnectionSide;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Range;
|
||||
import com.google.common.collect.Sets;
|
||||
|
||||
class PacketTypeParser {
|
||||
public final static Range<Integer> DEFAULT_MAX_RANGE = Guava.closedRange(0, 255);
|
||||
|
||||
private Sender side = null;
|
||||
private Protocol protocol = null;
|
||||
|
||||
public Set<PacketType> parseTypes(Deque<String> arguments, Range<Integer> defaultRange) {
|
||||
Set<PacketType> result = Sets.newHashSet();
|
||||
side = null;
|
||||
protocol = null;
|
||||
|
||||
// Find these first
|
||||
while (side == null) {
|
||||
String arg = arguments.poll();
|
||||
|
||||
// Attempt to parse a side or protocol first
|
||||
if (side == null) {
|
||||
ConnectionSide connection = parseSide(arg);
|
||||
|
||||
if (connection != null) {
|
||||
side = connection.getSender();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (protocol == null) {
|
||||
if ((protocol = parseProtocol(arg)) != null) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Specify connection side (CLIENT or SERVER).");
|
||||
}
|
||||
|
||||
// Then we move on to parsing IDs (named packet types soon to come)
|
||||
List<Range<Integer>> ranges = RangeParser.getRanges(arguments, DEFAULT_MAX_RANGE);
|
||||
|
||||
// And finally, parse packet names if we have a protocol
|
||||
if (protocol != null) {
|
||||
for (Iterator<String> it = arguments.iterator(); it.hasNext(); ) {
|
||||
String name = it.next().toUpperCase();
|
||||
Collection<PacketType> names = PacketType.fromName(name);
|
||||
|
||||
for (PacketType type : names) {
|
||||
if (type.getProtocol() == protocol && type.getSender() == side) {
|
||||
result.add(type);
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Supply a default integer range
|
||||
if (ranges.isEmpty() && result.isEmpty()) {
|
||||
ranges = Lists.newArrayList();
|
||||
ranges.add(defaultRange);
|
||||
}
|
||||
|
||||
for (Range<Integer> range : ranges) {
|
||||
for (Integer id : Guava.toSet(range)) {
|
||||
// Deprecated packets
|
||||
if (protocol == null) {
|
||||
if (PacketType.hasLegacy(id)) {
|
||||
result.add(PacketType.findLegacy(id, side));
|
||||
}
|
||||
} else {
|
||||
if (PacketType.hasCurrent(protocol, side, id)) {
|
||||
result.add(PacketType.findCurrent(protocol, side, id));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the last parsed protocol.
|
||||
* @return Last protocol.
|
||||
*/
|
||||
public Protocol getLastProtocol() {
|
||||
return protocol;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the last sender.
|
||||
* @return Last sender.
|
||||
*/
|
||||
public Sender getLastSide() {
|
||||
return side;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a connection sides from a string.
|
||||
* @param text - the possible connection side.
|
||||
* @return The connection side, or NULL if not found.
|
||||
*/
|
||||
public ConnectionSide parseSide(String text) {
|
||||
if (text == null)
|
||||
return null;
|
||||
String candidate = text.toLowerCase();
|
||||
|
||||
// Parse the side gracefully
|
||||
if ("client".startsWith(candidate))
|
||||
return ConnectionSide.CLIENT_SIDE;
|
||||
else if ("server".startsWith(candidate))
|
||||
return ConnectionSide.SERVER_SIDE;
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a protocol from a string.
|
||||
* @param text - the possible protocol.
|
||||
* @return The protocol, or NULL if not found.
|
||||
*/
|
||||
public Protocol parseProtocol(String text) {
|
||||
if (text == null)
|
||||
return null;
|
||||
String candidate = text.toLowerCase();
|
||||
|
||||
if ("handshake".equals(candidate) || "handshaking".equals(candidate))
|
||||
return Protocol.HANDSHAKING;
|
||||
else if ("login".equals(candidate))
|
||||
return Protocol.LOGIN;
|
||||
else if ("play".equals(candidate) || "game".equals(candidate))
|
||||
return Protocol.PLAY;
|
||||
else if ("status".equals(candidate))
|
||||
return Protocol.STATUS;
|
||||
else
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,346 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import com.comphenix.protocol.injector.PacketFilterManager;
|
||||
import com.comphenix.protocol.reflect.FieldAccessException;
|
||||
import com.comphenix.protocol.reflect.IntEnum;
|
||||
|
||||
/**
|
||||
* List of known packet IDs since 1.3.2.
|
||||
* <p>
|
||||
* Deprecated: Use {@link PacketType} instead.
|
||||
* @author Kristian
|
||||
*/
|
||||
@Deprecated
|
||||
public final class Packets {
|
||||
|
||||
/**
|
||||
* The highest possible packet ID. It's unlikely that this value will ever change.
|
||||
*/
|
||||
public static final int MAXIMUM_PACKET_ID = 255;
|
||||
|
||||
/**
|
||||
* The maximum number of unique packet IDs. It's unlikely this will ever change.
|
||||
*/
|
||||
public static final int PACKET_COUNT = 256;
|
||||
|
||||
/**
|
||||
* List of packets sent only by the server.
|
||||
* <p>
|
||||
* Deprecated: Use {@link PacketType} instead.
|
||||
* @author Kristian
|
||||
*/
|
||||
@Deprecated
|
||||
public static final class Server extends IntEnum {
|
||||
/**
|
||||
* The singleton instance. Can also be retrieved from the parent class.
|
||||
*/
|
||||
private static Server INSTANCE = new Server();
|
||||
|
||||
public static final int KEEP_ALIVE = 0;
|
||||
public static final int LOGIN = 1;
|
||||
public static final int CHAT = 3;
|
||||
public static final int UPDATE_TIME = 4;
|
||||
public static final int ENTITY_EQUIPMENT = 5;
|
||||
public static final int SPAWN_POSITION = 6;
|
||||
public static final int UPDATE_HEALTH = 8;
|
||||
public static final int RESPAWN = 9;
|
||||
public static final int FLYING = 10;
|
||||
public static final int PLAYER_POSITION = 11;
|
||||
public static final int PLAYER_LOOK = 12;
|
||||
public static final int PLAYER_LOOK_MOVE = 13;
|
||||
/**
|
||||
* Made bi-directional in 1.4.6.
|
||||
*/
|
||||
public static final int BLOCK_ITEM_SWITCH = 16;
|
||||
public static final int ENTITY_LOCATION_ACTION = 17;
|
||||
public static final int ARM_ANIMATION = 18;
|
||||
public static final int NAMED_ENTITY_SPAWN = 20;
|
||||
/**
|
||||
* Removed in 1.4.6 and replaced with VEHICLE_SPAWN.
|
||||
* @see <a href="http://www.wiki.vg/Protocol_History#2012-12-20">Protocol History - MinecraftCoalition</a>
|
||||
*/
|
||||
@Deprecated()
|
||||
public static final int PICKUP_SPAWN = 21;
|
||||
public static final int COLLECT = 22;
|
||||
public static final int VEHICLE_SPAWN = 23;
|
||||
public static final int MOB_SPAWN = 24;
|
||||
public static final int ENTITY_PAINTING = 25;
|
||||
public static final int ADD_EXP_ORB = 26;
|
||||
public static final int ENTITY_VELOCITY = 28;
|
||||
public static final int DESTROY_ENTITY = 29;
|
||||
public static final int ENTITY = 30;
|
||||
public static final int REL_ENTITY_MOVE = 31;
|
||||
public static final int ENTITY_LOOK = 32;
|
||||
public static final int REL_ENTITY_MOVE_LOOK = 33;
|
||||
public static final int ENTITY_TELEPORT = 34;
|
||||
public static final int ENTITY_HEAD_ROTATION = 35;
|
||||
public static final int ENTITY_STATUS = 38;
|
||||
public static final int ATTACH_ENTITY = 39;
|
||||
|
||||
/**
|
||||
* Sent when an entities DataWatcher is updated.
|
||||
* <p>
|
||||
* Remember to clone the packet if you are modifying it.
|
||||
*/
|
||||
public static final int ENTITY_METADATA = 40;
|
||||
public static final int MOB_EFFECT = 41;
|
||||
public static final int REMOVE_MOB_EFFECT = 42;
|
||||
public static final int SET_EXPERIENCE = 43;
|
||||
public static final int UPDATE_ATTRIBUTES = 44;
|
||||
public static final int MAP_CHUNK = 51;
|
||||
public static final int MULTI_BLOCK_CHANGE = 52;
|
||||
public static final int BLOCK_CHANGE = 53;
|
||||
public static final int PLAY_NOTE_BLOCK = 54;
|
||||
public static final int BLOCK_BREAK_ANIMATION = 55;
|
||||
public static final int MAP_CHUNK_BULK = 56;
|
||||
public static final int EXPLOSION = 60;
|
||||
public static final int WORLD_EVENT = 61;
|
||||
public static final int NAMED_SOUND_EFFECT = 62;
|
||||
public static final int WORLD_PARTICLES = 63;
|
||||
public static final int BED = 70;
|
||||
public static final int WEATHER = 71;
|
||||
public static final int OPEN_WINDOW = 100;
|
||||
public static final int CLOSE_WINDOW = 101;
|
||||
public static final int SET_SLOT = 103;
|
||||
public static final int WINDOW_ITEMS = 104;
|
||||
public static final int CRAFT_PROGRESS_BAR = 105;
|
||||
public static final int TRANSACTION = 106;
|
||||
public static final int SET_CREATIVE_SLOT = 107;
|
||||
public static final int UPDATE_SIGN = 130;
|
||||
public static final int ITEM_DATA = 131;
|
||||
|
||||
/**
|
||||
* Sent the first time a tile entity (chest inventory, etc.) is withing range of the player, or has been updated.
|
||||
* <p>
|
||||
* Remember to clone the packet if you are modifying it.
|
||||
*/
|
||||
public static final int TILE_ENTITY_DATA = 132;
|
||||
public static final int OPEN_TILE_ENTITY = 133;
|
||||
public static final int STATISTIC = 200;
|
||||
public static final int PLAYER_INFO = 201;
|
||||
public static final int ABILITIES = 202;
|
||||
public static final int TAB_COMPLETE = 203;
|
||||
public static final int SCOREBOARD_OBJECTIVE = 206;
|
||||
public static final int UPDATE_SCORE = 207;
|
||||
public static final int DISPLAY_SCOREBOARD = 208;
|
||||
public static final int TEAMS = 209;
|
||||
public static final int CUSTOM_PAYLOAD = 250;
|
||||
public static final int KEY_RESPONSE = 252;
|
||||
public static final int KEY_REQUEST = 253;
|
||||
public static final int KICK_DISCONNECT = 255;
|
||||
|
||||
/**
|
||||
* This packet was introduced in 1.7.2.
|
||||
*/
|
||||
public static final int PING_TIME = 230;
|
||||
|
||||
/**
|
||||
* This packet was introduced in 1.7.2.
|
||||
*/
|
||||
public static final int LOGIN_SUCCESS = 232;
|
||||
|
||||
/**
|
||||
* A registry that parses between names and packet IDs.
|
||||
* @return The current server registry.
|
||||
*/
|
||||
public static Server getRegistry() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the given packet is a valid server packet in the current version of Minecraft.
|
||||
* <p>
|
||||
* Use {@link PacketType#isSupported()} instead.
|
||||
* @param packetID - the packet to test.
|
||||
* @return TRUE if this packet is supported, FALSE otherwise.
|
||||
* @throws FieldAccessException If we're unable to retrieve the server packet data from Minecraft.
|
||||
*/
|
||||
@Deprecated
|
||||
public static boolean isSupported(int packetID) throws FieldAccessException {
|
||||
return PacketFilterManager.getServerPackets().contains(packetID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve every client packet the current version of Minecraft is aware of.
|
||||
* @return Every supported server packet.
|
||||
* @throws FieldAccessException If we're unable to retrieve the server packet data from Minecraft.
|
||||
*/
|
||||
@Deprecated
|
||||
public static Set<Integer> getSupported() throws FieldAccessException {
|
||||
return PacketFilterManager.getServerPackets();
|
||||
}
|
||||
|
||||
// We only allow a single instance of this class
|
||||
private Server() {
|
||||
super();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* List of packets sent by the client.
|
||||
* <p>
|
||||
* Deprecated: Use {@link PacketType} instead.
|
||||
* @author Kristian
|
||||
*/
|
||||
@Deprecated
|
||||
public static class Client extends IntEnum {
|
||||
/**
|
||||
* The singleton instance. Can also be retrieved from the parent class.
|
||||
*/
|
||||
private static Client INSTANCE = new Client();
|
||||
|
||||
public static final int KEEP_ALIVE = 0;
|
||||
public static final int LOGIN = 1;
|
||||
public static final int HANDSHAKE = 2;
|
||||
public static final int CHAT = 3;
|
||||
public static final int USE_ENTITY = 7;
|
||||
|
||||
/**
|
||||
* Since 1.3.1, the client no longer sends a respawn packet. Moved to CLIENT_COMMAND.
|
||||
*/
|
||||
@Deprecated
|
||||
public static final int RESPAWN = 9;
|
||||
|
||||
public static final int FLYING = 10;
|
||||
public static final int PLAYER_POSITION = 11;
|
||||
public static final int PLAYER_LOOK = 12;
|
||||
public static final int PLAYER_LOOK_MOVE = 13;
|
||||
public static final int BLOCK_DIG = 14;
|
||||
public static final int PLACE = 15;
|
||||
public static final int BLOCK_ITEM_SWITCH = 16;
|
||||
public static final int ARM_ANIMATION = 18;
|
||||
public static final int ENTITY_ACTION = 19;
|
||||
public static final int PLAYER_INPUT = 27;
|
||||
public static final int CLOSE_WINDOW = 101;
|
||||
public static final int WINDOW_CLICK = 102;
|
||||
public static final int TRANSACTION = 106;
|
||||
public static final int SET_CREATIVE_SLOT = 107;
|
||||
public static final int BUTTON_CLICK = 108;
|
||||
public static final int UPDATE_SIGN = 130;
|
||||
public static final int ABILITIES = 202;
|
||||
public static final int TAB_COMPLETE = 203;
|
||||
public static final int LOCALE_AND_VIEW_DISTANCE = 204;
|
||||
public static final int CLIENT_COMMAND = 205;
|
||||
public static final int CUSTOM_PAYLOAD = 250;
|
||||
public static final int KEY_RESPONSE = 252;
|
||||
public static final int GET_INFO = 254;
|
||||
public static final int KICK_DISCONNECT = 255;
|
||||
|
||||
/**
|
||||
* This packet was introduced in 1.7.2.
|
||||
*/
|
||||
public static final int PING_TIME = 230;
|
||||
|
||||
/**
|
||||
* This packet was introduced in 1.7.2.
|
||||
*/
|
||||
public static final int LOGIN_START = 231;
|
||||
|
||||
/**
|
||||
* A registry that parses between names and packet IDs.
|
||||
* @return The current client registry.
|
||||
*/
|
||||
public static Client getRegistry() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the given packet is a valid client packet in the current version of Minecraft.
|
||||
* @param packetID - the packet to test.
|
||||
* @return TRUE if this packet is supported, FALSE otherwise.
|
||||
* @throws FieldAccessException If we're unable to retrieve the client packet data from Minecraft.
|
||||
*/
|
||||
public static boolean isSupported(int packetID) throws FieldAccessException {
|
||||
return PacketFilterManager.getClientPackets().contains(packetID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve every client packet the current version of Minecraft is aware of.
|
||||
* @return Every supported client packet.
|
||||
* @throws FieldAccessException If we're unable to retrieve the client packet data from Minecraft.
|
||||
*/
|
||||
public static Set<Integer> getSupported() throws FieldAccessException {
|
||||
return PacketFilterManager.getClientPackets();
|
||||
}
|
||||
|
||||
// Like above
|
||||
private Client() {
|
||||
super();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A registry that parses between names and packet IDs.
|
||||
* <p>
|
||||
* Deprecated: Use {@link PacketType} instead.
|
||||
* @return The current client registry.
|
||||
*/
|
||||
@Deprecated
|
||||
public static Server getServerRegistry() {
|
||||
return Server.getRegistry();
|
||||
}
|
||||
|
||||
/**
|
||||
* A registry that parses between names and packet IDs.
|
||||
* <p>
|
||||
* Deprecated: Use {@link PacketType} instead.
|
||||
* @return The current server registry.
|
||||
*/
|
||||
@Deprecated
|
||||
public static Client getClientRegistry() {
|
||||
return Client.INSTANCE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a packet by name. Must be capitalized and use underscores.
|
||||
* <p>
|
||||
* Deprecated: Use {@link PacketType} instead.
|
||||
* @param name - name of packet to find.
|
||||
* @return The packet ID found.
|
||||
*/
|
||||
@Deprecated
|
||||
public static int valueOf(String name) {
|
||||
Integer serverAttempt = Server.INSTANCE.valueOf(name);
|
||||
|
||||
if (serverAttempt != null)
|
||||
return serverAttempt;
|
||||
else
|
||||
return Client.INSTANCE.valueOf(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the name of a packet.
|
||||
* <p>
|
||||
* Deprecated: Use {@link PacketType} instead.
|
||||
* @param packetID - packet to retrieve name.
|
||||
* @return The name, or NULL if unable to find such a packet.
|
||||
*/
|
||||
@Deprecated
|
||||
public static String getDeclaredName(int packetID) {
|
||||
String serverAttempt = Server.INSTANCE.getDeclaredName(packetID);
|
||||
|
||||
if (serverAttempt != null)
|
||||
return serverAttempt;
|
||||
else
|
||||
return Client.INSTANCE.getDeclaredName(packetID);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,500 @@
|
||||
/**
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
package com.comphenix.protocol;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.bukkit.configuration.Configuration;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks;
|
||||
import com.google.common.base.Charsets;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.io.Files;
|
||||
|
||||
/**
|
||||
* Represents the configuration of ProtocolLib.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public class ProtocolConfig {
|
||||
private static final String LAST_UPDATE_FILE = "lastupdate";
|
||||
|
||||
private static final String SECTION_GLOBAL = "global";
|
||||
private static final String SECTION_AUTOUPDATER = "auto updater";
|
||||
|
||||
private static final String METRICS_ENABLED = "metrics";
|
||||
|
||||
private static final String IGNORE_VERSION_CHECK = "ignore version check";
|
||||
private static final String BACKGROUND_COMPILER_ENABLED = "background compiler";
|
||||
|
||||
private static final String DEBUG_MODE_ENABLED = "debug";
|
||||
private static final String DETAILED_ERROR = "detailed error";
|
||||
private static final String INJECTION_METHOD = "injection method";
|
||||
|
||||
private static final String SCRIPT_ENGINE_NAME = "script engine";
|
||||
private static final String SUPPRESSED_REPORTS = "suppressed reports";
|
||||
|
||||
private static final String UPDATER_NOTIFY = "notify";
|
||||
private static final String UPDATER_DOWNLAD = "download";
|
||||
private static final String UPDATER_DELAY = "delay";
|
||||
|
||||
// Defaults
|
||||
private static final long DEFAULT_UPDATER_DELAY = 43200;
|
||||
|
||||
private Plugin plugin;
|
||||
private Configuration config;
|
||||
private boolean loadingSections;
|
||||
|
||||
private ConfigurationSection global;
|
||||
private ConfigurationSection updater;
|
||||
|
||||
// Last update time
|
||||
private long lastUpdateTime;
|
||||
private boolean configChanged;
|
||||
private boolean valuesChanged;
|
||||
|
||||
// Modifications
|
||||
private int modCount;
|
||||
|
||||
public ProtocolConfig(Plugin plugin) {
|
||||
this.plugin = plugin;
|
||||
reloadConfig();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reload configuration file.
|
||||
*/
|
||||
public void reloadConfig() {
|
||||
// Reset
|
||||
configChanged = false;
|
||||
valuesChanged = false;
|
||||
modCount++;
|
||||
|
||||
this.config = plugin.getConfig();
|
||||
this.lastUpdateTime = loadLastUpdate();
|
||||
loadSections(!loadingSections);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the last update time stamp from the file system.
|
||||
*
|
||||
* @return Last update time stamp.
|
||||
*/
|
||||
private long loadLastUpdate() {
|
||||
File dataFile = getLastUpdateFile();
|
||||
|
||||
if (dataFile.exists()) {
|
||||
try {
|
||||
return Long.parseLong(Files.toString(dataFile, Charsets.UTF_8));
|
||||
} catch (NumberFormatException e) {
|
||||
plugin.getLogger().warning("Cannot parse " + dataFile + " as a number.");
|
||||
} catch (IOException e) {
|
||||
plugin.getLogger().warning("Cannot read " + dataFile);
|
||||
}
|
||||
}
|
||||
// Default last update
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the given time stamp.
|
||||
*
|
||||
* @param value - time stamp to store.
|
||||
*/
|
||||
private void saveLastUpdate(long value) {
|
||||
File dataFile = getLastUpdateFile();
|
||||
|
||||
// The data folder must exist
|
||||
dataFile.getParentFile().mkdirs();
|
||||
|
||||
if (dataFile.exists())
|
||||
dataFile.delete();
|
||||
|
||||
try {
|
||||
Files.write(Long.toString(value), dataFile, Charsets.UTF_8);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Cannot write " + dataFile, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the file that is used to store the update time stamp.
|
||||
*
|
||||
* @return File storing the update time stamp.
|
||||
*/
|
||||
private File getLastUpdateFile() {
|
||||
return new File(plugin.getDataFolder(), LAST_UPDATE_FILE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load data sections.
|
||||
*
|
||||
* @param copyDefaults - whether or not to copy configuration defaults.
|
||||
*/
|
||||
private void loadSections(boolean copyDefaults) {
|
||||
if (config != null) {
|
||||
global = config.getConfigurationSection(SECTION_GLOBAL);
|
||||
}
|
||||
if (global != null) {
|
||||
updater = global.getConfigurationSection(SECTION_AUTOUPDATER);
|
||||
}
|
||||
|
||||
// Automatically copy defaults
|
||||
if (copyDefaults && (!getFile().exists() || global == null || updater == null)) {
|
||||
loadingSections = true;
|
||||
|
||||
if (config != null)
|
||||
config.options().copyDefaults(true);
|
||||
plugin.saveDefaultConfig();
|
||||
plugin.reloadConfig();
|
||||
loadingSections = false;
|
||||
|
||||
// Inform the user
|
||||
plugin.getLogger().info("Created default configuration.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a particular configuration key value pair.
|
||||
*
|
||||
* @param section - the configuration root.
|
||||
* @param path - the path to the key.
|
||||
* @param value - the value to set.
|
||||
*/
|
||||
private void setConfig(ConfigurationSection section, String path, Object value) {
|
||||
configChanged = true;
|
||||
section.set(path, value);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> T getGlobalValue(String path, T def) {
|
||||
try {
|
||||
return (T) global.get(path, def);
|
||||
} catch (Throwable ex) {
|
||||
return def;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> T getUpdaterValue(String path, T def) {
|
||||
try {
|
||||
return (T) updater.get(path, def);
|
||||
} catch (Throwable ex) {
|
||||
return def;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a reference to the configuration file.
|
||||
*
|
||||
* @return Configuration file on disk.
|
||||
*/
|
||||
public File getFile() {
|
||||
return new File(plugin.getDataFolder(), "config.yml");
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if detailed error reporting is enabled. Default FALSE.
|
||||
*
|
||||
* @return TRUE if it is enabled, FALSE otherwise.
|
||||
*/
|
||||
public boolean isDetailedErrorReporting() {
|
||||
return getGlobalValue(DETAILED_ERROR, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether or not detailed error reporting is enabled.
|
||||
*
|
||||
* @param value - TRUE if it is enabled, FALSE otherwise.
|
||||
*/
|
||||
public void setDetailedErrorReporting(boolean value) {
|
||||
global.set(DETAILED_ERROR, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve whether or not ProtocolLib should determine if a new version has been released.
|
||||
*
|
||||
* @return TRUE if it should do this automatically, FALSE otherwise.
|
||||
*/
|
||||
public boolean isAutoNotify() {
|
||||
return getUpdaterValue(UPDATER_NOTIFY, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether or not ProtocolLib should determine if a new version has been released.
|
||||
*
|
||||
* @param value - TRUE to do this automatically, FALSE otherwise.
|
||||
*/
|
||||
public void setAutoNotify(boolean value) {
|
||||
setConfig(updater, UPDATER_NOTIFY, value);
|
||||
modCount++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve whether or not ProtocolLib should automatically download the new version.
|
||||
*
|
||||
* @return TRUE if it should, FALSE otherwise.
|
||||
*/
|
||||
public boolean isAutoDownload() {
|
||||
return updater != null && getUpdaterValue(UPDATER_DOWNLAD, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether or not ProtocolLib should automatically download the new version.
|
||||
*
|
||||
* @param value - TRUE if it should. FALSE otherwise.
|
||||
*/
|
||||
public void setAutoDownload(boolean value) {
|
||||
setConfig(updater, UPDATER_DOWNLAD, value);
|
||||
modCount++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether or not debug mode is enabled.
|
||||
* <p>
|
||||
* This grants access to the filter command.
|
||||
*
|
||||
* @return TRUE if it is, FALSE otherwise.
|
||||
*/
|
||||
public boolean isDebug() {
|
||||
return getGlobalValue(DEBUG_MODE_ENABLED, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether or not debug mode is enabled.
|
||||
*
|
||||
* @param value - TRUE if it is enabled, FALSE otherwise.
|
||||
*/
|
||||
public void setDebug(boolean value) {
|
||||
setConfig(global, DEBUG_MODE_ENABLED, value);
|
||||
modCount++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve an immutable list of every suppressed report type.
|
||||
*
|
||||
* @return Every suppressed report type.
|
||||
*/
|
||||
public ImmutableList<String> getSuppressedReports() {
|
||||
return ImmutableList.copyOf(getGlobalValue(SUPPRESSED_REPORTS, new ArrayList<String>()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the list of suppressed report types,
|
||||
*
|
||||
* @param reports - suppressed report types.
|
||||
*/
|
||||
public void setSuppressedReports(List<String> reports) {
|
||||
global.set(SUPPRESSED_REPORTS, Lists.newArrayList(reports));
|
||||
modCount++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the amount of time to wait until checking for a new update.
|
||||
*
|
||||
* @return The amount of time to wait.
|
||||
*/
|
||||
public long getAutoDelay() {
|
||||
// Note that the delay must be greater than 59 seconds
|
||||
return Math.max(getUpdaterValue(UPDATER_DELAY, 0), DEFAULT_UPDATER_DELAY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the amount of time to wait until checking for a new update.
|
||||
* <p>
|
||||
* This time must be greater than 59 seconds.
|
||||
*
|
||||
* @param delaySeconds - the amount of time to wait.
|
||||
*/
|
||||
public void setAutoDelay(long delaySeconds) {
|
||||
// Silently fix the delay
|
||||
if (delaySeconds < DEFAULT_UPDATER_DELAY)
|
||||
delaySeconds = DEFAULT_UPDATER_DELAY;
|
||||
setConfig(updater, UPDATER_DELAY, delaySeconds);
|
||||
modCount++;
|
||||
}
|
||||
|
||||
/**
|
||||
* The version of Minecraft to ignore the built-in safety feature.
|
||||
*
|
||||
* @return The version to ignore ProtocolLib's satefy.
|
||||
*/
|
||||
public String getIgnoreVersionCheck() {
|
||||
return getGlobalValue(IGNORE_VERSION_CHECK, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets under which version of Minecraft the version safety feature will be ignored.
|
||||
* <p>
|
||||
* This is useful if a server operator has tested and verified that a version of ProtocolLib works, but doesn't want or can't upgrade to a newer version.
|
||||
*
|
||||
* @param ignoreVersion - the version of Minecraft where the satefy will be disabled.
|
||||
*/
|
||||
public void setIgnoreVersionCheck(String ignoreVersion) {
|
||||
setConfig(global, IGNORE_VERSION_CHECK, ignoreVersion);
|
||||
modCount++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve whether or not metrics is enabled.
|
||||
*
|
||||
* @return TRUE if metrics is enabled, FALSE otherwise.
|
||||
*/
|
||||
public boolean isMetricsEnabled() {
|
||||
return getGlobalValue(METRICS_ENABLED, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether or not metrics is enabled.
|
||||
* <p>
|
||||
* This setting will take effect next time ProtocolLib is started.
|
||||
*
|
||||
* @param enabled - whether or not metrics is enabled.
|
||||
*/
|
||||
public void setMetricsEnabled(boolean enabled) {
|
||||
setConfig(global, METRICS_ENABLED, enabled);
|
||||
modCount++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve whether or not the background compiler for structure modifiers is enabled or not.
|
||||
*
|
||||
* @return TRUE if it is enabled, FALSE otherwise.
|
||||
*/
|
||||
public boolean isBackgroundCompilerEnabled() {
|
||||
return getGlobalValue(BACKGROUND_COMPILER_ENABLED, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether or not the background compiler for structure modifiers is enabled or not.
|
||||
* <p>
|
||||
* This setting will take effect next time ProtocolLib is started.
|
||||
*
|
||||
* @param enabled - TRUE if is enabled/running, FALSE otherwise.
|
||||
*/
|
||||
public void setBackgroundCompilerEnabled(boolean enabled) {
|
||||
setConfig(global, BACKGROUND_COMPILER_ENABLED, enabled);
|
||||
modCount++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the last time we updated, in seconds since 1970.01.01 00:00.
|
||||
*
|
||||
* @return Last update time.
|
||||
*/
|
||||
public long getAutoLastTime() {
|
||||
return lastUpdateTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the last time we updated, in seconds since 1970.01.01 00:00.
|
||||
* <p>
|
||||
* Note that this is not considered to modify the configuration, so the modification count will not be incremented.
|
||||
*
|
||||
* @param lastTimeSeconds - new last update time.
|
||||
*/
|
||||
public void setAutoLastTime(long lastTimeSeconds) {
|
||||
this.valuesChanged = true;
|
||||
this.lastUpdateTime = lastTimeSeconds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the unique name of the script engine to use for filtering.
|
||||
*
|
||||
* @return Script engine to use.
|
||||
*/
|
||||
public String getScriptEngineName() {
|
||||
return getGlobalValue(SCRIPT_ENGINE_NAME, "JavaScript");
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the unique name of the script engine to use for filtering.
|
||||
* <p>
|
||||
* This setting will take effect next time ProtocolLib is started.
|
||||
*
|
||||
* @param name - name of the script engine to use.
|
||||
*/
|
||||
public void setScriptEngineName(String name) {
|
||||
setConfig(global, SCRIPT_ENGINE_NAME, name);
|
||||
modCount++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the default injection method.
|
||||
*
|
||||
* @return Default method.
|
||||
*/
|
||||
public PlayerInjectHooks getDefaultMethod() {
|
||||
return PlayerInjectHooks.NETWORK_SERVER_OBJECT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the injection method that has been set in the configuration, or use a default value.
|
||||
*
|
||||
* @return Injection method to use.
|
||||
* @throws IllegalArgumentException If the configuration option is malformed.
|
||||
*/
|
||||
public PlayerInjectHooks getInjectionMethod() throws IllegalArgumentException {
|
||||
String text = global.getString(INJECTION_METHOD);
|
||||
|
||||
// Default hook if nothing has been set
|
||||
PlayerInjectHooks hook = getDefaultMethod();
|
||||
|
||||
if (text != null)
|
||||
hook = PlayerInjectHooks.valueOf(text.toUpperCase().replace(" ", "_"));
|
||||
return hook;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the starting injection method to use.
|
||||
*
|
||||
* @return Injection method.
|
||||
*/
|
||||
public void setInjectionMethod(PlayerInjectHooks hook) {
|
||||
setConfig(global, INJECTION_METHOD, hook.name());
|
||||
modCount++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the number of modifications made to this configuration.
|
||||
*
|
||||
* @return The number of modifications.
|
||||
*/
|
||||
public int getModificationCount() {
|
||||
return modCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the current configuration file.
|
||||
*/
|
||||
public void saveAll() {
|
||||
if (valuesChanged)
|
||||
saveLastUpdate(lastUpdateTime);
|
||||
if (configChanged)
|
||||
plugin.saveConfig();
|
||||
|
||||
// And we're done
|
||||
valuesChanged = false;
|
||||
configChanged = false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,761 @@
|
||||
/**
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
package com.comphenix.protocol;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Handler;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.LogRecord;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.PluginCommand;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.bukkit.plugin.PluginManager;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import com.comphenix.executors.BukkitExecutors;
|
||||
import com.comphenix.protocol.async.AsyncFilterManager;
|
||||
import com.comphenix.protocol.error.BasicErrorReporter;
|
||||
import com.comphenix.protocol.error.DelegatedErrorReporter;
|
||||
import com.comphenix.protocol.error.DetailedErrorReporter;
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.error.Report;
|
||||
import com.comphenix.protocol.error.ReportType;
|
||||
import com.comphenix.protocol.injector.DelayedSingleTask;
|
||||
import com.comphenix.protocol.injector.InternalManager;
|
||||
import com.comphenix.protocol.injector.PacketFilterManager;
|
||||
import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks;
|
||||
import com.comphenix.protocol.metrics.Statistics;
|
||||
import com.comphenix.protocol.reflect.compiler.BackgroundCompiler;
|
||||
import com.comphenix.protocol.updater.Updater;
|
||||
import com.comphenix.protocol.updater.Updater.UpdateType;
|
||||
import com.comphenix.protocol.utility.ChatExtensions;
|
||||
import com.comphenix.protocol.utility.EnhancerFactory;
|
||||
import com.comphenix.protocol.utility.MinecraftVersion;
|
||||
import com.google.common.base.Splitter;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.common.util.concurrent.ListeningScheduledExecutorService;
|
||||
|
||||
/**
|
||||
* The main entry point for ProtocolLib.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public class ProtocolLibrary extends JavaPlugin {
|
||||
// Every possible error or warning report type
|
||||
public static final ReportType REPORT_CANNOT_LOAD_CONFIG = new ReportType("Cannot load configuration");
|
||||
public static final ReportType REPORT_CANNOT_DELETE_CONFIG = new ReportType("Cannot delete old ProtocolLib configuration.");
|
||||
public static final ReportType REPORT_CANNOT_PARSE_INJECTION_METHOD = new ReportType("Cannot parse injection method. Using default.");
|
||||
|
||||
public static final ReportType REPORT_PLUGIN_LOAD_ERROR = new ReportType("Cannot load ProtocolLib.");
|
||||
public static final ReportType REPORT_PLUGIN_ENABLE_ERROR = new ReportType("Cannot enable ProtocolLib.");
|
||||
|
||||
public static final ReportType REPORT_METRICS_IO_ERROR = new ReportType("Unable to enable metrics due to network problems.");
|
||||
public static final ReportType REPORT_METRICS_GENERIC_ERROR = new ReportType("Unable to enable metrics due to network problems.");
|
||||
|
||||
public static final ReportType REPORT_CANNOT_PARSE_MINECRAFT_VERSION = new ReportType("Unable to retrieve current Minecraft version. Assuming %s");
|
||||
public static final ReportType REPORT_CANNOT_DETECT_CONFLICTING_PLUGINS = new ReportType("Unable to detect conflicting plugin versions.");
|
||||
public static final ReportType REPORT_CANNOT_REGISTER_COMMAND = new ReportType("Cannot register command %s: %s");
|
||||
|
||||
public static final ReportType REPORT_CANNOT_CREATE_TIMEOUT_TASK = new ReportType("Unable to create packet timeout task.");
|
||||
public static final ReportType REPORT_CANNOT_UPDATE_PLUGIN = new ReportType("Cannot perform automatic updates.");
|
||||
|
||||
/**
|
||||
* The minimum version ProtocolLib has been tested with.
|
||||
*/
|
||||
public static final String MINIMUM_MINECRAFT_VERSION = "1.0";
|
||||
|
||||
/**
|
||||
* The maximum version ProtocolLib has been tested with,
|
||||
*/
|
||||
public static final String MAXIMUM_MINECRAFT_VERSION = "1.8.8";
|
||||
|
||||
/**
|
||||
* The date (with ISO 8601 or YYYY-MM-DD) when the most recent version (1.8.8) was released.
|
||||
*/
|
||||
public static final String MINECRAFT_LAST_RELEASE_DATE = "2015-07-27";
|
||||
|
||||
// Update information
|
||||
static final String BUKKIT_DEV_SLUG = "protocollib";
|
||||
static final int BUKKIT_DEV_ID = 45564;
|
||||
|
||||
// Different commands
|
||||
private enum ProtocolCommand {
|
||||
FILTER,
|
||||
PACKET,
|
||||
PROTOCOL
|
||||
}
|
||||
|
||||
/**
|
||||
* The number of milliseconds per second.
|
||||
*/
|
||||
static final long MILLI_PER_SECOND = 1000;
|
||||
|
||||
private static final String PERMISSION_INFO = "protocol.info";
|
||||
|
||||
// There should only be one protocol manager, so we'll make it static
|
||||
private static InternalManager protocolManager;
|
||||
|
||||
// Error reporter
|
||||
private static ErrorReporter reporter = new BasicErrorReporter();
|
||||
|
||||
// Strongly typed configuration
|
||||
private static ProtocolConfig config;
|
||||
|
||||
// Metrics and statistics
|
||||
private Statistics statistics;
|
||||
|
||||
// Executors
|
||||
private static ListeningScheduledExecutorService executorAsync;
|
||||
private static ListeningScheduledExecutorService executorSync;
|
||||
|
||||
// Structure compiler
|
||||
private BackgroundCompiler backgroundCompiler;
|
||||
|
||||
// Used to clean up server packets that have expired, but mostly required to simulate
|
||||
// recieving client packets.
|
||||
private int packetTask = -1;
|
||||
private int tickCounter = 0;
|
||||
private static final int ASYNC_MANAGER_DELAY = 1;
|
||||
|
||||
// Used to unhook players after a delay
|
||||
private DelayedSingleTask unhookTask;
|
||||
|
||||
// Settings/options
|
||||
private int configExpectedMod = -1;
|
||||
|
||||
// Updater
|
||||
private Updater updater;
|
||||
private static boolean UPDATES_DISABLED;
|
||||
|
||||
// Logger
|
||||
private static Logger logger;
|
||||
private Handler redirectHandler;
|
||||
|
||||
// Commands
|
||||
private CommandProtocol commandProtocol;
|
||||
private CommandPacket commandPacket;
|
||||
private CommandFilter commandFilter;
|
||||
|
||||
// Whether or not disable is not needed
|
||||
private boolean skipDisable;
|
||||
|
||||
@Override
|
||||
public void onLoad() {
|
||||
// Load configuration
|
||||
logger = getLoggerSafely();
|
||||
Application.registerPrimaryThread();
|
||||
|
||||
// Initialize enhancer factory
|
||||
EnhancerFactory.getInstance().setClassLoader(getClassLoader());
|
||||
|
||||
// Initialize executors
|
||||
executorAsync = BukkitExecutors.newAsynchronous(this);
|
||||
executorSync = BukkitExecutors.newSynchronous(this);
|
||||
|
||||
// Add global parameters
|
||||
DetailedErrorReporter detailedReporter = new DetailedErrorReporter(this);
|
||||
reporter = getFilteredReporter(detailedReporter);
|
||||
|
||||
// Configuration
|
||||
saveDefaultConfig();
|
||||
reloadConfig();
|
||||
|
||||
try {
|
||||
config = new ProtocolConfig(this);
|
||||
} catch (Exception e) {
|
||||
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_LOAD_CONFIG).error(e));
|
||||
|
||||
// Load it again
|
||||
if (deleteConfig()) {
|
||||
config = new ProtocolConfig(this);
|
||||
} else {
|
||||
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_DELETE_CONFIG));
|
||||
}
|
||||
}
|
||||
|
||||
// Print the state of the debug mode
|
||||
if (config.isDebug()) {
|
||||
logger.warning("Debug mode is enabled!");
|
||||
}
|
||||
// And the state of the error reporter
|
||||
if (config.isDetailedErrorReporting()) {
|
||||
detailedReporter.setDetailedReporting(true);
|
||||
logger.warning("Detailed error reporting enabled!");
|
||||
}
|
||||
|
||||
try {
|
||||
// Check for other versions
|
||||
checkConflictingVersions();
|
||||
|
||||
// Handle unexpected Minecraft versions
|
||||
MinecraftVersion version = verifyMinecraftVersion();
|
||||
|
||||
// Set updater - this will not perform any update automatically
|
||||
updater = Updater.create(this, BUKKIT_DEV_ID, getFile(), UpdateType.NO_DOWNLOAD, true);
|
||||
|
||||
unhookTask = new DelayedSingleTask(this);
|
||||
protocolManager = PacketFilterManager.newBuilder()
|
||||
.classLoader(getClassLoader())
|
||||
.server(getServer())
|
||||
.library(this)
|
||||
.minecraftVersion(version)
|
||||
.unhookTask(unhookTask)
|
||||
.reporter(reporter)
|
||||
.build();
|
||||
|
||||
// Setup error reporter
|
||||
detailedReporter.addGlobalParameter("manager", protocolManager);
|
||||
|
||||
// Update injection hook
|
||||
try {
|
||||
PlayerInjectHooks hook = config.getInjectionMethod();
|
||||
|
||||
// Only update the hook if it's different
|
||||
if (!protocolManager.getPlayerHook().equals(hook)) {
|
||||
logger.info("Changing player hook from " + protocolManager.getPlayerHook() + " to " + hook);
|
||||
protocolManager.setPlayerHook(hook);
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
reporter.reportWarning(config, Report.newBuilder(REPORT_CANNOT_PARSE_INJECTION_METHOD).error(e));
|
||||
}
|
||||
|
||||
// Send logging information to player listeners too
|
||||
initializeCommands();
|
||||
setupBroadcastUsers(PERMISSION_INFO);
|
||||
|
||||
} catch (OutOfMemoryError e) {
|
||||
throw e;
|
||||
} catch (ThreadDeath e) {
|
||||
throw e;
|
||||
} catch (Throwable e) {
|
||||
reporter.reportDetailed(this, Report.newBuilder(REPORT_PLUGIN_LOAD_ERROR).error(e).callerParam(protocolManager));
|
||||
disablePlugin();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize all command handlers.
|
||||
*/
|
||||
private void initializeCommands() {
|
||||
// Initialize command handlers
|
||||
for (ProtocolCommand command : ProtocolCommand.values()) {
|
||||
try {
|
||||
switch (command) {
|
||||
case PROTOCOL:
|
||||
commandProtocol = new CommandProtocol(reporter, this, updater, config);
|
||||
break;
|
||||
case FILTER:
|
||||
commandFilter = new CommandFilter(reporter, this, config);
|
||||
break;
|
||||
case PACKET:
|
||||
commandPacket = new CommandPacket(reporter, this, logger, commandFilter, protocolManager);
|
||||
break;
|
||||
}
|
||||
} catch (OutOfMemoryError e) {
|
||||
throw e;
|
||||
} catch (ThreadDeath e) {
|
||||
throw e;
|
||||
} catch (LinkageError e) {
|
||||
logger.warning("Failed to register command " + command.name() + ": " + e);
|
||||
} catch (Throwable e) {
|
||||
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_REGISTER_COMMAND)
|
||||
.messageParam(command.name(), e.getMessage()).error(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a error reporter that may be filtered by the configuration.
|
||||
* @return The new default error reporter.
|
||||
*/
|
||||
private ErrorReporter getFilteredReporter(ErrorReporter reporter) {
|
||||
return new DelegatedErrorReporter(reporter) {
|
||||
private int lastModCount = -1;
|
||||
private Set<String> reports = Sets.newHashSet();
|
||||
|
||||
@Override
|
||||
protected Report filterReport(Object sender, Report report, boolean detailed) {
|
||||
try {
|
||||
String canonicalName = ReportType.getReportName(sender, report.getType());
|
||||
String reportName = Iterables.getLast(Splitter.on("#").split(canonicalName)).toUpperCase();
|
||||
|
||||
if (config != null && config.getModificationCount() != lastModCount) {
|
||||
// Update our cached set again
|
||||
reports = Sets.newHashSet(config.getSuppressedReports());
|
||||
lastModCount = config.getModificationCount();
|
||||
}
|
||||
|
||||
// Cancel reports either on the full canonical name, or just the report name
|
||||
if (reports.contains(canonicalName) || reports.contains(reportName))
|
||||
return null;
|
||||
|
||||
} catch (Exception e) {
|
||||
// Only report this with a minor message
|
||||
logger.warning("Error filtering reports: " + e.toString());
|
||||
}
|
||||
// Don't filter anything
|
||||
return report;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private boolean deleteConfig() {
|
||||
return config.getFile().delete();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reloadConfig() {
|
||||
super.reloadConfig();
|
||||
|
||||
// Reload configuration
|
||||
if (config != null) {
|
||||
config.reloadConfig();
|
||||
}
|
||||
}
|
||||
|
||||
private void setupBroadcastUsers(final String permission) {
|
||||
// Guard against multiple calls
|
||||
if (redirectHandler != null)
|
||||
return;
|
||||
|
||||
// Broadcast information to every user too
|
||||
redirectHandler = new Handler() {
|
||||
@Override
|
||||
public void publish(LogRecord record) {
|
||||
// Only display warnings and above
|
||||
if (record.getLevel().intValue() >= Level.WARNING.intValue()) {
|
||||
commandPacket.broadcastMessageSilently(record.getMessage(), permission);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() {
|
||||
// Not needed.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws SecurityException {
|
||||
// Do nothing.
|
||||
}
|
||||
};
|
||||
|
||||
logger.addHandler(redirectHandler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
try {
|
||||
Server server = getServer();
|
||||
PluginManager manager = server.getPluginManager();
|
||||
|
||||
// Don't do anything else!
|
||||
if (manager == null)
|
||||
return;
|
||||
|
||||
// Silly plugin reloaders!
|
||||
if (protocolManager == null) {
|
||||
Logger directLogging = Logger.getLogger("Minecraft");
|
||||
String[] message = new String[] {
|
||||
" ProtocolLib does not support plugin reloaders! ", " Please use the built-in reload command! "
|
||||
};
|
||||
|
||||
// Print as severe
|
||||
for (String line : ChatExtensions.toFlowerBox(message, "*", 3, 1)) {
|
||||
directLogging.severe(line);
|
||||
}
|
||||
|
||||
disablePlugin();
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for incompatible plugins
|
||||
checkForIncompatibility(manager);
|
||||
|
||||
// Initialize background compiler
|
||||
if (backgroundCompiler == null && config.isBackgroundCompilerEnabled()) {
|
||||
backgroundCompiler = new BackgroundCompiler(getClassLoader(), reporter);
|
||||
BackgroundCompiler.setInstance(backgroundCompiler);
|
||||
|
||||
logger.info("Started structure compiler thread.");
|
||||
} else {
|
||||
logger.info("Structure compiler thread has been disabled.");
|
||||
}
|
||||
|
||||
// Set up command handlers
|
||||
registerCommand(CommandProtocol.NAME, commandProtocol);
|
||||
registerCommand(CommandPacket.NAME, commandPacket);
|
||||
registerCommand(CommandFilter.NAME, commandFilter);
|
||||
|
||||
// Player login and logout events
|
||||
protocolManager.registerEvents(manager, this);
|
||||
|
||||
// Worker that ensures that async packets are eventually sent
|
||||
// It also performs the update check.
|
||||
createPacketTask(server);
|
||||
} catch (OutOfMemoryError e) {
|
||||
throw e;
|
||||
} catch (ThreadDeath e) {
|
||||
throw e;
|
||||
} catch (Throwable e) {
|
||||
reporter.reportDetailed(this, Report.newBuilder(REPORT_PLUGIN_ENABLE_ERROR).error(e));
|
||||
disablePlugin();
|
||||
return;
|
||||
}
|
||||
|
||||
// Try to enable statistics
|
||||
try {
|
||||
if (config.isMetricsEnabled()) {
|
||||
statistics = new Statistics(this);
|
||||
}
|
||||
} catch (OutOfMemoryError e) {
|
||||
throw e;
|
||||
} catch (ThreadDeath e) {
|
||||
throw e;
|
||||
} catch (IOException e) {
|
||||
reporter.reportDetailed(this, Report.newBuilder(REPORT_METRICS_IO_ERROR).error(e).callerParam(statistics));
|
||||
} catch (Throwable e) {
|
||||
reporter.reportDetailed(this, Report.newBuilder(REPORT_METRICS_GENERIC_ERROR).error(e).callerParam(statistics));
|
||||
}
|
||||
}
|
||||
|
||||
// Plugin authors: Notify me to remove these
|
||||
public static List<String> INCOMPATIBLE = Arrays.asList("TagAPI");
|
||||
|
||||
private void checkForIncompatibility(PluginManager manager) {
|
||||
for (String plugin : INCOMPATIBLE) {
|
||||
if (manager.getPlugin(plugin) != null) {
|
||||
// Special case for TagAPI and iTag
|
||||
if (plugin.equals("TagAPI")) {
|
||||
Plugin iTag = manager.getPlugin("iTag");
|
||||
if (iTag == null || iTag.getDescription().getVersion().startsWith("1.0")) {
|
||||
logger.severe("Detected incompatible plugin: TagAPI");
|
||||
}
|
||||
} else {
|
||||
logger.severe("Detected incompatible plugin: " + plugin);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Used to check Minecraft version
|
||||
private MinecraftVersion verifyMinecraftVersion() {
|
||||
MinecraftVersion minimum = new MinecraftVersion(MINIMUM_MINECRAFT_VERSION);
|
||||
MinecraftVersion maximum = new MinecraftVersion(MAXIMUM_MINECRAFT_VERSION);
|
||||
|
||||
try {
|
||||
MinecraftVersion current = new MinecraftVersion(getServer());
|
||||
|
||||
// Skip certain versions
|
||||
if (!config.getIgnoreVersionCheck().equals(current.getVersion())) {
|
||||
// We'll just warn the user for now
|
||||
if (current.compareTo(minimum) < 0)
|
||||
logger.warning("Version " + current + " is lower than the minimum " + minimum);
|
||||
if (current.compareTo(maximum) > 0)
|
||||
logger.warning("Version " + current + " has not yet been tested! Proceed with caution.");
|
||||
}
|
||||
|
||||
return current;
|
||||
} catch (Exception e) {
|
||||
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_PARSE_MINECRAFT_VERSION).error(e).messageParam(maximum));
|
||||
|
||||
// Unknown version - just assume it is the latest
|
||||
return maximum;
|
||||
}
|
||||
}
|
||||
|
||||
private void checkConflictingVersions() {
|
||||
Pattern ourPlugin = Pattern.compile("ProtocolLib-(.*)\\.jar");
|
||||
MinecraftVersion currentVersion = new MinecraftVersion(getDescription().getVersion());
|
||||
MinecraftVersion newestVersion = null;
|
||||
|
||||
// Skip the file that contains this current instance however
|
||||
File loadedFile = getFile();
|
||||
|
||||
try {
|
||||
// Scan the plugin folder for newer versions of ProtocolLib
|
||||
// The plugin folder isn't always plugins/
|
||||
File pluginFolder = getDataFolder().getParentFile();
|
||||
|
||||
File[] candidates = pluginFolder.listFiles();
|
||||
if (candidates != null) {
|
||||
for (File candidate : pluginFolder.listFiles()) {
|
||||
if (candidate.isFile() && !candidate.equals(loadedFile)) {
|
||||
Matcher match = ourPlugin.matcher(candidate.getName());
|
||||
if (match.matches()) {
|
||||
MinecraftVersion version = new MinecraftVersion(match.group(1));
|
||||
|
||||
if (candidate.length() == 0) {
|
||||
// Delete and inform the user
|
||||
logger.info((candidate.delete() ? "Deleted " : "Could not delete ") + candidate);
|
||||
} else if (newestVersion == null || newestVersion.compareTo(version) < 0) {
|
||||
newestVersion = version;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// TODO This shows [ProtocolLib] and [ProtocolLibrary] in the message
|
||||
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_DETECT_CONFLICTING_PLUGINS).error(e));
|
||||
}
|
||||
|
||||
// See if the newest version is actually higher
|
||||
if (newestVersion != null && currentVersion.compareTo(newestVersion) < 0) {
|
||||
// We don't need to set internal classes or instances to NULL - that would break the other loaded plugin
|
||||
skipDisable = true;
|
||||
|
||||
throw new IllegalStateException(String.format(
|
||||
"Detected a newer version of ProtocolLib (%s) in plugin folder than the current (%s). Disabling.",
|
||||
newestVersion.getVersion(), currentVersion.getVersion()));
|
||||
}
|
||||
}
|
||||
|
||||
private void registerCommand(String name, CommandExecutor executor) {
|
||||
try {
|
||||
// Ignore these - they must have printed an error already
|
||||
if (executor == null)
|
||||
return;
|
||||
|
||||
PluginCommand command = getCommand(name);
|
||||
|
||||
// Try to load the command
|
||||
if (command != null) {
|
||||
command.setExecutor(executor);
|
||||
} else {
|
||||
throw new RuntimeException("plugin.yml might be corrupt.");
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_REGISTER_COMMAND).messageParam(name, e.getMessage()).error(e));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable the current plugin.
|
||||
*/
|
||||
private void disablePlugin() {
|
||||
getServer().getPluginManager().disablePlugin(this);
|
||||
}
|
||||
|
||||
private void createPacketTask(Server server) {
|
||||
try {
|
||||
if (packetTask >= 0)
|
||||
throw new IllegalStateException("Packet task has already been created");
|
||||
|
||||
// Attempt to create task
|
||||
packetTask = server.getScheduler().scheduleSyncRepeatingTask(this, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
AsyncFilterManager manager = (AsyncFilterManager) protocolManager.getAsynchronousManager();
|
||||
|
||||
// We KNOW we're on the main thread at the moment
|
||||
manager.sendProcessedPackets(tickCounter++, true);
|
||||
|
||||
// House keeping
|
||||
updateConfiguration();
|
||||
|
||||
// Check for updates too
|
||||
if (!UPDATES_DISABLED && (tickCounter % 20) == 0) {
|
||||
checkUpdates();
|
||||
}
|
||||
}
|
||||
}, ASYNC_MANAGER_DELAY, ASYNC_MANAGER_DELAY);
|
||||
} catch (OutOfMemoryError e) {
|
||||
throw e;
|
||||
} catch (ThreadDeath e) {
|
||||
throw e;
|
||||
} catch (Throwable e) {
|
||||
if (packetTask == -1) {
|
||||
reporter.reportDetailed(this, Report.newBuilder(REPORT_CANNOT_CREATE_TIMEOUT_TASK).error(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateConfiguration() {
|
||||
if (config != null && config.getModificationCount() != configExpectedMod) {
|
||||
configExpectedMod = config.getModificationCount();
|
||||
|
||||
// Update the debug flag
|
||||
protocolManager.setDebug(config.isDebug());
|
||||
}
|
||||
}
|
||||
|
||||
private void checkUpdates() {
|
||||
// Ignore milliseconds - it's pointless
|
||||
long currentTime = System.currentTimeMillis() / MILLI_PER_SECOND;
|
||||
|
||||
try {
|
||||
long updateTime = config.getAutoLastTime() + config.getAutoDelay();
|
||||
|
||||
// Should we update?
|
||||
if (currentTime > updateTime && !updater.isChecking()) {
|
||||
// Initiate the update as if it came from the console
|
||||
if (config.isAutoDownload())
|
||||
commandProtocol.updateVersion(getServer().getConsoleSender(), false);
|
||||
else if (config.isAutoNotify())
|
||||
commandProtocol.checkVersion(getServer().getConsoleSender(), false);
|
||||
else
|
||||
commandProtocol.updateFinished();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
reporter.reportDetailed(this, Report.newBuilder(REPORT_CANNOT_UPDATE_PLUGIN).error(e));
|
||||
UPDATES_DISABLED = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
if (skipDisable) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Disable compiler
|
||||
if (backgroundCompiler != null) {
|
||||
backgroundCompiler.shutdownAll();
|
||||
backgroundCompiler = null;
|
||||
BackgroundCompiler.setInstance(null);
|
||||
}
|
||||
|
||||
// Clean up
|
||||
if (packetTask >= 0) {
|
||||
getServer().getScheduler().cancelTask(packetTask);
|
||||
packetTask = -1;
|
||||
}
|
||||
|
||||
// And redirect handler too
|
||||
if (redirectHandler != null) {
|
||||
logger.removeHandler(redirectHandler);
|
||||
}
|
||||
if (protocolManager != null)
|
||||
protocolManager.close();
|
||||
else
|
||||
return; // Plugin reloaders!
|
||||
|
||||
if (unhookTask != null)
|
||||
unhookTask.close();
|
||||
protocolManager = null;
|
||||
statistics = null;
|
||||
|
||||
// To clean up global parameters
|
||||
reporter = new BasicErrorReporter();
|
||||
}
|
||||
|
||||
// Get the Bukkit logger first, before we try to create our own
|
||||
private Logger getLoggerSafely() {
|
||||
Logger log = null;
|
||||
|
||||
try {
|
||||
log = getLogger();
|
||||
} catch (OutOfMemoryError e) {
|
||||
throw e;
|
||||
} catch (ThreadDeath e) {
|
||||
throw e;
|
||||
} catch (Throwable e) {
|
||||
// Ignore
|
||||
}
|
||||
|
||||
// Use the default logger instead
|
||||
if (log == null)
|
||||
log = Logger.getLogger("Minecraft");
|
||||
return log;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the current error reporter.
|
||||
* <p>
|
||||
* This is guaranteed to not be NULL in 2.5.0 and later.
|
||||
* @return Current error reporter.
|
||||
*/
|
||||
public static ErrorReporter getErrorReporter() {
|
||||
return reporter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the current strongly typed configuration.
|
||||
* @return The configuration, or NULL if ProtocolLib hasn't loaded yet.
|
||||
*/
|
||||
public static ProtocolConfig getConfiguration() {
|
||||
return config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the packet protocol manager.
|
||||
* @return Packet protocol manager, or NULL if it has been disabled.
|
||||
*/
|
||||
public static ProtocolManager getProtocolManager() {
|
||||
return protocolManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the metrics instance used to measure users of this library.
|
||||
* <p>
|
||||
* Note that this method may return NULL when the server is reloading or shutting down. It is also
|
||||
* NULL if metrics has been disabled.
|
||||
* @return Metrics instance container.
|
||||
*/
|
||||
public Statistics getStatistics() {
|
||||
return statistics;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve an executor service for performing asynchronous tasks on the behalf of ProtocolLib.
|
||||
* <p>
|
||||
* Note that this service is NULL if ProtocolLib has not been initialized yet.
|
||||
* @return The executor service, or NULL.
|
||||
*/
|
||||
public static ListeningScheduledExecutorService getExecutorAsync() {
|
||||
return executorAsync;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve an executor service for performing synchronous tasks (main thread) on the behalf of ProtocolLib.
|
||||
* <p>
|
||||
* Note that this service is NULL if ProtocolLib has not been initialized yet.
|
||||
* @return The executor service, or NULL.
|
||||
*/
|
||||
public static ListeningScheduledExecutorService getExecutorSync() {
|
||||
return executorSync;
|
||||
}
|
||||
|
||||
// ---- Logging Methods
|
||||
|
||||
public static void log(Level level, String message, Object... args) {
|
||||
logger.log(level, MessageFormat.format(message, args));
|
||||
}
|
||||
|
||||
public static void log(String message, Object... args) {
|
||||
log(Level.INFO, message, args);
|
||||
}
|
||||
|
||||
public static void log(Level level, String message, Throwable ex) {
|
||||
logger.log(level, message, ex);
|
||||
}
|
||||
|
||||
public void disableUpdates() {
|
||||
UPDATES_DISABLED = true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,282 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import com.comphenix.protocol.async.AsyncMarker;
|
||||
import com.comphenix.protocol.events.ListenerPriority;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.events.PacketListener;
|
||||
import com.comphenix.protocol.injector.PacketConstructor;
|
||||
import com.comphenix.protocol.reflect.FieldAccessException;
|
||||
import com.comphenix.protocol.utility.MinecraftVersion;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
|
||||
/**
|
||||
* Represents an API for accessing the Minecraft protocol.
|
||||
* @author Kristian
|
||||
*/
|
||||
public interface ProtocolManager extends PacketStream {
|
||||
/**
|
||||
* Retrieve the protocol version of a given player.
|
||||
* <p>
|
||||
* This only really makes sense of a server that support clients of multiple Minecraft versions, such as Spigot #1628.
|
||||
* @param player - the player.
|
||||
* @return The associated protocol version, or {@link Integer#MIN_VALUE} if unknown.
|
||||
*/
|
||||
public int getProtocolVersion(Player player);
|
||||
|
||||
/**
|
||||
* Send a packet to the given player.
|
||||
* <p>
|
||||
* Re-sending a previously cancelled packet is discouraged. Use {@link AsyncMarker#incrementProcessingDelay()}
|
||||
* to delay a packet until a certain condition has been met.
|
||||
*
|
||||
* @param receiver - the receiver.
|
||||
* @param packet - packet to send.
|
||||
* @param filters - whether or not to invoke any packet filters below {@link ListenerPriority#MONITOR}.
|
||||
* @throws InvocationTargetException - if an error occurred when sending the packet.
|
||||
*/
|
||||
@Override
|
||||
public void sendServerPacket(Player receiver, PacketContainer packet, boolean filters)
|
||||
throws InvocationTargetException;
|
||||
|
||||
/**
|
||||
* Simulate receiving a certain packet from a given player.
|
||||
* <p>
|
||||
* Receiving a previously cancelled packet is discouraged. Use {@link AsyncMarker#incrementProcessingDelay()}
|
||||
* to delay a packet until a certain condition has been met.
|
||||
*
|
||||
* @param sender - the sender.
|
||||
* @param packet - the packet that was sent.
|
||||
* @param filters - whether or not to invoke any packet filters below {@link ListenerPriority#MONITOR}.
|
||||
* @throws InvocationTargetException If the reflection machinery failed.
|
||||
* @throws IllegalAccessException If the underlying method caused an error.
|
||||
*/
|
||||
@Override
|
||||
public void recieveClientPacket(Player sender, PacketContainer packet, boolean filters)
|
||||
throws IllegalAccessException, InvocationTargetException;
|
||||
|
||||
/**
|
||||
* Broadcast a given packet to every connected player on the server.
|
||||
* @param packet - the packet to broadcast.
|
||||
* @throws FieldAccessException If we were unable to send the packet due to reflection problems.
|
||||
*/
|
||||
public void broadcastServerPacket(PacketContainer packet);
|
||||
|
||||
/**
|
||||
* Broadcast a packet to every player that is receiving information about a given entity.
|
||||
* <p>
|
||||
* This is usually every player in the same world within an observable distance. If the entity is a
|
||||
* player, it will only be included if <i>includeTracker</i> is TRUE.
|
||||
* @param packet - the packet to broadcast.
|
||||
* @param entity - the entity whose trackers we will inform.
|
||||
* @param includeTracker - whether or not to also transmit the packet to the entity, if it is a tracker.
|
||||
* @throws FieldAccessException If we were unable to send the packet due to reflection problems.
|
||||
*/
|
||||
public void broadcastServerPacket(PacketContainer packet, Entity entity, boolean includeTracker);
|
||||
|
||||
/**
|
||||
* Broadcast a packet to every player within the given maximum observer distance.
|
||||
* @param packet - the packet to broadcast.
|
||||
* @param origin - the origin to consider when calculating the distance to each observer.
|
||||
* @param maxObserverDistance - the maximum distance to the origin.
|
||||
*/
|
||||
public void broadcastServerPacket(PacketContainer packet, Location origin, int maxObserverDistance);
|
||||
|
||||
/**
|
||||
* Retrieves a list of every registered packet listener.
|
||||
* @return Every registered packet listener.
|
||||
*/
|
||||
public ImmutableSet<PacketListener> getPacketListeners();
|
||||
|
||||
/**
|
||||
* Adds a packet listener.
|
||||
* <p>
|
||||
* Adding an already registered listener has no effect. If you need to change the packets
|
||||
* the current listener is observing, you must first remove the packet listener before you
|
||||
* can register it again.
|
||||
* @param listener - new packet listener.
|
||||
*/
|
||||
public void addPacketListener(PacketListener listener);
|
||||
|
||||
/**
|
||||
* Removes a given packet listener.
|
||||
* <p>
|
||||
* Attempting to remove a listener that doesn't exist has no effect.
|
||||
* @param listener - the packet listener to remove.
|
||||
*/
|
||||
public void removePacketListener(PacketListener listener);
|
||||
|
||||
/**
|
||||
* Removes every listener associated with the given plugin.
|
||||
* @param plugin - the plugin to unload.
|
||||
*/
|
||||
public void removePacketListeners(Plugin plugin);
|
||||
|
||||
/**
|
||||
* Constructs a new encapsulated Minecraft packet with the given ID.
|
||||
* <p>
|
||||
* Deprecated: Use {@link #createPacket(PacketType)} instead.
|
||||
* @param id - packet ID.
|
||||
* @return New encapsulated Minecraft packet.
|
||||
*/
|
||||
@Deprecated
|
||||
public PacketContainer createPacket(int id);
|
||||
|
||||
/**
|
||||
* Constructs a new encapsulated Minecraft packet with the given ID.
|
||||
* @param type - packet type.
|
||||
* @return New encapsulated Minecraft packet.
|
||||
*/
|
||||
public PacketContainer createPacket(PacketType type);
|
||||
|
||||
/**
|
||||
* Constructs a new encapsulated Minecraft packet with the given ID.
|
||||
* <p>
|
||||
* If set to true, the <i>forceDefaults</i> option will force the system to automatically
|
||||
* give non-primitive fields in the packet sensible default values. For instance, certain
|
||||
* packets - like Packet60Explosion - require a List or Set to be non-null. If the
|
||||
* forceDefaults option is true, the List or Set will be automatically created.
|
||||
* <p>
|
||||
* Deprecated: Use {@link #createPacket(PacketType, boolean)} instead.
|
||||
*
|
||||
* @param id - packet ID.
|
||||
* @param forceDefaults - TRUE to use sensible defaults in most fields, FALSE otherwise.
|
||||
* @return New encapsulated Minecraft packet.
|
||||
*/
|
||||
@Deprecated
|
||||
public PacketContainer createPacket(int id, boolean forceDefaults);
|
||||
|
||||
/**
|
||||
* Constructs a new encapsulated Minecraft packet with the given ID.
|
||||
* <p>
|
||||
* If set to true, the <i>forceDefaults</i> option will force the system to automatically
|
||||
* give non-primitive fields in the packet sensible default values. For instance, certain
|
||||
* packets - like Packet60Explosion - require a List or Set to be non-null. If the
|
||||
* forceDefaults option is true, the List or Set will be automatically created.
|
||||
*
|
||||
* @param type - packet type.
|
||||
* @param forceDefaults - TRUE to use sensible defaults in most fields, FALSE otherwise.
|
||||
* @return New encapsulated Minecraft packet.
|
||||
*/
|
||||
public PacketContainer createPacket(PacketType type, boolean forceDefaults);
|
||||
|
||||
/**
|
||||
* Construct a packet using the special builtin Minecraft constructors.
|
||||
* <p>
|
||||
* Deprecated: Use {@link #createPacketConstructor(PacketType, Object...)} instead.
|
||||
* @param id - the packet ID.
|
||||
* @param arguments - arguments that will be passed to the constructor.
|
||||
* @return The packet constructor.
|
||||
*/
|
||||
@Deprecated
|
||||
public PacketConstructor createPacketConstructor(int id, Object... arguments);
|
||||
|
||||
/**
|
||||
* Construct a packet using the special builtin Minecraft constructors.
|
||||
* @param type - the packet type.
|
||||
* @param arguments - arguments that will be passed to the constructor.
|
||||
* @return The packet constructor.
|
||||
*/
|
||||
public PacketConstructor createPacketConstructor(PacketType type, Object... arguments);
|
||||
|
||||
/**
|
||||
* Completely resend an entity to a list of clients.
|
||||
* <p>
|
||||
* Note that this method is NOT thread safe. If you call this method from anything
|
||||
* but the main thread, it will throw an exception.
|
||||
* @param entity - entity to refresh.
|
||||
* @param observers - the clients to update.
|
||||
*/
|
||||
public void updateEntity(Entity entity, List<Player> observers) throws FieldAccessException;
|
||||
|
||||
/**
|
||||
* Retrieve the associated entity.
|
||||
* @param container - the world the entity belongs to.
|
||||
* @param id - the unique ID of the entity.
|
||||
* @return The associated entity.
|
||||
* @throws FieldAccessException Reflection failed.
|
||||
*/
|
||||
public Entity getEntityFromID(World container, int id) throws FieldAccessException;
|
||||
|
||||
/**
|
||||
* Retrieve every client that is receiving information about a given entity.
|
||||
* @param entity - the entity that is being tracked.
|
||||
* @return Every client/player that is tracking the given entity.
|
||||
* @throws FieldAccessException If reflection failed.
|
||||
*/
|
||||
public List<Player> getEntityTrackers(Entity entity) throws FieldAccessException;
|
||||
|
||||
/**
|
||||
* Retrieves a immutable set containing the ID of the sent server packets that will be observed by listeners.
|
||||
* <p>
|
||||
* Deprecated: Use {@link #getSendingFilterTypes()} instead.
|
||||
* @return Every filtered server packet.
|
||||
*/
|
||||
@Deprecated
|
||||
public Set<Integer> getSendingFilters();
|
||||
|
||||
/**
|
||||
* Retrieves a immutable set containing the type of the sent server packets that will be observed by listeners.
|
||||
* @return Every filtered server packet.
|
||||
*/
|
||||
public Set<PacketType> getSendingFilterTypes();
|
||||
|
||||
/**
|
||||
* Retrieves a immutable set containing the ID of the received client packets that will be observed by listeners.
|
||||
* <p>
|
||||
* Deprecated: Use {@link #getReceivingFilterTypes()} instead.
|
||||
* @return Every filtered client packet.
|
||||
*/
|
||||
@Deprecated
|
||||
public Set<Integer> getReceivingFilters();
|
||||
|
||||
/**
|
||||
* Retrieves a immutable set containing the type of the received client packets that will be observed by listeners.
|
||||
* @return Every filtered client packet.
|
||||
*/
|
||||
public Set<PacketType> getReceivingFilterTypes();
|
||||
|
||||
/**
|
||||
* Retrieve the current Minecraft version.
|
||||
* @return The current version.
|
||||
*/
|
||||
public MinecraftVersion getMinecraftVersion();
|
||||
|
||||
/**
|
||||
* Determines whether or not this protocol manager has been disabled.
|
||||
* @return TRUE if it has, FALSE otherwise.
|
||||
*/
|
||||
public boolean isClosed();
|
||||
|
||||
/**
|
||||
* Retrieve the current asynchronous packet manager.
|
||||
* @return Asynchronous packet manager.
|
||||
*/
|
||||
public AsynchronousManager getAsynchronousManager();
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Deque;
|
||||
import java.util.List;
|
||||
|
||||
import com.comphenix.protocol.compat.guava.Guava;
|
||||
import com.google.common.collect.Range;
|
||||
|
||||
/**
|
||||
* Used to parse ranges in CommandPacket.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
class RangeParser {
|
||||
/**
|
||||
* Parse a range from a given text.
|
||||
* @param text - the text.
|
||||
* @param legalRange - range of legal values.
|
||||
* @return The parsed ranges.
|
||||
*/
|
||||
public static List<Range<Integer>> getRanges(String text, Range<Integer> legalRange) {
|
||||
return getRanges(new ArrayDeque<String>(Arrays.asList(text)), legalRange);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse ranges from an array of elements.
|
||||
* @param args - array of elements.
|
||||
* @param offset - beginning offset.
|
||||
* @param lastIndex - the last index of the array to read.
|
||||
* @param legalRange - range of legal values.
|
||||
* @return The parsed ranges.
|
||||
*/
|
||||
public static List<Range<Integer>> getRanges(Deque<String> input, Range<Integer> legalRange) {
|
||||
List<String> tokens = tokenizeInput(input);
|
||||
List<Range<Integer>> ranges = new ArrayList<Range<Integer>>();
|
||||
|
||||
for (int i = 0; i < tokens.size(); i++) {
|
||||
Range<Integer> range;
|
||||
String current = tokens.get(i);
|
||||
String next = i + 1 < tokens.size() ? tokens.get(i + 1) : null;
|
||||
|
||||
// Yoda equality is done for null-safety
|
||||
if ("-".equals(current)) {
|
||||
throw new IllegalArgumentException("A hyphen must appear between two numbers.");
|
||||
} else if ("-".equals(next)) {
|
||||
if (i + 2 >= tokens.size())
|
||||
throw new IllegalArgumentException("Cannot form a range without a upper limit.");
|
||||
|
||||
// This is a proper range
|
||||
range = Guava.closedRange(Integer.parseInt(current), Integer.parseInt(tokens.get(i + 2)));
|
||||
ranges.add(range);
|
||||
|
||||
// Skip the two next tokens
|
||||
i += 2;
|
||||
|
||||
} else {
|
||||
// Just a single number
|
||||
range = Guava.singleton(Integer.parseInt(current));
|
||||
ranges.add(range);
|
||||
}
|
||||
|
||||
// Validate ranges
|
||||
if (!legalRange.encloses(range)) {
|
||||
throw new IllegalArgumentException(range + " is not in the range " + range.toString());
|
||||
}
|
||||
}
|
||||
|
||||
return simplify(ranges, legalRange.upperEndpoint());
|
||||
}
|
||||
|
||||
/**
|
||||
* Simplify a list of ranges by assuming a maximum value.
|
||||
* @param ranges - the list of ranges to simplify.
|
||||
* @param maximum - the maximum value (minimum value is always 0).
|
||||
* @return A simplified list of ranges.
|
||||
*/
|
||||
private static List<Range<Integer>> simplify(List<Range<Integer>> ranges, int maximum) {
|
||||
List<Range<Integer>> result = new ArrayList<Range<Integer>>();
|
||||
boolean[] set = new boolean[maximum + 1];
|
||||
int start = -1;
|
||||
|
||||
// Set every ID
|
||||
for (Range<Integer> range : ranges) {
|
||||
for (int id : Guava.toSet(range)) {
|
||||
set[id] = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Generate ranges from this set
|
||||
for (int i = 0; i <= set.length; i++) {
|
||||
if (i < set.length && set[i]) {
|
||||
if (start < 0) {
|
||||
start = i;
|
||||
}
|
||||
} else {
|
||||
if (start >= 0) {
|
||||
result.add(Guava.closedRange(start, i - 1));
|
||||
start = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static List<String> tokenizeInput(Deque<String> input) {
|
||||
List<String> tokens = new ArrayList<String>();
|
||||
|
||||
// Tokenize the input
|
||||
while (!input.isEmpty()) {
|
||||
StringBuilder number = new StringBuilder();
|
||||
String text = input.peek();
|
||||
|
||||
for (int j = 0; j < text.length(); j++) {
|
||||
char current = text.charAt(j);
|
||||
|
||||
if (Character.isDigit(current)) {
|
||||
number.append(current);
|
||||
} else if (Character.isWhitespace(current)) {
|
||||
// That's ok
|
||||
} else if (current == '-') {
|
||||
// Add the number token first
|
||||
if (number.length() > 0) {
|
||||
tokens.add(number.toString());
|
||||
number.setLength(0);
|
||||
}
|
||||
|
||||
tokens.add(Character.toString(current));
|
||||
} else {
|
||||
// We're no longer dealing with integers - quit
|
||||
return tokens;
|
||||
}
|
||||
}
|
||||
|
||||
// Add the number token, if it hasn't already
|
||||
if (number.length() > 0)
|
||||
tokens.add(number.toString());
|
||||
input.poll();
|
||||
}
|
||||
|
||||
return tokens;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,494 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol.async;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.bukkit.scheduler.BukkitScheduler;
|
||||
|
||||
import com.comphenix.protocol.AsynchronousManager;
|
||||
import com.comphenix.protocol.PacketStream;
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.ProtocolManager;
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.events.ListeningWhitelist;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.events.PacketListener;
|
||||
import com.comphenix.protocol.injector.PacketFilterManager;
|
||||
import com.comphenix.protocol.injector.PrioritizedListener;
|
||||
import com.comphenix.protocol.injector.SortedPacketListenerList;
|
||||
import com.comphenix.protocol.injector.packet.PacketRegistry;
|
||||
import com.google.common.base.Objects;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Sets;
|
||||
|
||||
/**
|
||||
* Represents a filter manager for asynchronous packets.
|
||||
* <p>
|
||||
* By using {@link AsyncMarker#incrementProcessingDelay()}, a packet can be delayed without having to block the
|
||||
* processing thread.
|
||||
* @author Kristian
|
||||
*/
|
||||
public class AsyncFilterManager implements AsynchronousManager {
|
||||
|
||||
private SortedPacketListenerList serverTimeoutListeners;
|
||||
private SortedPacketListenerList clientTimeoutListeners;
|
||||
private Set<PacketListener> timeoutListeners;
|
||||
|
||||
private PacketProcessingQueue serverProcessingQueue;
|
||||
private PacketProcessingQueue clientProcessingQueue;
|
||||
|
||||
// Sending queues
|
||||
private final PlayerSendingHandler playerSendingHandler;
|
||||
|
||||
// Report exceptions
|
||||
private final ErrorReporter reporter;
|
||||
|
||||
// The likely main thread
|
||||
private final Thread mainThread;
|
||||
|
||||
// Default scheduler
|
||||
private final BukkitScheduler scheduler;
|
||||
|
||||
// Current packet index
|
||||
private final AtomicInteger currentSendingIndex = new AtomicInteger();
|
||||
|
||||
// Our protocol manager
|
||||
private ProtocolManager manager;
|
||||
|
||||
/**
|
||||
* Initialize a asynchronous filter manager.
|
||||
* <p>
|
||||
* <b>Internal method</b>. Retrieve the global asynchronous manager from the protocol manager instead.
|
||||
* @param reporter - desired error reporter.
|
||||
* @param scheduler - task scheduler.
|
||||
*/
|
||||
public AsyncFilterManager(ErrorReporter reporter, BukkitScheduler scheduler) {
|
||||
// Initialize timeout listeners
|
||||
this.serverTimeoutListeners = new SortedPacketListenerList();
|
||||
this.clientTimeoutListeners = new SortedPacketListenerList();
|
||||
this.timeoutListeners = Sets.newSetFromMap(new ConcurrentHashMap<PacketListener, Boolean>());
|
||||
|
||||
this.playerSendingHandler = new PlayerSendingHandler(reporter, serverTimeoutListeners, clientTimeoutListeners);
|
||||
this.serverProcessingQueue = new PacketProcessingQueue(playerSendingHandler);
|
||||
this.clientProcessingQueue = new PacketProcessingQueue(playerSendingHandler);
|
||||
this.playerSendingHandler.initializeScheduler();
|
||||
|
||||
this.scheduler = scheduler;
|
||||
this.reporter = reporter;
|
||||
this.mainThread = Thread.currentThread();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the protocol manager.
|
||||
* @return The protocol manager.
|
||||
*/
|
||||
public ProtocolManager getManager() {
|
||||
return manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the associated protocol manager.
|
||||
* @param manager - the new manager.
|
||||
*/
|
||||
public void setManager(ProtocolManager manager) {
|
||||
this.manager = manager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AsyncListenerHandler registerAsyncHandler(PacketListener listener) {
|
||||
return registerAsyncHandler(listener, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerTimeoutHandler(PacketListener listener) {
|
||||
if (listener == null)
|
||||
throw new IllegalArgumentException("listener cannot be NULL.");
|
||||
if (!timeoutListeners.add(listener))
|
||||
return;
|
||||
|
||||
ListeningWhitelist sending = listener.getSendingWhitelist();
|
||||
ListeningWhitelist receiving = listener.getReceivingWhitelist();
|
||||
|
||||
if (!ListeningWhitelist.isEmpty(sending))
|
||||
serverTimeoutListeners.addListener(listener, sending);
|
||||
if (!ListeningWhitelist.isEmpty(receiving))
|
||||
serverTimeoutListeners.addListener(listener, receiving);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<PacketListener> getTimeoutHandlers() {
|
||||
return ImmutableSet.copyOf(timeoutListeners);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<PacketListener> getAsyncHandlers() {
|
||||
ImmutableSet.Builder<PacketListener> builder = ImmutableSet.builder();
|
||||
|
||||
// Add every asynchronous packet listener
|
||||
for (PrioritizedListener<AsyncListenerHandler> handler :
|
||||
Iterables.concat(serverProcessingQueue.values(), clientProcessingQueue.values())) {
|
||||
builder.add(handler.getListener().getAsyncListener());
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers an asynchronous packet handler.
|
||||
* <p>
|
||||
* Use {@link AsyncMarker#incrementProcessingDelay()} to delay a packet until its ready to be transmitted.
|
||||
* <p>
|
||||
* To start listening asynchronously, pass the getListenerLoop() runnable to a different thread.
|
||||
* <p>
|
||||
* Asynchronous events will only be executed if a synchronous listener with the same packets is registered.
|
||||
* If you already have a synchronous event, call this method with autoInject set to FALSE.
|
||||
*
|
||||
* @param listener - the packet listener that will receive these asynchronous events.
|
||||
* @param autoInject - whether or not to automatically create the corresponding synchronous listener,
|
||||
* @return An asynchronous handler.
|
||||
*/
|
||||
public AsyncListenerHandler registerAsyncHandler(PacketListener listener, boolean autoInject) {
|
||||
AsyncListenerHandler handler = new AsyncListenerHandler(mainThread, this, listener);
|
||||
|
||||
ListeningWhitelist sendingWhitelist = listener.getSendingWhitelist();
|
||||
ListeningWhitelist receivingWhitelist = listener.getReceivingWhitelist();
|
||||
|
||||
if (!hasValidWhitelist(sendingWhitelist) && !hasValidWhitelist(receivingWhitelist)) {
|
||||
throw new IllegalArgumentException("Listener has an empty sending and receiving whitelist.");
|
||||
}
|
||||
|
||||
// Add listener to either or both processing queue
|
||||
if (hasValidWhitelist(sendingWhitelist)) {
|
||||
PacketFilterManager.verifyWhitelist(listener, sendingWhitelist);
|
||||
serverProcessingQueue.addListener(handler, sendingWhitelist);
|
||||
}
|
||||
if (hasValidWhitelist(receivingWhitelist)) {
|
||||
PacketFilterManager.verifyWhitelist(listener, receivingWhitelist);
|
||||
clientProcessingQueue.addListener(handler, receivingWhitelist);
|
||||
}
|
||||
|
||||
// We need a synchronized listener to get the ball rolling
|
||||
if (autoInject) {
|
||||
handler.setNullPacketListener(new NullPacketListener(listener));
|
||||
manager.addPacketListener(handler.getNullPacketListener());
|
||||
}
|
||||
return handler;
|
||||
}
|
||||
|
||||
private boolean hasValidWhitelist(ListeningWhitelist whitelist) {
|
||||
return whitelist != null && whitelist.getTypes().size() > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterTimeoutHandler(PacketListener listener) {
|
||||
if (listener == null)
|
||||
throw new IllegalArgumentException("listener cannot be NULL.");
|
||||
|
||||
ListeningWhitelist sending = listener.getSendingWhitelist();
|
||||
ListeningWhitelist receiving = listener.getReceivingWhitelist();
|
||||
|
||||
// Do it in the opposite order
|
||||
if (serverTimeoutListeners.removeListener(listener, sending).size() > 0 ||
|
||||
clientTimeoutListeners.removeListener(listener, receiving).size() > 0) {
|
||||
timeoutListeners.remove(listener);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterAsyncHandler(PacketListener listener) {
|
||||
if (listener == null)
|
||||
throw new IllegalArgumentException("listener cannot be NULL.");
|
||||
|
||||
AsyncListenerHandler handler =
|
||||
findHandler(serverProcessingQueue, listener.getSendingWhitelist(), listener);
|
||||
|
||||
if (handler == null) {
|
||||
handler = findHandler(clientProcessingQueue, listener.getReceivingWhitelist(), listener);
|
||||
}
|
||||
unregisterAsyncHandler(handler);
|
||||
}
|
||||
|
||||
// Search for the first correct handler
|
||||
private AsyncListenerHandler findHandler(PacketProcessingQueue queue, ListeningWhitelist search, PacketListener target) {
|
||||
if (ListeningWhitelist.isEmpty(search))
|
||||
return null;
|
||||
|
||||
for (PacketType type : search.getTypes()) {
|
||||
for (PrioritizedListener<AsyncListenerHandler> element : queue.getListener(type)) {
|
||||
if (element.getListener().getAsyncListener() == target) {
|
||||
return element.getListener();
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterAsyncHandler(AsyncListenerHandler handler) {
|
||||
if (handler == null)
|
||||
throw new IllegalArgumentException("listenerToken cannot be NULL");
|
||||
|
||||
handler.cancel();
|
||||
}
|
||||
|
||||
// Called by AsyncListenerHandler
|
||||
void unregisterAsyncHandlerInternal(AsyncListenerHandler handler) {
|
||||
|
||||
PacketListener listener = handler.getAsyncListener();
|
||||
boolean synchronusOK = onMainThread();
|
||||
|
||||
// Unregister null packet listeners
|
||||
if (handler.getNullPacketListener() != null) {
|
||||
manager.removePacketListener(handler.getNullPacketListener());
|
||||
}
|
||||
|
||||
// Just remove it from the queue(s)
|
||||
if (hasValidWhitelist(listener.getSendingWhitelist())) {
|
||||
List<PacketType> removed = serverProcessingQueue.removeListener(handler, listener.getSendingWhitelist());
|
||||
|
||||
// We're already taking care of this, so don't do anything
|
||||
playerSendingHandler.sendServerPackets(removed, synchronusOK);
|
||||
}
|
||||
|
||||
if (hasValidWhitelist(listener.getReceivingWhitelist())) {
|
||||
List<PacketType> removed = clientProcessingQueue.removeListener(handler, listener.getReceivingWhitelist());
|
||||
playerSendingHandler.sendClientPackets(removed, synchronusOK);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if we're running on the main thread.
|
||||
* @return TRUE if we are, FALSE otherwise.
|
||||
*/
|
||||
private boolean onMainThread() {
|
||||
return Thread.currentThread().getId() == mainThread.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterAsyncHandlers(Plugin plugin) {
|
||||
unregisterAsyncHandlers(serverProcessingQueue, plugin);
|
||||
unregisterAsyncHandlers(clientProcessingQueue, plugin);
|
||||
}
|
||||
|
||||
private void unregisterAsyncHandlers(PacketProcessingQueue processingQueue, Plugin plugin) {
|
||||
|
||||
// Iterate through every packet listener
|
||||
for (PrioritizedListener<AsyncListenerHandler> listener : processingQueue.values()) {
|
||||
// Remove the listener
|
||||
if (Objects.equal(listener.getListener().getPlugin(), plugin)) {
|
||||
unregisterAsyncHandler(listener.getListener());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue a packet for asynchronous processing.
|
||||
*
|
||||
* @param syncPacket - synchronous packet event.
|
||||
* @param asyncMarker - the asynchronous marker to use.
|
||||
*/
|
||||
public synchronized void enqueueSyncPacket(PacketEvent syncPacket, AsyncMarker asyncMarker) {
|
||||
PacketEvent newEvent = PacketEvent.fromSynchronous(syncPacket, asyncMarker);
|
||||
|
||||
if (asyncMarker.isQueued() || asyncMarker.isTransmitted())
|
||||
throw new IllegalArgumentException("Cannot queue a packet that has already been queued.");
|
||||
|
||||
asyncMarker.setQueuedSendingIndex(asyncMarker.getNewSendingIndex());
|
||||
|
||||
// The player is only be null when they're logged out,
|
||||
// so this should be a pretty safe check
|
||||
Player player = newEvent.getPlayer();
|
||||
if (player != null) {
|
||||
// Start the process
|
||||
getSendingQueue(syncPacket).enqueue(newEvent);
|
||||
|
||||
// We know this is occuring on the main thread, so pass TRUE
|
||||
getProcessingQueue(syncPacket).enqueue(newEvent, true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Integer> getSendingFilters() {
|
||||
return PacketRegistry.toLegacy(serverProcessingQueue.keySet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<PacketType> getReceivingTypes() {
|
||||
return serverProcessingQueue.keySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Integer> getReceivingFilters() {
|
||||
return PacketRegistry.toLegacy(clientProcessingQueue.keySet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<PacketType> getSendingTypes() {
|
||||
return clientProcessingQueue.keySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the current task scheduler.
|
||||
* @return Current task scheduler.
|
||||
*/
|
||||
public BukkitScheduler getScheduler() {
|
||||
return scheduler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasAsynchronousListeners(PacketEvent packet) {
|
||||
Collection<?> list = getProcessingQueue(packet).getListener(packet.getPacketType());
|
||||
return list != null && list.size() > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a asynchronous marker with all the default values.
|
||||
* @return Asynchronous marker.
|
||||
*/
|
||||
public AsyncMarker createAsyncMarker() {
|
||||
return createAsyncMarker(AsyncMarker.DEFAULT_TIMEOUT_DELTA);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an async marker with the given sending priority delta and timeout delta.
|
||||
* @param timeoutDelta - how long (in ms) until the packet expire.
|
||||
* @return An async marker.
|
||||
*/
|
||||
public AsyncMarker createAsyncMarker(long timeoutDelta) {
|
||||
return createAsyncMarker(timeoutDelta, currentSendingIndex.incrementAndGet());
|
||||
}
|
||||
|
||||
// Helper method
|
||||
private AsyncMarker createAsyncMarker(long timeoutDelta, long sendingIndex) {
|
||||
return new AsyncMarker(manager, sendingIndex, System.currentTimeMillis(), timeoutDelta);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketStream getPacketStream() {
|
||||
return manager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ErrorReporter getErrorReporter() {
|
||||
return reporter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanupAll() {
|
||||
serverProcessingQueue.cleanupAll();
|
||||
playerSendingHandler.cleanupAll();
|
||||
timeoutListeners.clear();
|
||||
|
||||
serverTimeoutListeners = null;
|
||||
clientTimeoutListeners = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void signalPacketTransmission(PacketEvent packet) {
|
||||
signalPacketTransmission(packet, onMainThread());
|
||||
}
|
||||
|
||||
/**
|
||||
* Signal that a packet is ready to be transmitted.
|
||||
* @param packet - packet to signal.
|
||||
* @param onMainThread - whether or not this method was run by the main thread.
|
||||
*/
|
||||
private void signalPacketTransmission(PacketEvent packet, boolean onMainThread) {
|
||||
AsyncMarker marker = packet.getAsyncMarker();
|
||||
if (marker == null)
|
||||
throw new IllegalArgumentException(
|
||||
"A sync packet cannot be transmitted by the asynchronous manager.");
|
||||
if (!marker.isQueued())
|
||||
throw new IllegalArgumentException(
|
||||
"A packet must have been queued before it can be transmitted.");
|
||||
|
||||
// Only send if the packet is ready
|
||||
if (marker.decrementProcessingDelay() == 0) {
|
||||
PacketSendingQueue queue = getSendingQueue(packet, false);
|
||||
|
||||
// No need to create a new queue if the player has logged out
|
||||
if (queue != null)
|
||||
queue.signalPacketUpdate(packet, onMainThread);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the sending queue this packet belongs to.
|
||||
* @param packet - the packet.
|
||||
* @return The server or client sending queue the packet belongs to.
|
||||
*/
|
||||
public PacketSendingQueue getSendingQueue(PacketEvent packet) {
|
||||
return playerSendingHandler.getSendingQueue(packet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the sending queue this packet belongs to.
|
||||
* @param packet - the packet.
|
||||
* @param createNew - if TRUE, create a new queue if it hasn't already been created.
|
||||
* @return The server or client sending queue the packet belongs to.
|
||||
*/
|
||||
public PacketSendingQueue getSendingQueue(PacketEvent packet, boolean createNew) {
|
||||
return playerSendingHandler.getSendingQueue(packet, createNew);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the processing queue this packet belongs to.
|
||||
* @param packet - the packet.
|
||||
* @return The server or client sending processing the packet belongs to.
|
||||
*/
|
||||
public PacketProcessingQueue getProcessingQueue(PacketEvent packet) {
|
||||
return packet.isServerPacket() ? serverProcessingQueue : clientProcessingQueue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Signal that a packet has finished processing.
|
||||
* @param packet - packet to signal.
|
||||
*/
|
||||
public void signalFreeProcessingSlot(PacketEvent packet) {
|
||||
getProcessingQueue(packet).signalProcessingDone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send any due packets, or clean up packets that have expired.
|
||||
* @param tickCounter Tick counter
|
||||
* @param onMainThread Whether or not to execute on the main thread
|
||||
*/
|
||||
public void sendProcessedPackets(int tickCounter, boolean onMainThread) {
|
||||
// The server queue is unlikely to need checking that often
|
||||
if (tickCounter % 10 == 0) {
|
||||
playerSendingHandler.trySendServerPackets(onMainThread);
|
||||
}
|
||||
|
||||
playerSendingHandler.trySendClientPackets(onMainThread);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up after a given player has logged out.
|
||||
* @param player - the player that has just logged out.
|
||||
*/
|
||||
public void removePlayer(Player player) {
|
||||
playerSendingHandler.removePlayer(player);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,706 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol.async;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import com.comphenix.protocol.ProtocolLibrary;
|
||||
import com.comphenix.protocol.error.Report;
|
||||
import com.comphenix.protocol.error.ReportType;
|
||||
import com.comphenix.protocol.events.ListeningWhitelist;
|
||||
import com.comphenix.protocol.events.PacketAdapter;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.events.PacketListener;
|
||||
import com.comphenix.protocol.timing.TimedListenerManager;
|
||||
import com.comphenix.protocol.timing.TimedListenerManager.ListenerType;
|
||||
import com.comphenix.protocol.timing.TimedTracker;
|
||||
import com.comphenix.protocol.utility.WrappedScheduler;
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.base.Joiner;
|
||||
|
||||
/**
|
||||
* Represents a handler for an asynchronous event.
|
||||
* <p>
|
||||
* Use {@link AsyncMarker#incrementProcessingDelay()} to delay a packet until a certain condition has been met.
|
||||
* @author Kristian
|
||||
*/
|
||||
public class AsyncListenerHandler {
|
||||
public static final ReportType REPORT_HANDLER_NOT_STARTED = new ReportType(
|
||||
"Plugin %s did not start the asynchronous handler %s by calling start() or syncStart().");
|
||||
|
||||
/**
|
||||
* Signal an end to packet processing.
|
||||
*/
|
||||
private static final PacketEvent INTERUPT_PACKET = new PacketEvent(new Object());
|
||||
|
||||
/**
|
||||
* Called when the threads have to wake up for something important.
|
||||
*/
|
||||
private static final PacketEvent WAKEUP_PACKET = new PacketEvent(new Object());
|
||||
|
||||
/**
|
||||
* The expected number of ticks per second.
|
||||
*/
|
||||
private static final int TICKS_PER_SECOND = 20;
|
||||
|
||||
// Unique worker ID
|
||||
private static final AtomicInteger nextID = new AtomicInteger();
|
||||
|
||||
// Default queue capacity
|
||||
private static final int DEFAULT_CAPACITY = 1024;
|
||||
|
||||
// Cancel the async handler
|
||||
private volatile boolean cancelled;
|
||||
|
||||
// Number of worker threads
|
||||
private final AtomicInteger started = new AtomicInteger();
|
||||
|
||||
// The packet listener
|
||||
private PacketListener listener;
|
||||
|
||||
// The filter manager
|
||||
private AsyncFilterManager filterManager;
|
||||
private NullPacketListener nullPacketListener;
|
||||
|
||||
// List of queued packets
|
||||
private ArrayBlockingQueue<PacketEvent> queuedPackets = new ArrayBlockingQueue<PacketEvent>(DEFAULT_CAPACITY);
|
||||
|
||||
// List of cancelled tasks
|
||||
private final Set<Integer> stoppedTasks = new HashSet<Integer>();
|
||||
private final Object stopLock = new Object();
|
||||
|
||||
// Processing task on the main thread
|
||||
private int syncTask = -1;
|
||||
|
||||
// Minecraft main thread
|
||||
private Thread mainThread;
|
||||
|
||||
// Warn plugins that the async listener handler must be started
|
||||
private int warningTask;
|
||||
|
||||
// Timing manager
|
||||
private TimedListenerManager timedManager = TimedListenerManager.getInstance();
|
||||
|
||||
/**
|
||||
* Construct a manager for an asynchronous packet handler.
|
||||
* @param mainThread - the main game thread.
|
||||
* @param filterManager - the parent filter manager.
|
||||
* @param listener - the current packet listener.
|
||||
*/
|
||||
AsyncListenerHandler(Thread mainThread, AsyncFilterManager filterManager, PacketListener listener) {
|
||||
if (filterManager == null)
|
||||
throw new IllegalArgumentException("filterManager cannot be NULL");
|
||||
if (listener == null)
|
||||
throw new IllegalArgumentException("listener cannot be NULL");
|
||||
|
||||
this.mainThread = mainThread;
|
||||
this.filterManager = filterManager;
|
||||
this.listener = listener;
|
||||
startWarningTask();
|
||||
}
|
||||
|
||||
private void startWarningTask() {
|
||||
warningTask = filterManager.getScheduler().scheduleSyncDelayedTask(getPlugin(), new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
ProtocolLibrary.getErrorReporter().reportWarning(AsyncListenerHandler.this, Report.
|
||||
newBuilder(REPORT_HANDLER_NOT_STARTED).
|
||||
messageParam(listener.getPlugin(), AsyncListenerHandler.this).
|
||||
build()
|
||||
);
|
||||
}
|
||||
}, 2 * TICKS_PER_SECOND);
|
||||
}
|
||||
|
||||
private void stopWarningTask() {
|
||||
int taskId = warningTask;
|
||||
|
||||
// Ensure we have a task to cancel
|
||||
if (warningTask >= 0) {
|
||||
filterManager.getScheduler().cancelTask(taskId);
|
||||
warningTask = -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether or not this asynchronous handler has been cancelled.
|
||||
* @return TRUE if it has been cancelled/stopped, FALSE otherwise.
|
||||
*/
|
||||
public boolean isCancelled() {
|
||||
return cancelled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the current asynchronous packet listener.
|
||||
* @return Current packet listener.
|
||||
*/
|
||||
public PacketListener getAsyncListener() {
|
||||
return listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the synchronized listener that has been automatically created.
|
||||
* @param nullPacketListener - automatically created listener.
|
||||
*/
|
||||
void setNullPacketListener(NullPacketListener nullPacketListener) {
|
||||
this.nullPacketListener = nullPacketListener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the synchronized listener that was automatically created.
|
||||
* @return Automatically created listener.
|
||||
*/
|
||||
PacketListener getNullPacketListener() {
|
||||
return nullPacketListener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the plugin associated with this async listener.
|
||||
* @return The plugin.
|
||||
*/
|
||||
public Plugin getPlugin() {
|
||||
return listener != null ? listener.getPlugin() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel the handler.
|
||||
*/
|
||||
public void cancel() {
|
||||
// Remove the listener as quickly as possible
|
||||
close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Queue a packet for processing.
|
||||
* @param packet - a packet for processing.
|
||||
* @throws IllegalStateException If the underlying packet queue is full.
|
||||
*/
|
||||
public void enqueuePacket(PacketEvent packet) {
|
||||
if (packet == null)
|
||||
throw new IllegalArgumentException("packet is NULL");
|
||||
|
||||
queuedPackets.add(packet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a worker that will initiate the listener loop. Note that using stop() to
|
||||
* close a specific worker is less efficient than stopping an arbitrary worker.
|
||||
* <p>
|
||||
* <b>Warning</b>: Never call the run() method in the main thread.
|
||||
* @return The listener loop
|
||||
*/
|
||||
public AsyncRunnable getListenerLoop() {
|
||||
return new AsyncRunnable() {
|
||||
|
||||
private final AtomicBoolean firstRun = new AtomicBoolean();
|
||||
private final AtomicBoolean finished = new AtomicBoolean();
|
||||
private final int id = nextID.incrementAndGet();
|
||||
|
||||
@Override
|
||||
public int getID() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
// Careful now
|
||||
if (firstRun.compareAndSet(false, true)) {
|
||||
listenerLoop(id);
|
||||
|
||||
synchronized (stopLock) {
|
||||
stoppedTasks.remove(id);
|
||||
stopLock.notifyAll();
|
||||
finished.set(true);
|
||||
}
|
||||
|
||||
} else {
|
||||
if (finished.get())
|
||||
throw new IllegalStateException(
|
||||
"This listener has already been run. Create a new instead.");
|
||||
else
|
||||
throw new IllegalStateException(
|
||||
"This listener loop has already been started. Create a new instead.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean stop() throws InterruptedException {
|
||||
synchronized (stopLock) {
|
||||
if (!isRunning())
|
||||
return false;
|
||||
|
||||
stoppedTasks.add(id);
|
||||
|
||||
// Wake up threads - we have a listener to stop
|
||||
for (int i = 0; i < getWorkers(); i++) {
|
||||
queuedPackets.offer(WAKEUP_PACKET);
|
||||
}
|
||||
|
||||
finished.set(true);
|
||||
waitForStops();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRunning() {
|
||||
return firstRun.get() && !finished.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFinished() {
|
||||
return finished.get();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a singler worker thread handling the asynchronous listener.
|
||||
*/
|
||||
public synchronized void start() {
|
||||
if (listener.getPlugin() == null)
|
||||
throw new IllegalArgumentException("Cannot start task without a valid plugin.");
|
||||
if (cancelled)
|
||||
throw new IllegalStateException("Cannot start a worker when the listener is closing.");
|
||||
|
||||
final AsyncRunnable listenerLoop = getListenerLoop();
|
||||
|
||||
stopWarningTask();
|
||||
scheduleAsync(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Thread thread = Thread.currentThread();
|
||||
|
||||
String previousName = thread.getName();
|
||||
String workerName = getFriendlyWorkerName(listenerLoop.getID());
|
||||
|
||||
// Add the friendly worker name
|
||||
thread.setName(workerName);
|
||||
listenerLoop.run();
|
||||
thread.setName(previousName);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a singler worker thread handling the asynchronous listener.
|
||||
* <p>
|
||||
* This method is intended to allow callers to customize the thread priority
|
||||
* before the worker loop is actually called. This is simpler than to
|
||||
* schedule the worker threads manually.
|
||||
* <pre><code>
|
||||
* listenerHandler.start(new Function<AsyncRunnable, Void>() {
|
||||
* @Override
|
||||
* public Void apply(@Nullable AsyncRunnable workerLoop) {
|
||||
* Thread thread = Thread.currentThread();
|
||||
* int prevPriority = thread.getPriority();
|
||||
*
|
||||
* thread.setPriority(Thread.MIN_PRIORITY);
|
||||
* workerLoop.run();
|
||||
* thread.setPriority(prevPriority);
|
||||
* return null;
|
||||
* }
|
||||
* });
|
||||
* }
|
||||
* </code></pre>
|
||||
* @param executor - a method that will execute the given listener loop.
|
||||
*/
|
||||
public synchronized void start(Function<AsyncRunnable, Void> executor) {
|
||||
if (listener.getPlugin() == null)
|
||||
throw new IllegalArgumentException("Cannot start task without a valid plugin.");
|
||||
if (cancelled)
|
||||
throw new IllegalStateException("Cannot start a worker when the listener is closing.");
|
||||
|
||||
final AsyncRunnable listenerLoop = getListenerLoop();
|
||||
final Function<AsyncRunnable, Void> delegateCopy = executor;
|
||||
|
||||
scheduleAsync(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
delegateCopy.apply(listenerLoop);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void scheduleAsync(Runnable runnable) {
|
||||
// Handle deprecation
|
||||
WrappedScheduler.runAsynchronouslyRepeat(listener.getPlugin(), filterManager.getScheduler(), runnable, 0L, -1L);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a friendly thread name using the following convention:
|
||||
* <p><code>
|
||||
* Protocol Worker {id} - {plugin} - [recv: {packets}, send: {packets}]
|
||||
* </code></p>
|
||||
* @param id - the worker ID.
|
||||
* @return A friendly thread name.
|
||||
*/
|
||||
public String getFriendlyWorkerName(int id) {
|
||||
return String.format("Protocol Worker #%s - %s - [recv: %s, send: %s]",
|
||||
id,
|
||||
PacketAdapter.getPluginName(listener),
|
||||
fromWhitelist(listener.getReceivingWhitelist()),
|
||||
fromWhitelist(listener.getSendingWhitelist())
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the given whitelist to a comma-separated list of packet IDs.
|
||||
* @param whitelist - the whitelist.
|
||||
* @return A comma separated list of packet IDs in the whitelist, or the emtpy string.
|
||||
*/
|
||||
private String fromWhitelist(ListeningWhitelist whitelist) {
|
||||
if (whitelist == null)
|
||||
return "";
|
||||
else
|
||||
return Joiner.on(", ").join(whitelist.getTypes());
|
||||
}
|
||||
|
||||
/**
|
||||
* Start processing packets on the main thread.
|
||||
* <p>
|
||||
* This is useful if you need to synchronize with the main thread in your packet listener, but
|
||||
* you're not performing any expensive processing.
|
||||
* <p>
|
||||
* <b>Note</b>: Use a asynchronous worker if the packet listener may use more than 0.5 ms
|
||||
* of processing time on a single packet. Do as much as possible on the worker thread, and schedule synchronous tasks
|
||||
* to use the Bukkit API instead.
|
||||
* @return TRUE if the synchronized processing was successfully started, FALSE if it's already running.
|
||||
* @throws IllegalStateException If we couldn't start the underlying task.
|
||||
*/
|
||||
public synchronized boolean syncStart() {
|
||||
return syncStart(500, TimeUnit.MICROSECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start processing packets on the main thread.
|
||||
* <p>
|
||||
* This is useful if you need to synchronize with the main thread in your packet listener, but
|
||||
* you're not performing any expensive processing.
|
||||
* <p>
|
||||
* The processing time parameter gives the upper bound for the amount of time spent processing pending packets.
|
||||
* It should be set to a fairly low number, such as 0.5 ms or 1% of a game tick - to reduce the impact
|
||||
* on the main thread. Never go beyond 50 milliseconds.
|
||||
* <p>
|
||||
* <b>Note</b>: Use a asynchronous worker if the packet listener may exceed the ideal processing time
|
||||
* on a single packet. Do as much as possible on the worker thread, and schedule synchronous tasks
|
||||
* to use the Bukkit API instead.
|
||||
*
|
||||
* @param time - the amount of processing time alloted per game tick (20 ticks per second).
|
||||
* @param unit - the unit of the processingTime argument.
|
||||
* @return TRUE if the synchronized processing was successfully started, FALSE if it's already running.
|
||||
* @throws IllegalStateException If we couldn't start the underlying task.
|
||||
*/
|
||||
public synchronized boolean syncStart(final long time, final TimeUnit unit) {
|
||||
if (time <= 0)
|
||||
throw new IllegalArgumentException("Time must be greater than zero.");
|
||||
if (unit == null)
|
||||
throw new IllegalArgumentException("TimeUnit cannot be NULL.");
|
||||
|
||||
final long tickDelay = 1;
|
||||
final int workerID = nextID.incrementAndGet();
|
||||
|
||||
if (syncTask < 0) {
|
||||
stopWarningTask();
|
||||
|
||||
syncTask = filterManager.getScheduler().scheduleSyncRepeatingTask(getPlugin(), new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
long stopTime = System.nanoTime() + unit.convert(time, TimeUnit.NANOSECONDS);
|
||||
|
||||
while (!cancelled) {
|
||||
PacketEvent packet = queuedPackets.poll();
|
||||
|
||||
if (packet == INTERUPT_PACKET || packet == WAKEUP_PACKET) {
|
||||
// Sorry, asynchronous threads!
|
||||
queuedPackets.add(packet);
|
||||
|
||||
// Try again next tick
|
||||
break;
|
||||
} else if (packet != null && packet.getAsyncMarker() != null) {
|
||||
processPacket(workerID, packet, "onSyncPacket()");
|
||||
} else {
|
||||
// No more packets left - wait a tick
|
||||
break;
|
||||
}
|
||||
|
||||
// Check time here, ensuring that we at least process one packet
|
||||
if (System.nanoTime() < stopTime)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}, tickDelay, tickDelay);
|
||||
|
||||
// This is very bad - force the caller to handle it
|
||||
if (syncTask < 0)
|
||||
throw new IllegalStateException("Cannot start synchronous task.");
|
||||
else
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop processing packets on the main thread.
|
||||
* @return TRUE if we stopped any processing tasks, FALSE if it has already been stopped.
|
||||
*/
|
||||
public synchronized boolean syncStop() {
|
||||
if (syncTask > 0) {
|
||||
filterManager.getScheduler().cancelTask(syncTask);
|
||||
|
||||
syncTask = -1;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start multiple worker threads for this listener.
|
||||
* @param count - number of worker threads to start.
|
||||
*/
|
||||
public synchronized void start(int count) {
|
||||
for (int i = 0; i < count; i++)
|
||||
start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop a worker thread.
|
||||
*/
|
||||
public synchronized void stop() {
|
||||
queuedPackets.add(INTERUPT_PACKET);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the given amount of worker threads.
|
||||
* @param count - number of threads to stop.
|
||||
*/
|
||||
public synchronized void stop(int count) {
|
||||
for (int i = 0; i < count; i++)
|
||||
stop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current number of workers.
|
||||
* <p>
|
||||
* This method can only be called with a count of zero when the listener is closing.
|
||||
* @param count - new number of workers.
|
||||
*/
|
||||
public synchronized void setWorkers(int count) {
|
||||
if (count < 0)
|
||||
throw new IllegalArgumentException("Number of workers cannot be less than zero.");
|
||||
if (count > DEFAULT_CAPACITY)
|
||||
throw new IllegalArgumentException("Cannot initiate more than " + DEFAULT_CAPACITY + " workers");
|
||||
if (cancelled && count > 0)
|
||||
throw new IllegalArgumentException("Cannot add workers when the listener is closing.");
|
||||
|
||||
long time = System.currentTimeMillis();
|
||||
|
||||
// Try to get to the correct count
|
||||
while (started.get() != count) {
|
||||
if (started.get() < count)
|
||||
start();
|
||||
else
|
||||
stop();
|
||||
|
||||
// May happen if another thread is doing something similar to "setWorkers"
|
||||
if ((System.currentTimeMillis() - time) > 50)
|
||||
throw new RuntimeException("Failed to set worker count.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the current number of registered workers.
|
||||
* <p>
|
||||
* Note that the returned value may be out of data.
|
||||
* @return Number of registered workers.
|
||||
*/
|
||||
public synchronized int getWorkers() {
|
||||
return started.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait until every tasks scheduled to stop has actually stopped.
|
||||
* @return TRUE if the current listener should stop, FALSE otherwise.
|
||||
* @throws InterruptedException - If the current thread was interrupted.
|
||||
*/
|
||||
private boolean waitForStops() throws InterruptedException {
|
||||
synchronized (stopLock) {
|
||||
while (stoppedTasks.size() > 0 && !cancelled) {
|
||||
stopLock.wait();
|
||||
}
|
||||
return cancelled;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The main processing loop of asynchronous threads.
|
||||
* <p>
|
||||
* Note: DO NOT call this method from the main thread
|
||||
* @param workerID - the current worker ID.
|
||||
*/
|
||||
private void listenerLoop(int workerID) {
|
||||
// Danger, danger!
|
||||
if (Thread.currentThread().getId() == mainThread.getId())
|
||||
throw new IllegalStateException("Do not call this method from the main thread.");
|
||||
if (cancelled)
|
||||
throw new IllegalStateException("Listener has been cancelled. Create a new listener instead.");
|
||||
|
||||
try {
|
||||
// Wait if certain threads are stopping
|
||||
if (waitForStops())
|
||||
return;
|
||||
|
||||
// Proceed
|
||||
started.incrementAndGet();
|
||||
|
||||
while (!cancelled) {
|
||||
PacketEvent packet = queuedPackets.take();
|
||||
|
||||
// Handle cancel requests
|
||||
if (packet == WAKEUP_PACKET) {
|
||||
// This is a bit slow, but it should be safe
|
||||
synchronized (stopLock) {
|
||||
// Are we the one who is supposed to stop?
|
||||
if (stoppedTasks.contains(workerID))
|
||||
return;
|
||||
if (waitForStops())
|
||||
return;
|
||||
}
|
||||
} else if (packet == INTERUPT_PACKET) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (packet != null && packet.getAsyncMarker() != null) {
|
||||
processPacket(workerID, packet, "onAsyncPacket()");
|
||||
}
|
||||
}
|
||||
|
||||
} catch (InterruptedException e) {
|
||||
// We're done
|
||||
} finally {
|
||||
// Clean up
|
||||
started.decrementAndGet();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a packet is scheduled for processing.
|
||||
* @param workerID - the current worker ID.
|
||||
* @param packet - the current packet.
|
||||
* @param methodName - name of the method.
|
||||
*/
|
||||
private void processPacket(int workerID, PacketEvent packet, String methodName) {
|
||||
AsyncMarker marker = packet.getAsyncMarker();
|
||||
|
||||
// Here's the core of the asynchronous processing
|
||||
try {
|
||||
synchronized (marker.getProcessingLock()) {
|
||||
marker.setListenerHandler(this);
|
||||
marker.setWorkerID(workerID);
|
||||
|
||||
// We're not THAT worried about performance here
|
||||
if (timedManager.isTiming()) {
|
||||
// Retrieve the tracker to use
|
||||
TimedTracker tracker = timedManager.getTracker(listener,
|
||||
packet.isServerPacket() ? ListenerType.ASYNC_SERVER_SIDE : ListenerType.ASYNC_CLIENT_SIDE);
|
||||
long token = tracker.beginTracking();
|
||||
|
||||
if (packet.isServerPacket())
|
||||
listener.onPacketSending(packet);
|
||||
else
|
||||
listener.onPacketReceiving(packet);
|
||||
|
||||
// And we're done
|
||||
tracker.endTracking(token, packet.getPacketType());
|
||||
|
||||
} else {
|
||||
if (packet.isServerPacket())
|
||||
listener.onPacketSending(packet);
|
||||
else
|
||||
listener.onPacketReceiving(packet);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (OutOfMemoryError e) {
|
||||
throw e;
|
||||
} catch (ThreadDeath e) {
|
||||
throw e;
|
||||
} catch (Throwable e) {
|
||||
// Minecraft doesn't want your Exception.
|
||||
filterManager.getErrorReporter().reportMinimal(listener.getPlugin(), methodName, e);
|
||||
}
|
||||
|
||||
// Now, get the next non-cancelled listener
|
||||
if (!marker.hasExpired()) {
|
||||
for (; marker.getListenerTraversal().hasNext(); ) {
|
||||
AsyncListenerHandler handler = marker.getListenerTraversal().next().getListener();
|
||||
|
||||
if (!handler.isCancelled()) {
|
||||
handler.enqueuePacket(packet);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// There are no more listeners - queue the packet for transmission
|
||||
filterManager.signalFreeProcessingSlot(packet);
|
||||
|
||||
// Note that listeners can opt to delay the packet transmission
|
||||
filterManager.signalPacketTransmission(packet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Close all worker threads and the handler itself.
|
||||
*/
|
||||
private synchronized void close() {
|
||||
// Remove the listener itself
|
||||
if (!cancelled) {
|
||||
filterManager.unregisterAsyncHandlerInternal(this);
|
||||
cancelled = true;
|
||||
|
||||
// Close processing tasks
|
||||
syncStop();
|
||||
|
||||
// Tell every uncancelled thread to end
|
||||
stopThreads();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Use the poision pill method to stop every worker thread.
|
||||
*/
|
||||
private void stopThreads() {
|
||||
// Poison Pill Shutdown
|
||||
queuedPackets.clear();
|
||||
stop(started.get());
|
||||
|
||||
// Individual shut down is irrelevant now
|
||||
synchronized (stopLock) {
|
||||
stopLock.notifyAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,485 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol.async;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import com.comphenix.protocol.PacketStream;
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.ProtocolLibrary;
|
||||
import com.comphenix.protocol.events.NetworkMarker;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.injector.PrioritizedListener;
|
||||
import com.comphenix.protocol.reflect.FieldAccessException;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
import com.comphenix.protocol.utility.MinecraftVersion;
|
||||
import com.google.common.primitives.Longs;
|
||||
|
||||
/**
|
||||
* Contains information about the packet that is being processed by asynchronous listeners.
|
||||
* <p>
|
||||
* Asynchronous listeners can use this to set packet timeout or transmission order.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
|
||||
|
||||
/**
|
||||
* Generated by Eclipse.
|
||||
*/
|
||||
private static final long serialVersionUID = -2621498096616187384L;
|
||||
|
||||
/**
|
||||
* Default number of milliseconds until a packet will rejected.
|
||||
*/
|
||||
public static final int DEFAULT_TIMEOUT_DELTA = 1800 * 1000;
|
||||
|
||||
/**
|
||||
* Default number of packets to skip.
|
||||
*/
|
||||
public static final int DEFAULT_SENDING_DELTA = 0;
|
||||
|
||||
/**
|
||||
* The packet stream responsible for transmitting the packet when it's done processing.
|
||||
*/
|
||||
private transient PacketStream packetStream;
|
||||
|
||||
/**
|
||||
* Current list of async packet listeners.
|
||||
*/
|
||||
private transient Iterator<PrioritizedListener<AsyncListenerHandler>> listenerTraversal;
|
||||
|
||||
// Timeout handling
|
||||
private long initialTime;
|
||||
private long timeout;
|
||||
|
||||
// Packet order
|
||||
private long originalSendingIndex;
|
||||
private long newSendingIndex;
|
||||
|
||||
// Used to determine if a packet must be reordered in the sending queue
|
||||
private Long queuedSendingIndex;
|
||||
|
||||
// Whether or not the packet has been processed by the listeners
|
||||
private volatile boolean processed;
|
||||
|
||||
// Whether or not the packet has been sent
|
||||
private volatile boolean transmitted;
|
||||
|
||||
// Whether or not the asynchronous processing itself should be cancelled
|
||||
private volatile boolean asyncCancelled;
|
||||
|
||||
// Whether or not to delay processing
|
||||
private AtomicInteger processingDelay = new AtomicInteger();
|
||||
|
||||
// Used to synchronize processing on the shared PacketEvent
|
||||
private Object processingLock = new Object();
|
||||
|
||||
// Used to identify the asynchronous worker
|
||||
private transient AsyncListenerHandler listenerHandler;
|
||||
private transient int workerID;
|
||||
|
||||
// Determine if Minecraft processes this packet asynchronously
|
||||
private volatile static Method isMinecraftAsync;
|
||||
private volatile static boolean alwaysSync;
|
||||
|
||||
/**
|
||||
* Create a container for asyncronous packets.
|
||||
* @param initialTime - the current time in milliseconds since 01.01.1970 00:00.
|
||||
*/
|
||||
AsyncMarker(PacketStream packetStream, long sendingIndex, long initialTime, long timeoutDelta) {
|
||||
if (packetStream == null)
|
||||
throw new IllegalArgumentException("packetStream cannot be NULL");
|
||||
|
||||
this.packetStream = packetStream;
|
||||
|
||||
// Timeout
|
||||
this.initialTime = initialTime;
|
||||
this.timeout = initialTime + timeoutDelta;
|
||||
|
||||
// Sending index
|
||||
this.originalSendingIndex = sendingIndex;
|
||||
this.newSendingIndex = sendingIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the time the packet was initially queued for asynchronous processing.
|
||||
* @return The initial time in number of milliseconds since 01.01.1970 00:00.
|
||||
*/
|
||||
public long getInitialTime() {
|
||||
return initialTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the time the packet will be forcefully rejected.
|
||||
* @return The time to reject the packet, in milliseconds since 01.01.1970 00:00.
|
||||
*/
|
||||
public long getTimeout() {
|
||||
return timeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the time the packet will be forcefully rejected.
|
||||
* @param timeout - time to reject the packet, in milliseconds since 01.01.1970 00:00.
|
||||
*/
|
||||
public void setTimeout(long timeout) {
|
||||
this.timeout = timeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the order the packet was originally transmitted.
|
||||
* @return The original packet index.
|
||||
*/
|
||||
public long getOriginalSendingIndex() {
|
||||
return originalSendingIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the desired sending order after processing has completed.
|
||||
* <p>
|
||||
* Higher sending order means lower priority.
|
||||
* @return Desired sending order.
|
||||
*/
|
||||
public long getNewSendingIndex() {
|
||||
return newSendingIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the desired sending order after processing has completed.
|
||||
* <p>
|
||||
* Higher sending order means lower priority.
|
||||
* @param newSendingIndex - new packet send index.
|
||||
*/
|
||||
public void setNewSendingIndex(long newSendingIndex) {
|
||||
this.newSendingIndex = newSendingIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the packet stream responsible for transmitting this packet.
|
||||
* @return The packet stream.
|
||||
*/
|
||||
public PacketStream getPacketStream() {
|
||||
return packetStream;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the output packet stream responsible for transmitting this packet.
|
||||
* @param packetStream - new output packet stream.
|
||||
*/
|
||||
public void setPacketStream(PacketStream packetStream) {
|
||||
this.packetStream = packetStream;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve whether or not this packet has been processed by the async listeners.
|
||||
* @return TRUE if it has been processed, FALSE otherwise.
|
||||
*/
|
||||
public boolean isProcessed() {
|
||||
return processed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether or not this packet has been processed by the async listeners.
|
||||
* @param processed - TRUE if it has, FALSE otherwise.
|
||||
*/
|
||||
void setProcessed(boolean processed) {
|
||||
this.processed = processed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment the number of times the current packet must be signalled as done before its transmitted.
|
||||
* <p>
|
||||
* This is useful if an asynchronous listener is waiting for further information before the
|
||||
* packet can be sent to the user. A packet listener <b>MUST</b> eventually call
|
||||
* {@link AsyncFilterManager#signalPacketTransmission(PacketEvent)},
|
||||
* even if the packet is cancelled, after this method is called.
|
||||
* <p>
|
||||
* It is recommended that processing outside a packet listener is wrapped in a synchronized block
|
||||
* using the {@link #getProcessingLock()} method.
|
||||
*
|
||||
* @return The new processing delay.
|
||||
*/
|
||||
public int incrementProcessingDelay() {
|
||||
return processingDelay.incrementAndGet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrement the number of times this packet must be signalled as done before it's transmitted.
|
||||
* @return The new processing delay. If zero, the packet should be sent.
|
||||
*/
|
||||
int decrementProcessingDelay() {
|
||||
return processingDelay.decrementAndGet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the number of times a packet must be signalled to be done before it's sent.
|
||||
* @return Number of processing delays.
|
||||
*/
|
||||
public int getProcessingDelay() {
|
||||
return processingDelay.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not this packet is or has been queued for processing.
|
||||
* @return TRUE if it has, FALSE otherwise.
|
||||
*/
|
||||
public boolean isQueued() {
|
||||
return queuedSendingIndex != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the sending index when the packet was queued.
|
||||
* @return Queued sending index.
|
||||
*/
|
||||
public long getQueuedSendingIndex() {
|
||||
return queuedSendingIndex != null ? queuedSendingIndex : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the sending index when the packet was queued.
|
||||
* @param queuedSendingIndex - sending index.
|
||||
*/
|
||||
void setQueuedSendingIndex(Long queuedSendingIndex) {
|
||||
this.queuedSendingIndex = queuedSendingIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processing lock used to synchronize access to the parent PacketEvent and PacketContainer.
|
||||
* <p>
|
||||
* This lock is automatically acquired for every asynchronous packet listener. It should only be
|
||||
* used to synchronize access to a PacketEvent if it's processing has been delayed.
|
||||
* @return A processing lock.
|
||||
*/
|
||||
public Object getProcessingLock() {
|
||||
return processingLock;
|
||||
}
|
||||
|
||||
public void setProcessingLock(Object processingLock) {
|
||||
this.processingLock = processingLock;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve whether or not this packet has already been sent.
|
||||
* @return TRUE if it has been sent before, FALSE otherwise.
|
||||
*/
|
||||
public boolean isTransmitted() {
|
||||
return transmitted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if this packet has expired.
|
||||
* @return TRUE if it has, FALSE otherwise.
|
||||
*/
|
||||
public boolean hasExpired() {
|
||||
return hasExpired(System.currentTimeMillis());
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if this packet has expired given this time.
|
||||
* @param currentTime - the current time in milliseconds since 01.01.1970 00:00.
|
||||
* @return TRUE if it has, FALSE otherwise.
|
||||
*/
|
||||
public boolean hasExpired(long currentTime) {
|
||||
return timeout < currentTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the asynchronous handling should be cancelled.
|
||||
* @return TRUE if it should, FALSE otherwise.
|
||||
*/
|
||||
public boolean isAsyncCancelled() {
|
||||
return asyncCancelled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether or not the asynchronous handling should be cancelled.
|
||||
* <p>
|
||||
* This is only relevant during the synchronous processing. Asynchronous
|
||||
* listeners should use the normal cancel-field to cancel a PacketEvent.
|
||||
*
|
||||
* @param asyncCancelled - TRUE to cancel it, FALSE otherwise.
|
||||
*/
|
||||
public void setAsyncCancelled(boolean asyncCancelled) {
|
||||
this.asyncCancelled = asyncCancelled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the current asynchronous listener handler.
|
||||
* @return Asychronous listener handler, or NULL if this packet is not asynchronous.
|
||||
*/
|
||||
public AsyncListenerHandler getListenerHandler() {
|
||||
return listenerHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current asynchronous listener handler.
|
||||
* <p>
|
||||
* Used by the worker to update the value.
|
||||
* @param listenerHandler - new listener handler.
|
||||
*/
|
||||
void setListenerHandler(AsyncListenerHandler listenerHandler) {
|
||||
this.listenerHandler = listenerHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the current worker ID.
|
||||
* @return Current worker ID.
|
||||
*/
|
||||
public int getWorkerID() {
|
||||
return workerID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current worker ID.
|
||||
* <p>
|
||||
* Used by the worker.
|
||||
* @param workerID - new worker ID.
|
||||
*/
|
||||
void setWorkerID(int workerID) {
|
||||
this.workerID = workerID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve iterator for the next listener in line.
|
||||
* @return Next async packet listener iterator.
|
||||
*/
|
||||
Iterator<PrioritizedListener<AsyncListenerHandler>> getListenerTraversal() {
|
||||
return listenerTraversal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the iterator for the next listener.
|
||||
* @param listenerTraversal - the new async packet listener iterator.
|
||||
*/
|
||||
void setListenerTraversal(Iterator<PrioritizedListener<AsyncListenerHandler>> listenerTraversal) {
|
||||
this.listenerTraversal = listenerTraversal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transmit a given packet to the current packet stream.
|
||||
* @param event - the packet to send.
|
||||
* @throws IOException If the packet couldn't be sent.
|
||||
*/
|
||||
void sendPacket(PacketEvent event) throws IOException {
|
||||
try {
|
||||
if (event.isServerPacket()) {
|
||||
packetStream.sendServerPacket(event.getPlayer(), event.getPacket(), NetworkMarker.getNetworkMarker(event), false);
|
||||
} else {
|
||||
packetStream.recieveClientPacket(event.getPlayer(), event.getPacket(), NetworkMarker.getNetworkMarker(event), false);
|
||||
}
|
||||
transmitted = true;
|
||||
|
||||
} catch (InvocationTargetException e) {
|
||||
throw new IOException("Cannot send packet", e);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new IOException("Cannot send packet", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if Minecraft allows asynchronous processing of this packet.
|
||||
* @param event - packet event
|
||||
* @return TRUE if it does, FALSE otherwise.
|
||||
* @throws FieldAccessException If determining fails for some reasaon
|
||||
*/
|
||||
public boolean isMinecraftAsync(PacketEvent event) throws FieldAccessException {
|
||||
if (isMinecraftAsync == null && !alwaysSync) {
|
||||
try {
|
||||
isMinecraftAsync = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass()).getMethodByName("a_.*");
|
||||
} catch (RuntimeException e) {
|
||||
// This will occur in 1.2.5 (or possibly in later versions)
|
||||
List<Method> methods = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass()).
|
||||
getMethodListByParameters(boolean.class, new Class[] {});
|
||||
|
||||
// Try to look for boolean methods
|
||||
if (methods.size() == 2) {
|
||||
isMinecraftAsync = methods.get(1);
|
||||
} else if (methods.size() == 1) {
|
||||
// We're in 1.2.5
|
||||
alwaysSync = true;
|
||||
} else if (MinecraftVersion.getCurrentVersion().isAtLeast(MinecraftVersion.BOUNTIFUL_UPDATE)) {
|
||||
// The centralized async marker was removed in 1.8
|
||||
// Incoming chat packets can be async
|
||||
if (event.getPacketType() == PacketType.Play.Client.CHAT) {
|
||||
String content = event.getPacket().getStrings().readSafely(0);
|
||||
if (content != null) {
|
||||
// Incoming chat packets are async only if they aren't commands
|
||||
return ! content.startsWith("/");
|
||||
} else {
|
||||
ProtocolLibrary.log(Level.WARNING, "Failed to determine contents of incoming chat packet!");
|
||||
alwaysSync = true;
|
||||
}
|
||||
} else {
|
||||
// TODO: Find more cases of async packets
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
ProtocolLibrary.log(Level.INFO, "Could not determine asynchronous state of packets (this can probably be ignored)");
|
||||
alwaysSync = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (alwaysSync) {
|
||||
return false;
|
||||
} else {
|
||||
try {
|
||||
// Wrap exceptions
|
||||
return (Boolean) isMinecraftAsync.invoke(event.getPacket().getHandle());
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new FieldAccessException("Illegal argument", e);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new FieldAccessException("Unable to reflect method call 'a_', or: isAsyncPacket.", e);
|
||||
} catch (InvocationTargetException e) {
|
||||
throw new FieldAccessException("Minecraft error", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(AsyncMarker o) {
|
||||
if (o == null)
|
||||
return 1;
|
||||
else
|
||||
return Longs.compare(getNewSendingIndex(), o.getNewSendingIndex());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
// Standard equals
|
||||
if (other == this)
|
||||
return true;
|
||||
if (other instanceof AsyncMarker)
|
||||
return getNewSendingIndex() == ((AsyncMarker) other).getNewSendingIndex();
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Longs.hashCode(getNewSendingIndex());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* A runnable representing a asynchronous event listener.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public interface AsyncRunnable extends Runnable {
|
||||
|
||||
/**
|
||||
* Retrieve a unique worker ID.
|
||||
* @return Unique worker ID.
|
||||
*/
|
||||
public int getID();
|
||||
|
||||
/**
|
||||
* Stop the given runnable.
|
||||
* <p>
|
||||
* This may not occur right away.
|
||||
* @return TRUE if the thread was stopped, FALSE if it was already stopped.
|
||||
* @throws InterruptedException if it is interrupted
|
||||
*/
|
||||
public boolean stop() throws InterruptedException;
|
||||
|
||||
/**
|
||||
* Determine if we're running or not.
|
||||
* @return TRUE if we're running, FALSE otherwise.
|
||||
*/
|
||||
public boolean isRunning();
|
||||
|
||||
/**
|
||||
* Determine if this runnable has already run its course.
|
||||
* @return TRUE if it has been stopped, FALSE otherwise.
|
||||
*/
|
||||
boolean isFinished();
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol.async;
|
||||
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import com.comphenix.protocol.events.ListenerOptions;
|
||||
import com.comphenix.protocol.events.ListenerPriority;
|
||||
import com.comphenix.protocol.events.ListeningWhitelist;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.events.PacketListener;
|
||||
|
||||
/**
|
||||
* Represents a NO OPERATION listener.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
class NullPacketListener implements PacketListener {
|
||||
|
||||
private ListeningWhitelist sendingWhitelist;
|
||||
private ListeningWhitelist receivingWhitelist;
|
||||
private Plugin plugin;
|
||||
|
||||
/**
|
||||
* Create a no-op listener with the same whitelist and plugin as the given listener.
|
||||
* @param original - the packet listener to copy.
|
||||
*/
|
||||
public NullPacketListener(PacketListener original) {
|
||||
this.sendingWhitelist = cloneWhitelist(ListenerPriority.LOW, original.getSendingWhitelist());
|
||||
this.receivingWhitelist = cloneWhitelist(ListenerPriority.LOW, original.getReceivingWhitelist());
|
||||
this.plugin = original.getPlugin();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketSending(PacketEvent event) {
|
||||
// NULL
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketReceiving(PacketEvent event) {
|
||||
// NULL
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListeningWhitelist getSendingWhitelist() {
|
||||
return sendingWhitelist;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListeningWhitelist getReceivingWhitelist() {
|
||||
return receivingWhitelist;
|
||||
}
|
||||
|
||||
private ListeningWhitelist cloneWhitelist(ListenerPriority priority, ListeningWhitelist whitelist) {
|
||||
if (whitelist != null)
|
||||
// We don't use the Bukkit API, so don't engage the ProtocolLib synchronization code
|
||||
return ListeningWhitelist.newBuilder(whitelist).priority(priority).mergeOptions(ListenerOptions.ASYNC).build();
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Plugin getPlugin() {
|
||||
return plugin;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* 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 com.comphenix.protocol.events.PacketEvent;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.ComparisonChain;
|
||||
import com.google.common.primitives.Longs;
|
||||
|
||||
/**
|
||||
* Provides a comparable to a packet event.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
class PacketEventHolder implements Comparable<PacketEventHolder> {
|
||||
|
||||
private PacketEvent event;
|
||||
private long sendingIndex = 0;
|
||||
|
||||
/**
|
||||
* A wrapper that ensures the packet event is ordered by sending index.
|
||||
* @param event - packet event to wrap.
|
||||
*/
|
||||
public PacketEventHolder(PacketEvent event) {
|
||||
this.event = Preconditions.checkNotNull(event, "Event must be non-null");
|
||||
|
||||
if (event.getAsyncMarker() != null)
|
||||
this.sendingIndex = event.getAsyncMarker().getNewSendingIndex();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the stored event.
|
||||
* @return The stored event.
|
||||
*/
|
||||
public PacketEvent getEvent() {
|
||||
return event;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(PacketEventHolder other) {
|
||||
return ComparisonChain.start().
|
||||
compare(sendingIndex, other.sendingIndex).
|
||||
result();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
// Standard equals
|
||||
if (other == this)
|
||||
return true;
|
||||
if (other instanceof PacketEventHolder)
|
||||
return sendingIndex == ((PacketEventHolder) other).sendingIndex;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Longs.hashCode(sendingIndex);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol.async;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.PriorityQueue;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.Semaphore;
|
||||
|
||||
import com.comphenix.protocol.ProtocolLibrary;
|
||||
import com.comphenix.protocol.concurrency.AbstractConcurrentListenerMultimap;
|
||||
import com.comphenix.protocol.error.Report;
|
||||
import com.comphenix.protocol.error.ReportType;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.injector.PrioritizedListener;
|
||||
import com.google.common.collect.MinMaxPriorityQueue;
|
||||
|
||||
|
||||
/**
|
||||
* Handles the processing of every packet type.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
class PacketProcessingQueue extends AbstractConcurrentListenerMultimap<AsyncListenerHandler> {
|
||||
public static final ReportType REPORT_GUAVA_CORRUPT_MISSING =
|
||||
new ReportType("Guava is either missing or corrupt. Reverting to PriorityQueue.");
|
||||
|
||||
// Initial number of elements
|
||||
public static final int INITIAL_CAPACITY = 64;
|
||||
|
||||
/**
|
||||
* Default maximum number of packets to process concurrently.
|
||||
*/
|
||||
public static final int DEFAULT_MAXIMUM_CONCURRENCY = 32;
|
||||
|
||||
/**
|
||||
* Default maximum number of packets to queue for processing.
|
||||
*/
|
||||
public static final int DEFAULT_QUEUE_LIMIT = 1024 * 60;
|
||||
|
||||
/**
|
||||
* Number of packets we're processing concurrently.
|
||||
*/
|
||||
private final int maximumConcurrency;
|
||||
private Semaphore concurrentProcessing;
|
||||
|
||||
// Queued packets for being processed
|
||||
private Queue<PacketEventHolder> processingQueue;
|
||||
|
||||
// Packets for sending
|
||||
private PlayerSendingHandler sendingHandler;
|
||||
|
||||
public PacketProcessingQueue(PlayerSendingHandler sendingHandler) {
|
||||
this(sendingHandler, INITIAL_CAPACITY, DEFAULT_QUEUE_LIMIT, DEFAULT_MAXIMUM_CONCURRENCY);
|
||||
}
|
||||
|
||||
public PacketProcessingQueue(PlayerSendingHandler sendingHandler, int initialSize, int maximumSize, int maximumConcurrency) {
|
||||
super();
|
||||
|
||||
try {
|
||||
this.processingQueue = Synchronization.queue(MinMaxPriorityQueue.
|
||||
expectedSize(initialSize).
|
||||
maximumSize(maximumSize).
|
||||
<PacketEventHolder>create(), null);
|
||||
} catch (IncompatibleClassChangeError e) {
|
||||
// Print in the console
|
||||
ProtocolLibrary.getErrorReporter().reportWarning(
|
||||
this, Report.newBuilder(REPORT_GUAVA_CORRUPT_MISSING).error(e));
|
||||
|
||||
// It's a Beta class after all
|
||||
this.processingQueue = Synchronization.queue(
|
||||
new PriorityQueue<PacketEventHolder>(), null);
|
||||
}
|
||||
|
||||
this.maximumConcurrency = maximumConcurrency;
|
||||
this.concurrentProcessing = new Semaphore(maximumConcurrency);
|
||||
this.sendingHandler = sendingHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue a packet for processing by the asynchronous listeners.
|
||||
* @param packet - packet to process.
|
||||
* @param onMainThread - whether or not this is occuring on the main thread.
|
||||
* @return TRUE if we sucessfully queued the packet, FALSE if the queue ran out if space.
|
||||
*/
|
||||
public boolean enqueue(PacketEvent packet, boolean onMainThread) {
|
||||
try {
|
||||
processingQueue.add(new PacketEventHolder(packet));
|
||||
|
||||
// Begin processing packets
|
||||
signalBeginProcessing(onMainThread);
|
||||
return true;
|
||||
} catch (IllegalStateException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Number of packet events in the queue.
|
||||
* @return The number of packet events in the queue.
|
||||
*/
|
||||
public int size() {
|
||||
return processingQueue.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the current method and each thread to signal that a packet might be ready for processing.
|
||||
* @param onMainThread - whether or not this is occuring on the main thread.
|
||||
*/
|
||||
public void signalBeginProcessing(boolean onMainThread) {
|
||||
while (concurrentProcessing.tryAcquire()) {
|
||||
PacketEventHolder holder = processingQueue.poll();
|
||||
|
||||
// Any packet queued?
|
||||
if (holder != null) {
|
||||
PacketEvent packet = holder.getEvent();
|
||||
AsyncMarker marker = packet.getAsyncMarker();
|
||||
Collection<PrioritizedListener<AsyncListenerHandler>> list = getListener(packet.getPacketType());
|
||||
|
||||
marker.incrementProcessingDelay();
|
||||
|
||||
// Yes, removing the marker will cause the chain to stop
|
||||
if (list != null) {
|
||||
Iterator<PrioritizedListener<AsyncListenerHandler>> iterator = list.iterator();
|
||||
|
||||
if (iterator.hasNext()) {
|
||||
marker.setListenerTraversal(iterator);
|
||||
iterator.next().getListener().enqueuePacket(packet);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// The packet has no further listeners. Just send it.
|
||||
if (marker.decrementProcessingDelay() == 0) {
|
||||
PacketSendingQueue sendingQueue = sendingHandler.getSendingQueue(packet, false);
|
||||
|
||||
// In case the player has logged out
|
||||
if (sendingQueue != null)
|
||||
sendingQueue.signalPacketUpdate(packet, onMainThread);
|
||||
}
|
||||
signalProcessingDone();
|
||||
|
||||
} else {
|
||||
// No more queued packets.
|
||||
signalProcessingDone();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a packet has been processed.
|
||||
*/
|
||||
public void signalProcessingDone() {
|
||||
concurrentProcessing.release();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the maximum number of packets to process at any given time.
|
||||
* @return Number of simultaneous packet to process.
|
||||
*/
|
||||
public int getMaximumConcurrency() {
|
||||
return maximumConcurrency;
|
||||
}
|
||||
|
||||
public void cleanupAll() {
|
||||
// Cancel all the threads and every listener
|
||||
for (PrioritizedListener<AsyncListenerHandler> handler : values()) {
|
||||
if (handler != null) {
|
||||
handler.getListener().cancel();
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the rest, just in case
|
||||
clearListeners();
|
||||
|
||||
// Remove every packet in the queue
|
||||
processingQueue.clear();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,307 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol.async;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.PriorityBlockingQueue;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.ProtocolLibrary;
|
||||
import com.comphenix.protocol.error.Report;
|
||||
import com.comphenix.protocol.error.ReportType;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.injector.PlayerLoggedOutException;
|
||||
import com.comphenix.protocol.reflect.FieldAccessException;
|
||||
|
||||
/**
|
||||
* Represents packets ready to be transmitted to a client.
|
||||
* @author Kristian
|
||||
*/
|
||||
abstract class PacketSendingQueue {
|
||||
public static final ReportType REPORT_DROPPED_PACKET = new ReportType("Warning: Dropped packet index %s of type %s.");
|
||||
|
||||
public static final int INITIAL_CAPACITY = 10;
|
||||
|
||||
private PriorityBlockingQueue<PacketEventHolder> sendingQueue;
|
||||
|
||||
// Asynchronous packet sending
|
||||
private Executor asynchronousSender;
|
||||
// Whether or not packet transmission must occur on a specific thread
|
||||
private final boolean notThreadSafe;
|
||||
// Whether or not we've run the cleanup procedure
|
||||
private boolean cleanedUp = false;
|
||||
|
||||
/**
|
||||
* Create a packet sending queue.
|
||||
* @param notThreadSafe - whether or not to synchronize with the main thread or a background thread.
|
||||
*/
|
||||
public PacketSendingQueue(boolean notThreadSafe, Executor asynchronousSender) {
|
||||
this.sendingQueue = new PriorityBlockingQueue<PacketEventHolder>(INITIAL_CAPACITY);
|
||||
this.notThreadSafe = notThreadSafe;
|
||||
this.asynchronousSender = asynchronousSender;
|
||||
}
|
||||
|
||||
/**
|
||||
* Number of packet events in the queue.
|
||||
* @return The number of packet events in the queue.
|
||||
*/
|
||||
public int size() {
|
||||
return sendingQueue.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue a packet for sending.
|
||||
* @param packet - packet to queue.
|
||||
*/
|
||||
public void enqueue(PacketEvent packet) {
|
||||
sendingQueue.add(new PacketEventHolder(packet));
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when one of the packets have finished processing.
|
||||
* @param packetUpdated - the packet that has now been updated.
|
||||
* @param onMainThread - whether or not this is occuring on the main thread.
|
||||
*/
|
||||
public synchronized void signalPacketUpdate(PacketEvent packetUpdated, boolean onMainThread) {
|
||||
|
||||
AsyncMarker marker = packetUpdated.getAsyncMarker();
|
||||
|
||||
// Should we reorder the event?
|
||||
if (marker.getQueuedSendingIndex() != marker.getNewSendingIndex() && !marker.hasExpired()) {
|
||||
PacketEvent copy = PacketEvent.fromSynchronous(packetUpdated, marker);
|
||||
|
||||
// "Cancel" the original event
|
||||
packetUpdated.setReadOnly(false);
|
||||
packetUpdated.setCancelled(true);
|
||||
|
||||
// Enqueue the copy with the new sending index
|
||||
enqueue(copy);
|
||||
}
|
||||
|
||||
// Mark this packet as finished
|
||||
marker.setProcessed(true);
|
||||
trySendPackets(onMainThread);
|
||||
}
|
||||
|
||||
/***
|
||||
* Invoked when a list of packet IDs are no longer associated with any listeners.
|
||||
* @param packetsRemoved - packets that no longer have any listeners.
|
||||
* @param onMainThread - whether or not this is occuring on the main thread.
|
||||
*/
|
||||
public synchronized void signalPacketUpdate(List<PacketType> packetsRemoved, boolean onMainThread) {
|
||||
Set<PacketType> lookup = new HashSet<PacketType>(packetsRemoved);
|
||||
|
||||
// Note that this is O(n), so it might be expensive
|
||||
for (PacketEventHolder holder : sendingQueue) {
|
||||
PacketEvent event = holder.getEvent();
|
||||
|
||||
if (lookup.contains(event.getPacketType())) {
|
||||
event.getAsyncMarker().setProcessed(true);
|
||||
}
|
||||
}
|
||||
|
||||
// This is likely to have changed the situation a bit
|
||||
trySendPackets(onMainThread);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to send any remaining packets.
|
||||
* @param onMainThread - whether or not this is occuring on the main thread.
|
||||
*/
|
||||
public void trySendPackets(boolean onMainThread) {
|
||||
// Whether or not to continue sending packets
|
||||
boolean sending = true;
|
||||
|
||||
// Transmit as many packets as we can
|
||||
while (sending) {
|
||||
PacketEventHolder holder = sendingQueue.poll();
|
||||
|
||||
if (holder != null) {
|
||||
sending = processPacketHolder(onMainThread, holder);
|
||||
|
||||
if (!sending) {
|
||||
// Add it back again
|
||||
sendingQueue.add(holder);
|
||||
}
|
||||
|
||||
} else {
|
||||
// No more packets to send
|
||||
sending = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when a packet might be ready for transmission.
|
||||
* @param onMainThread - TRUE if we're on the main thread, FALSE otherwise.
|
||||
* @param holder - packet container.
|
||||
* @return TRUE to continue sending packets, FALSE otherwise.
|
||||
*/
|
||||
private boolean processPacketHolder(boolean onMainThread, final PacketEventHolder holder) {
|
||||
PacketEvent current = holder.getEvent();
|
||||
AsyncMarker marker = current.getAsyncMarker();
|
||||
boolean hasExpired = marker.hasExpired();
|
||||
|
||||
// Guard in cause the queue is closed
|
||||
if (cleanedUp) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// End condition?
|
||||
if (marker.isProcessed() || hasExpired) {
|
||||
if (hasExpired) {
|
||||
// Notify timeout listeners
|
||||
onPacketTimeout(current);
|
||||
|
||||
// Recompute
|
||||
marker = current.getAsyncMarker();
|
||||
hasExpired = marker.hasExpired();
|
||||
|
||||
// Could happen due to the timeout listeners
|
||||
if (!marker.isProcessed() && !hasExpired) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Is it okay to send the packet?
|
||||
if (!current.isCancelled() && !hasExpired) {
|
||||
// Make sure we're on the main thread
|
||||
if (notThreadSafe) {
|
||||
try {
|
||||
boolean wantAsync = marker.isMinecraftAsync(current);
|
||||
boolean wantSync = !wantAsync;
|
||||
|
||||
// Wait for the next main thread heartbeat if we haven't fulfilled our promise
|
||||
if (!onMainThread && wantSync) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Let's give it what it wants
|
||||
if (onMainThread && wantAsync) {
|
||||
asynchronousSender.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// We know this isn't on the main thread
|
||||
processPacketHolder(false, holder);
|
||||
}
|
||||
});
|
||||
|
||||
// Scheduler will do the rest
|
||||
return true;
|
||||
}
|
||||
|
||||
} catch (FieldAccessException e) {
|
||||
e.printStackTrace();
|
||||
|
||||
// Just drop the packet
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Silently skip players that have logged out
|
||||
if (isOnline(current.getPlayer())) {
|
||||
sendPacket(current);
|
||||
}
|
||||
}
|
||||
|
||||
// Drop the packet
|
||||
return true;
|
||||
}
|
||||
|
||||
// Add it back and stop sending
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when a packet has timed out.
|
||||
* @param event - the timed out packet.
|
||||
*/
|
||||
protected abstract void onPacketTimeout(PacketEvent event);
|
||||
|
||||
private boolean isOnline(Player player) {
|
||||
return player != null && player.isOnline();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send every packet, regardless of the processing state.
|
||||
*/
|
||||
private void forceSend() {
|
||||
while (true) {
|
||||
PacketEventHolder holder = sendingQueue.poll();
|
||||
|
||||
if (holder != null) {
|
||||
sendPacket(holder.getEvent());
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not the packet transmission must synchronize with the main thread.
|
||||
* @return TRUE if it must, FALSE otherwise.
|
||||
*/
|
||||
public boolean isSynchronizeMain() {
|
||||
return notThreadSafe;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transmit a packet, if it hasn't already.
|
||||
* @param event - the packet to transmit.
|
||||
*/
|
||||
private void sendPacket(PacketEvent event) {
|
||||
|
||||
AsyncMarker marker = event.getAsyncMarker();
|
||||
|
||||
try {
|
||||
// Don't send a packet twice
|
||||
if (marker != null && !marker.isTransmitted()) {
|
||||
marker.sendPacket(event);
|
||||
}
|
||||
|
||||
} catch (PlayerLoggedOutException e) {
|
||||
ProtocolLibrary.getErrorReporter().reportDebug(this, Report.newBuilder(REPORT_DROPPED_PACKET).
|
||||
messageParam(marker.getOriginalSendingIndex(), event.getPacketType()).
|
||||
callerParam(event)
|
||||
);
|
||||
|
||||
} catch (IOException e) {
|
||||
// Just print the error
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatically transmits every delayed packet.
|
||||
*/
|
||||
public void cleanupAll() {
|
||||
if (!cleanedUp) {
|
||||
// Note that the cleanup itself will always occur on the main thread
|
||||
forceSend();
|
||||
|
||||
// And we're done
|
||||
cleanedUp = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,264 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol.async;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.concurrency.ConcurrentPlayerMap;
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.injector.SortedPacketListenerList;
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
|
||||
/**
|
||||
* Contains every sending queue for every player.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
class PlayerSendingHandler {
|
||||
private ErrorReporter reporter;
|
||||
private ConcurrentMap<Player, QueueContainer> playerSendingQueues;
|
||||
|
||||
// Timeout listeners
|
||||
private SortedPacketListenerList serverTimeoutListeners;
|
||||
private SortedPacketListenerList clientTimeoutListeners;
|
||||
|
||||
// Asynchronous packet sending
|
||||
private Executor asynchronousSender;
|
||||
|
||||
// Whether or not we're currently cleaning up
|
||||
private volatile boolean cleaningUp;
|
||||
|
||||
/**
|
||||
* Sending queues for a given player.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
private class QueueContainer {
|
||||
private PacketSendingQueue serverQueue;
|
||||
private PacketSendingQueue clientQueue;
|
||||
|
||||
public QueueContainer() {
|
||||
// Server packets can be sent concurrently
|
||||
serverQueue = new PacketSendingQueue(false, asynchronousSender) {
|
||||
@Override
|
||||
protected void onPacketTimeout(PacketEvent event) {
|
||||
if (!cleaningUp) {
|
||||
serverTimeoutListeners.invokePacketSending(reporter, event);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Client packets must be synchronized
|
||||
clientQueue = new PacketSendingQueue(true, asynchronousSender) {
|
||||
@Override
|
||||
protected void onPacketTimeout(PacketEvent event) {
|
||||
if (!cleaningUp) {
|
||||
clientTimeoutListeners.invokePacketSending(reporter, event);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public PacketSendingQueue getServerQueue() {
|
||||
return serverQueue;
|
||||
}
|
||||
|
||||
public PacketSendingQueue getClientQueue() {
|
||||
return clientQueue;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a packet sending handler.
|
||||
* @param reporter - error reporter.
|
||||
* @param serverTimeoutListeners - set of server timeout listeners.
|
||||
* @param clientTimeoutListeners - set of client timeout listeners.
|
||||
*/
|
||||
public PlayerSendingHandler(ErrorReporter reporter,
|
||||
SortedPacketListenerList serverTimeoutListeners, SortedPacketListenerList clientTimeoutListeners) {
|
||||
|
||||
this.reporter = reporter;
|
||||
this.serverTimeoutListeners = serverTimeoutListeners;
|
||||
this.clientTimeoutListeners = clientTimeoutListeners;
|
||||
|
||||
// Initialize storage of queues
|
||||
this.playerSendingQueues = ConcurrentPlayerMap.usingAddress();
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the asynchronous packet sender.
|
||||
*/
|
||||
public synchronized void initializeScheduler() {
|
||||
if (asynchronousSender == null) {
|
||||
ThreadFactory factory = new ThreadFactoryBuilder().
|
||||
setDaemon(true).
|
||||
setNameFormat("ProtocolLib-AsyncSender %s").
|
||||
build();
|
||||
asynchronousSender = Executors.newSingleThreadExecutor(factory);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the sending queue this packet belongs to.
|
||||
* @param packet - the packet.
|
||||
* @return The server or client sending queue the packet belongs to.
|
||||
*/
|
||||
public PacketSendingQueue getSendingQueue(PacketEvent packet) {
|
||||
return getSendingQueue(packet, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the sending queue this packet belongs to.
|
||||
* @param packet - the packet.
|
||||
* @param createNew - if TRUE, create a new queue if it hasn't already been created.
|
||||
* @return The server or client sending queue the packet belongs to.
|
||||
*/
|
||||
public PacketSendingQueue getSendingQueue(PacketEvent packet, boolean createNew) {
|
||||
QueueContainer queues = playerSendingQueues.get(packet.getPlayer());
|
||||
|
||||
// Safe concurrent initialization
|
||||
if (queues == null && createNew) {
|
||||
final QueueContainer newContainer = new QueueContainer();
|
||||
|
||||
// Attempt to map the queue
|
||||
queues = playerSendingQueues.putIfAbsent(packet.getPlayer(), newContainer);
|
||||
|
||||
if (queues == null) {
|
||||
queues = newContainer;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for NULL again
|
||||
if (queues != null)
|
||||
return packet.isServerPacket() ? queues.getServerQueue() : queues.getClientQueue();
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send all pending packets.
|
||||
*/
|
||||
public void sendAllPackets() {
|
||||
if (!cleaningUp) {
|
||||
for (QueueContainer queues : playerSendingQueues.values()) {
|
||||
queues.getClientQueue().cleanupAll();
|
||||
queues.getServerQueue().cleanupAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Immediately send every server packet with the given list of IDs.
|
||||
* @param types - types of every packet to send immediately.
|
||||
* @param synchronusOK - whether or not we're running on the main thread.
|
||||
*/
|
||||
public void sendServerPackets(List<PacketType> types, boolean synchronusOK) {
|
||||
if (!cleaningUp) {
|
||||
for (QueueContainer queue : playerSendingQueues.values()) {
|
||||
queue.getServerQueue().signalPacketUpdate(types, synchronusOK);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Immediately send every client packet with the given list of IDs.
|
||||
* @param ids - ID of every packet to send immediately.
|
||||
* @param synchronusOK - whether or not we're running on the main thread.
|
||||
*/
|
||||
public void sendClientPackets(List<PacketType> types, boolean synchronusOK) {
|
||||
if (!cleaningUp) {
|
||||
for (QueueContainer queue : playerSendingQueues.values()) {
|
||||
queue.getClientQueue().signalPacketUpdate(types, synchronusOK);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send any outstanding server packets.
|
||||
* @param onMainThread - whether or not this is occuring on the main thread.
|
||||
*/
|
||||
public void trySendServerPackets(boolean onMainThread) {
|
||||
for (QueueContainer queue : playerSendingQueues.values()) {
|
||||
queue.getServerQueue().trySendPackets(onMainThread);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send any outstanding server packets.
|
||||
* @param onMainThread - whether or not this is occuring on the main thread.
|
||||
*/
|
||||
public void trySendClientPackets(boolean onMainThread) {
|
||||
for (QueueContainer queue : playerSendingQueues.values()) {
|
||||
queue.getClientQueue().trySendPackets(onMainThread);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve every server packet queue for every player.
|
||||
* @return Every sever packet queue.
|
||||
*/
|
||||
public List<PacketSendingQueue> getServerQueues() {
|
||||
List<PacketSendingQueue> result = new ArrayList<PacketSendingQueue>();
|
||||
|
||||
for (QueueContainer queue : playerSendingQueues.values())
|
||||
result.add(queue.getServerQueue());
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve every client packet queue for every player.
|
||||
* @return Every client packet queue.
|
||||
*/
|
||||
public List<PacketSendingQueue> getClientQueues() {
|
||||
List<PacketSendingQueue> result = new ArrayList<PacketSendingQueue>();
|
||||
|
||||
for (QueueContainer queue : playerSendingQueues.values())
|
||||
result.add(queue.getClientQueue());
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send all pending packets and clean up queues.
|
||||
*/
|
||||
public void cleanupAll() {
|
||||
if (!cleaningUp) {
|
||||
cleaningUp = true;
|
||||
|
||||
sendAllPackets();
|
||||
playerSendingQueues.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when a player has just logged out.
|
||||
* @param player - the player that just logged out.
|
||||
*/
|
||||
public void removePlayer(Player player) {
|
||||
// Every packet will be dropped - there's nothing we can do
|
||||
playerSendingQueues.remove(player);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol.async;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.Queue;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
|
||||
/**
|
||||
* Synchronization views copied from Google Guava.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
class Synchronization {
|
||||
|
||||
/**
|
||||
* Create a synchronized wrapper for the given queue.
|
||||
* <p>
|
||||
* This wrapper cannot synchronize the iterator(). Callers are expected
|
||||
* to synchronize iterators manually.
|
||||
* @param queue - the queue to synchronize.
|
||||
* @param mutex - synchronization mutex, or NULL to use the queue.
|
||||
* @return A synchronization wrapper.
|
||||
*/
|
||||
public static <E> Queue<E> queue(Queue<E> queue, @Nullable Object mutex) {
|
||||
return (queue instanceof SynchronizedQueue) ?
|
||||
queue :
|
||||
new SynchronizedQueue<E>(queue, mutex);
|
||||
}
|
||||
|
||||
private static class SynchronizedObject implements Serializable {
|
||||
private static final long serialVersionUID = -4408866092364554628L;
|
||||
|
||||
final Object delegate;
|
||||
final Object mutex;
|
||||
|
||||
SynchronizedObject(Object delegate, @Nullable Object mutex) {
|
||||
this.delegate = Preconditions.checkNotNull(delegate);
|
||||
this.mutex = (mutex == null) ? this : mutex;
|
||||
}
|
||||
|
||||
Object delegate() {
|
||||
return delegate;
|
||||
}
|
||||
|
||||
// No equals and hashCode; see ForwardingObject for details.
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
synchronized (mutex) {
|
||||
return delegate.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class SynchronizedCollection<E> extends SynchronizedObject implements Collection<E> {
|
||||
private static final long serialVersionUID = 5440572373531285692L;
|
||||
|
||||
private SynchronizedCollection(Collection<E> delegate,
|
||||
@Nullable Object mutex) {
|
||||
super(delegate, mutex);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
Collection<E> delegate() {
|
||||
return (Collection<E>) super.delegate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean add(E e) {
|
||||
synchronized (mutex) {
|
||||
return delegate().add(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addAll(Collection<? extends E> c) {
|
||||
synchronized (mutex) {
|
||||
return delegate().addAll(c);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
synchronized (mutex) {
|
||||
delegate().clear();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(Object o) {
|
||||
synchronized (mutex) {
|
||||
return delegate().contains(o);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsAll(Collection<?> c) {
|
||||
synchronized (mutex) {
|
||||
return delegate().containsAll(c);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
synchronized (mutex) {
|
||||
return delegate().isEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<E> iterator() {
|
||||
return delegate().iterator(); // manually synchronized
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(Object o) {
|
||||
synchronized (mutex) {
|
||||
return delegate().remove(o);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeAll(Collection<?> c) {
|
||||
synchronized (mutex) {
|
||||
return delegate().removeAll(c);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean retainAll(Collection<?> c) {
|
||||
synchronized (mutex) {
|
||||
return delegate().retainAll(c);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
synchronized (mutex) {
|
||||
return delegate().size();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] toArray() {
|
||||
synchronized (mutex) {
|
||||
return delegate().toArray();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T[] toArray(T[] a) {
|
||||
synchronized (mutex) {
|
||||
return delegate().toArray(a);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class SynchronizedQueue<E> extends SynchronizedCollection<E> implements Queue<E> {
|
||||
private static final long serialVersionUID = 1961791630386791902L;
|
||||
|
||||
SynchronizedQueue(Queue<E> delegate, @Nullable Object mutex) {
|
||||
super(delegate, mutex);
|
||||
}
|
||||
|
||||
@Override
|
||||
Queue<E> delegate() {
|
||||
return (Queue<E>) super.delegate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public E element() {
|
||||
synchronized (mutex) {
|
||||
return delegate().element();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean offer(E e) {
|
||||
synchronized (mutex) {
|
||||
return delegate().offer(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public E peek() {
|
||||
synchronized (mutex) {
|
||||
return delegate().peek();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public E poll() {
|
||||
synchronized (mutex) {
|
||||
return delegate().poll();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public E remove() {
|
||||
synchronized (mutex) {
|
||||
return delegate().remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,246 @@
|
||||
package com.comphenix.protocol.collections;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.PriorityQueue;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.base.Objects;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.base.Ticker;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.primitives.Longs;
|
||||
|
||||
/**
|
||||
* Represents a hash map where each association may expire after a given time has elapsed.
|
||||
* <p>
|
||||
* Note that replaced key-value associations are only collected once the original expiration time has elapsed.
|
||||
*
|
||||
* @author Kristian Stangeland
|
||||
*
|
||||
* @param <K> - type of the keys.
|
||||
* @param <V> - type of the values.
|
||||
*/
|
||||
public class ExpireHashMap<K, V> {
|
||||
private class ExpireEntry implements Comparable<ExpireEntry> {
|
||||
public final long expireTime;
|
||||
public final K expireKey;
|
||||
public final V expireValue;
|
||||
|
||||
public ExpireEntry(long expireTime, K expireKey, V expireValue) {
|
||||
this.expireTime = expireTime;
|
||||
this.expireKey = expireKey;
|
||||
this.expireValue = expireValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(ExpireEntry o) {
|
||||
return Longs.compare(expireTime, o.expireTime);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ExpireEntry [expireTime=" + expireTime + ", expireKey=" + expireKey
|
||||
+ ", expireValue=" + expireValue + "]";
|
||||
}
|
||||
}
|
||||
|
||||
private Map<K, ExpireEntry> keyLookup = new HashMap<K, ExpireEntry>();
|
||||
private PriorityQueue<ExpireEntry> expireQueue = new PriorityQueue<ExpireEntry>();
|
||||
|
||||
// View of keyLookup with direct values
|
||||
private Map<K, V> valueView = Maps.transformValues(keyLookup, new Function<ExpireEntry, V>() {
|
||||
@Override
|
||||
public V apply(ExpireEntry entry) {
|
||||
return entry.expireValue;
|
||||
}
|
||||
});
|
||||
|
||||
// Supplied by the constructor
|
||||
private Ticker ticker;
|
||||
|
||||
/**
|
||||
* Construct a new hash map where each entry may expire at a given time.
|
||||
*/
|
||||
public ExpireHashMap() {
|
||||
this(Ticker.systemTicker());
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new hash map where each entry may expire at a given time.
|
||||
* @param ticker - supplier of the current time.
|
||||
*/
|
||||
public ExpireHashMap(Ticker ticker) {
|
||||
this.ticker = ticker;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the value associated with the given key, if it has not expired.
|
||||
* @param key - the key.
|
||||
* @return The value, or NULL if not found or it has expired.
|
||||
*/
|
||||
public V get(K key) {
|
||||
evictExpired();
|
||||
|
||||
ExpireEntry entry = keyLookup.get(key);
|
||||
return entry != null ? entry.expireValue : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Associate the given key with the given value, until the expire delay have elapsed.
|
||||
* @param key - the key.
|
||||
* @param value - the value.
|
||||
* @param expireDelay - the amount of time until this association expires. Must be greater than zero.
|
||||
* @param expireUnit - the unit of the expiration.
|
||||
* @return Any previously unexpired association with this key, or NULL.
|
||||
*/
|
||||
public V put(K key, V value, long expireDelay, TimeUnit expireUnit) {
|
||||
Preconditions.checkNotNull(expireUnit, "expireUnit cannot be NULL");
|
||||
Preconditions.checkState(expireDelay > 0, "expireDelay cannot be equal or less than zero.");
|
||||
evictExpired();
|
||||
|
||||
ExpireEntry entry = new ExpireEntry(
|
||||
ticker.read() + TimeUnit.NANOSECONDS.convert(expireDelay, expireUnit),
|
||||
key, value
|
||||
);
|
||||
ExpireEntry previous = keyLookup.put(key, entry);
|
||||
|
||||
// We enqueue its removal
|
||||
expireQueue.add(entry);
|
||||
return previous != null ? previous.expireValue : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the given key is referring to an unexpired association in the map.
|
||||
* @param key - the key.
|
||||
* @return TRUE if it is, FALSE otherwise.
|
||||
*/
|
||||
public boolean containsKey(K key) {
|
||||
evictExpired();
|
||||
return keyLookup.containsKey(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the given value is referring to an unexpired association in the map.
|
||||
* @param value - the value.
|
||||
* @return TRUE if it is, FALSE otherwise.
|
||||
*/
|
||||
public boolean containsValue(V value) {
|
||||
evictExpired();
|
||||
|
||||
// Linear scan is the best we've got
|
||||
for (ExpireEntry entry : keyLookup.values()) {
|
||||
if (Objects.equal(value, entry.expireValue)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a key and its associated value from the map.
|
||||
* @param key - the key to remove.
|
||||
* @return Value of the removed association, NULL otherwise.
|
||||
*/
|
||||
public V removeKey(K key) {
|
||||
evictExpired();
|
||||
|
||||
ExpireEntry entry = keyLookup.remove(key);
|
||||
return entry != null ? entry.expireValue : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the number of entries in the map.
|
||||
* @return The number of entries.
|
||||
*/
|
||||
public int size() {
|
||||
evictExpired();
|
||||
return keyLookup.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a view of the keys in the current map.
|
||||
* @return View of the keys.
|
||||
*/
|
||||
public Set<K> keySet() {
|
||||
evictExpired();
|
||||
return keyLookup.keySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a view of all the values in the current map.
|
||||
* @return All the values.
|
||||
*/
|
||||
public Collection<V> values() {
|
||||
evictExpired();
|
||||
return valueView.values();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a view of all the entries in the set.
|
||||
* @return All the entries.
|
||||
*/
|
||||
public Set<Entry<K, V>> entrySet() {
|
||||
evictExpired();
|
||||
return valueView.entrySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a view of this expire map as an ordinary map that does not support insertion.
|
||||
* @return The map.
|
||||
*/
|
||||
public Map<K, V> asMap() {
|
||||
evictExpired();
|
||||
return valueView;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all references to key-value pairs that have been removed or replaced before they were naturally evicted.
|
||||
* <p>
|
||||
* This operation requires a linear scan of the current entries in the map.
|
||||
*/
|
||||
public void collect() {
|
||||
// First evict what we can
|
||||
evictExpired();
|
||||
|
||||
// Recreate the eviction queue - this is faster than removing entries in the old queue
|
||||
expireQueue.clear();
|
||||
expireQueue.addAll(keyLookup.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all the entries in the current map.
|
||||
*/
|
||||
public void clear() {
|
||||
keyLookup.clear();
|
||||
expireQueue.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Evict any expired entries in the map.
|
||||
* <p>
|
||||
* This is called automatically by any of the read or write operations.
|
||||
*/
|
||||
protected void evictExpired() {
|
||||
long currentTime = ticker.read();
|
||||
|
||||
// Remove expired entries
|
||||
while (expireQueue.size() > 0 && expireQueue.peek().expireTime <= currentTime) {
|
||||
ExpireEntry entry = expireQueue.poll();
|
||||
|
||||
if (entry == keyLookup.get(entry.expireKey)) {
|
||||
keyLookup.remove(entry.expireKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return valueView.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
package com.comphenix.protocol.collections;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
/**
|
||||
* Represents a very quick integer-based lookup map, with a fixed key space size.
|
||||
* <p>
|
||||
* Integers must be non-negative.
|
||||
* @author Kristian
|
||||
*/
|
||||
public class IntegerMap<T> {
|
||||
private T[] array;
|
||||
private int size;
|
||||
|
||||
/**
|
||||
* Construct a new integer map.
|
||||
* @param <T> Parameter type
|
||||
* @return A new integer map.
|
||||
*/
|
||||
public static <T> IntegerMap<T> newMap() {
|
||||
return new IntegerMap<T>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new integer map with a default capacity.
|
||||
*/
|
||||
public IntegerMap() {
|
||||
this(8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new integer map with a given capacity.
|
||||
* @param initialCapacity - the capacity.
|
||||
*/
|
||||
public IntegerMap(int initialCapacity) {
|
||||
@SuppressWarnings("unchecked")
|
||||
T[] backingArray = (T[]) new Object[initialCapacity];
|
||||
this.array = backingArray;
|
||||
this.size = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Associate an integer key with the given value.
|
||||
* @param key - the integer key. Cannot be negative.
|
||||
* @param value - the value. Cannot be NULL.
|
||||
* @return The previous association, or NULL if not found.
|
||||
*/
|
||||
public T put(int key, T value) {
|
||||
ensureCapacity(key);
|
||||
|
||||
T old = array[key];
|
||||
array[key] = Preconditions.checkNotNull(value, "value cannot be NULL");
|
||||
|
||||
if (old == null)
|
||||
size++;
|
||||
return old;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an association from the map.
|
||||
* @param key - the key of the association to remove.
|
||||
* @return The old associated value, or NULL.
|
||||
*/
|
||||
public T remove(int key) {
|
||||
T old = array[key];
|
||||
array[key] = null;
|
||||
|
||||
if (old != null)
|
||||
size--;
|
||||
return old;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resize the backing array to fit the given key.
|
||||
* @param key - the key.
|
||||
*/
|
||||
protected void ensureCapacity(int key) {
|
||||
int newLength = array.length;
|
||||
|
||||
// Don't resize if the key fits
|
||||
if (key < 0)
|
||||
throw new IllegalArgumentException("Negative key values are not permitted.");
|
||||
if (key < newLength)
|
||||
return;
|
||||
|
||||
while (newLength <= key) {
|
||||
int next = newLength * 2;
|
||||
// Handle overflow
|
||||
newLength = next > newLength ? next : Integer.MAX_VALUE;
|
||||
}
|
||||
this.array = Arrays.copyOf(array, newLength);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the number of mappings in this map.
|
||||
* @return The number of mapping.
|
||||
*/
|
||||
public int size() {
|
||||
return size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the value associated with a given key.
|
||||
* @param key - the key.
|
||||
* @return The value, or NULL if not found.
|
||||
*/
|
||||
public T get(int key) {
|
||||
if (key >= 0 && key < array.length)
|
||||
return array[key];
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the given key exists in the map.
|
||||
* @param key - the key to check.
|
||||
* @return TRUE if it does, FALSE otherwise.
|
||||
*/
|
||||
public boolean containsKey(int key) {
|
||||
return get(key) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the current map to an Integer map.
|
||||
* @return The Integer map.
|
||||
*/
|
||||
public Map<Integer, Object> toMap() {
|
||||
Map<Integer, Object> map = Maps.newHashMap();
|
||||
|
||||
for (int i = 0; i < array.length; i++) {
|
||||
if (array[i] != null) {
|
||||
map.put(i, array[i]);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2015 dmulloy2
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
package com.comphenix.protocol.compat.guava;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.ProtocolLibrary;
|
||||
import com.google.common.collect.Range;
|
||||
|
||||
/**
|
||||
* @author dmulloy2
|
||||
*/
|
||||
|
||||
public class Guava {
|
||||
private static GuavaCompat compat;
|
||||
|
||||
static {
|
||||
try {
|
||||
Range.closed(1, 2);
|
||||
compat = new Guava17();
|
||||
} catch (Throwable ex) {
|
||||
try {
|
||||
Class<?> clazz = Class.forName("com.comphenix.protocol.compat.guava.Guava10");
|
||||
compat = (GuavaCompat) clazz.newInstance();
|
||||
} catch (Throwable ex1) {
|
||||
ProtocolLibrary.log(Level.SEVERE, "Failed to create Guava 10 compat:", ex1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static GuavaCompat getCompat() {
|
||||
return compat;
|
||||
}
|
||||
|
||||
public static <C extends Comparable<C>> Range<C> closedRange(C lower, C upper) {
|
||||
return getCompat().closedRange(lower, upper);
|
||||
}
|
||||
|
||||
public static <C extends Comparable<C>> Range<C> singleton(C singleton) {
|
||||
return getCompat().singletonRange(singleton);
|
||||
}
|
||||
|
||||
public static Set<Integer> toSet(Range<Integer> range) {
|
||||
return getCompat().toSet(range);
|
||||
}
|
||||
|
||||
public static DataInputStream addHeader(DataInputStream input, PacketType type) {
|
||||
return getCompat().addHeader(input, type);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2015 dmulloy2
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
package com.comphenix.protocol.compat.guava;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Set;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.google.common.collect.ContiguousSet;
|
||||
import com.google.common.collect.DiscreteDomain;
|
||||
import com.google.common.collect.Range;
|
||||
import com.google.common.io.ByteSource;
|
||||
|
||||
/**
|
||||
* @author dmulloy2
|
||||
*/
|
||||
|
||||
public class Guava17 implements GuavaCompat {
|
||||
|
||||
@Override
|
||||
public <C extends Comparable<C>> Range<C> closedRange(C lower, C upper) {
|
||||
return Range.closed(lower, upper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <C extends Comparable<C>> Range<C> singletonRange(C singleton) {
|
||||
return Range.singleton(singleton);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Integer> toSet(Range<Integer> range) {
|
||||
return ContiguousSet.create(range, DiscreteDomain.integers());
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataInputStream addHeader(final DataInputStream input, final PacketType type) {
|
||||
ByteSource header = new ByteSource() {
|
||||
@Override
|
||||
public InputStream openStream() throws IOException {
|
||||
byte[] data = new byte[] { (byte) type.getLegacyId() };
|
||||
return new ByteArrayInputStream(data);
|
||||
}
|
||||
};
|
||||
|
||||
ByteSource data = new ByteSource() {
|
||||
@Override
|
||||
public InputStream openStream() throws IOException {
|
||||
return input;
|
||||
}
|
||||
};
|
||||
|
||||
// Combine them into a single stream
|
||||
try {
|
||||
return new DataInputStream(ByteSource.concat(header, data).openStream());
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Cannot add header.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2015 dmulloy2
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
package com.comphenix.protocol.compat.guava;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.util.Set;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.google.common.collect.Range;
|
||||
|
||||
/**
|
||||
* @author dmulloy2
|
||||
*/
|
||||
|
||||
public interface GuavaCompat {
|
||||
|
||||
<C extends Comparable<C>> Range<C> closedRange(C lower, C upper);
|
||||
|
||||
<C extends Comparable<C>> Range<C> singletonRange(C singleton);
|
||||
|
||||
Set<Integer> toSet(Range<Integer> range);
|
||||
|
||||
DataInputStream addHeader(DataInputStream input, PacketType type);
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2015 dmulloy2
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
package com.comphenix.protocol.compat.netty;
|
||||
|
||||
import com.comphenix.protocol.injector.netty.Injector;
|
||||
|
||||
/**
|
||||
* @author dmulloy2
|
||||
*/
|
||||
|
||||
public interface ChannelInjector extends Injector {
|
||||
|
||||
WrappedChannel getChannel();
|
||||
}
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
/**
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2015 dmulloy2
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
package com.comphenix.protocol.compat.netty;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.PacketType.Protocol;
|
||||
import com.comphenix.protocol.PacketType.Sender;
|
||||
import com.comphenix.protocol.injector.netty.ProtocolRegistry;
|
||||
import com.comphenix.protocol.injector.packet.MapContainer;
|
||||
import com.comphenix.protocol.reflect.StructureModifier;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
/**
|
||||
* @author dmulloy2
|
||||
*/
|
||||
|
||||
public class LegacyProtocolRegistry extends ProtocolRegistry {
|
||||
|
||||
public LegacyProtocolRegistry() {
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initialize() {
|
||||
final Object[] protocols = enumProtocol.getEnumConstants();
|
||||
List<Map<Integer, Class<?>>> serverMaps = Lists.newArrayList();
|
||||
List<Map<Integer, Class<?>>> clientMaps = Lists.newArrayList();
|
||||
StructureModifier<Object> modifier = null;
|
||||
|
||||
// Result
|
||||
Register result = new Register();
|
||||
|
||||
for (Object protocol : protocols) {
|
||||
if (modifier == null)
|
||||
modifier = new StructureModifier<Object>(protocol.getClass().getSuperclass(), false);
|
||||
StructureModifier<Map<Integer, Class<?>>> maps = modifier.withTarget(protocol).withType(Map.class);
|
||||
|
||||
serverMaps.add(maps.read(0));
|
||||
clientMaps.add(maps.read(1));
|
||||
}
|
||||
// Maps we have to occationally check have changed
|
||||
for (Map<Integer, Class<?>> map : Iterables.concat(serverMaps, clientMaps)) {
|
||||
result.containers.add(new MapContainer(map));
|
||||
}
|
||||
|
||||
// Heuristic - there are more server packets than client packets
|
||||
if (sum(clientMaps) > sum(serverMaps)) {
|
||||
// Swap if this is violated
|
||||
List<Map<Integer, Class<?>>> temp = serverMaps;
|
||||
serverMaps = clientMaps;
|
||||
clientMaps = temp;
|
||||
}
|
||||
|
||||
for (int i = 0; i < protocols.length; i++) {
|
||||
Enum<?> enumProtocol = (Enum<?>) protocols[i];
|
||||
Protocol equivalent = Protocol.fromVanilla(enumProtocol);
|
||||
|
||||
// Associate known types
|
||||
associatePackets(result, serverMaps.get(i), equivalent, Sender.SERVER);
|
||||
associatePackets(result, clientMaps.get(i), equivalent, Sender.CLIENT);
|
||||
}
|
||||
|
||||
// Exchange (thread safe, as we have only one writer)
|
||||
this.register = result;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void associatePackets(Register register, Map<Integer, Class<?>> lookup, Protocol protocol, Sender sender) {
|
||||
for (Entry<Integer, Class<?>> entry : lookup.entrySet()) {
|
||||
PacketType type = PacketType.fromID(protocol, sender, entry.getKey(), entry.getValue());
|
||||
|
||||
try {
|
||||
register.typeToClass.put(type, entry.getValue());
|
||||
|
||||
if (sender == Sender.SERVER)
|
||||
register.serverPackets.add(type);
|
||||
if (sender == Sender.CLIENT)
|
||||
register.clientPackets.add(type);
|
||||
} catch (IllegalArgumentException ex) {
|
||||
// Sometimes this happens with fake packets, just ignore it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
/**
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2015 dmulloy2
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
package com.comphenix.protocol.compat.netty;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import com.comphenix.protocol.ProtocolLibrary;
|
||||
import com.comphenix.protocol.compat.netty.independent.IndependentNetty;
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.injector.PacketFilterManager;
|
||||
import com.comphenix.protocol.wrappers.WrappedServerPing.CompressedImage;
|
||||
|
||||
/**
|
||||
* @author dmulloy2
|
||||
*/
|
||||
|
||||
public class Netty {
|
||||
private static NettyCompat compat;
|
||||
|
||||
static {
|
||||
try {
|
||||
Class.forName("io.netty.buffer.ByteBuf");
|
||||
compat = new IndependentNetty();
|
||||
} catch (ClassNotFoundException ex) {
|
||||
try {
|
||||
Class<?> clazz = Class.forName("com.comphenix.protocol.compat.netty.shaded.ShadedNetty");
|
||||
compat = (NettyCompat) clazz.newInstance();
|
||||
} catch (Exception ex1) {
|
||||
ProtocolLibrary.log(Level.SEVERE, "Failed to create legacy netty compat:", ex1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static NettyCompat getCompat() {
|
||||
return compat;
|
||||
}
|
||||
|
||||
public static boolean isIndependent() {
|
||||
return compat instanceof IndependentNetty;
|
||||
}
|
||||
|
||||
public static WrappedByteBuf createPacketBuffer() {
|
||||
return getCompat().createPacketBuffer();
|
||||
}
|
||||
|
||||
public static Class<?> getGenericFutureListenerArray() {
|
||||
return getCompat().getGenericFutureListenerArray();
|
||||
}
|
||||
|
||||
public static Class<?> getChannelHandlerContext() {
|
||||
return getCompat().getChannelHandlerContext();
|
||||
}
|
||||
|
||||
public static String toEncodedText(CompressedImage image) {
|
||||
return getCompat().toEncodedText(image);
|
||||
}
|
||||
|
||||
public static WrappedByteBuf decode(byte[] encoded) {
|
||||
return getCompat().decode(encoded);
|
||||
}
|
||||
|
||||
public static ProtocolInjector getProtocolInjector(Plugin library, PacketFilterManager packetFilterManager, ErrorReporter reporter) {
|
||||
return getCompat().getProtocolInjector(library, packetFilterManager, reporter);
|
||||
}
|
||||
|
||||
public static WrappedByteBuf packetReader(DataInputStream input) {
|
||||
return getCompat().packetReader(input);
|
||||
}
|
||||
|
||||
public static WrappedByteBuf packetWriter(DataOutputStream output) {
|
||||
return getCompat().packetWriter(output);
|
||||
}
|
||||
|
||||
public static WrappedByteBuf copiedBuffer(byte[] array) {
|
||||
return getCompat().copiedBuffer(array);
|
||||
}
|
||||
|
||||
public static WrappedByteBuf buffer() {
|
||||
return getCompat().buffer();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2015 dmulloy2
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
package com.comphenix.protocol.compat.netty;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.injector.ListenerInvoker;
|
||||
import com.comphenix.protocol.wrappers.WrappedServerPing.CompressedImage;
|
||||
|
||||
|
||||
/**
|
||||
* @author dmulloy2
|
||||
*/
|
||||
|
||||
// TODO: Sort out packet readers/writers
|
||||
public interface NettyCompat {
|
||||
|
||||
WrappedByteBuf createPacketBuffer();
|
||||
|
||||
WrappedByteBuf copiedBuffer(byte[] array);
|
||||
|
||||
WrappedByteBuf buffer();
|
||||
|
||||
Class<?> getGenericFutureListenerArray();
|
||||
|
||||
Class<?> getChannelHandlerContext();
|
||||
|
||||
String toEncodedText(CompressedImage image);
|
||||
|
||||
WrappedByteBuf decode(byte[] encoded);
|
||||
|
||||
ProtocolInjector getProtocolInjector(Plugin plugin, ListenerInvoker invoker, ErrorReporter reporter);
|
||||
|
||||
WrappedByteBuf packetReader(DataInputStream input);
|
||||
|
||||
WrappedByteBuf packetWriter(DataOutputStream output);
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2015 dmulloy2
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
package com.comphenix.protocol.compat.netty;
|
||||
|
||||
import com.comphenix.protocol.injector.netty.ChannelListener;
|
||||
import com.comphenix.protocol.injector.packet.PacketInjector;
|
||||
import com.comphenix.protocol.injector.player.PlayerInjectionHandler;
|
||||
|
||||
/**
|
||||
* @author dmulloy2
|
||||
*/
|
||||
|
||||
public interface ProtocolInjector extends ChannelListener {
|
||||
|
||||
PlayerInjectionHandler getPlayerInjector();
|
||||
|
||||
PacketInjector getPacketInjector();
|
||||
|
||||
void setDebug(boolean debug);
|
||||
|
||||
void inject();
|
||||
|
||||
void close();
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2015 dmulloy2
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
package com.comphenix.protocol.compat.netty;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
|
||||
/**
|
||||
* @author dmulloy2
|
||||
*/
|
||||
|
||||
public interface WrappedByteBuf {
|
||||
|
||||
void writeBytes(ObjectInputStream input, int id) throws IOException;
|
||||
|
||||
Object getHandle();
|
||||
|
||||
int readableBytes();
|
||||
|
||||
void readBytes(ObjectOutputStream output, int readableBytes) throws IOException;
|
||||
|
||||
void readBytes(byte[] data);
|
||||
|
||||
void writeByte(byte b);
|
||||
|
||||
void writeByte(int i);
|
||||
|
||||
void writeBytes(byte[] bytes);
|
||||
|
||||
byte[] array();
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2015 dmulloy2
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
package com.comphenix.protocol.compat.netty;
|
||||
|
||||
/**
|
||||
* @author dmulloy2
|
||||
*/
|
||||
|
||||
public interface WrappedChannel {
|
||||
|
||||
void writeAndFlush(Object packet);
|
||||
}
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
/**
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2015 dmulloy2
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
package com.comphenix.protocol.compat.netty.independent;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.buffer.UnpooledByteBufAllocator;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.base64.Base64;
|
||||
import io.netty.util.concurrent.GenericFutureListener;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import com.comphenix.protocol.compat.netty.NettyCompat;
|
||||
import com.comphenix.protocol.compat.netty.ProtocolInjector;
|
||||
import com.comphenix.protocol.compat.netty.WrappedByteBuf;
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.injector.ListenerInvoker;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
import com.comphenix.protocol.wrappers.WrappedServerPing.CompressedImage;
|
||||
import com.google.common.base.Charsets;
|
||||
|
||||
/**
|
||||
* @author dmulloy2
|
||||
*/
|
||||
|
||||
public class IndependentNetty implements NettyCompat {
|
||||
|
||||
@Override
|
||||
public WrappedByteBuf createPacketBuffer() {
|
||||
ByteBuf buffer = UnpooledByteBufAllocator.DEFAULT.buffer();
|
||||
Class<?> packetSerializer = MinecraftReflection.getPacketDataSerializerClass();
|
||||
|
||||
try {
|
||||
return new NettyByteBuf((ByteBuf) packetSerializer.getConstructor(ByteBuf.class).newInstance(buffer));
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Cannot construct packet serializer.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getGenericFutureListenerArray() {
|
||||
return GenericFutureListener[].class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getChannelHandlerContext() {
|
||||
return ChannelHandlerContext.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toEncodedText(CompressedImage image) {
|
||||
final ByteBuf buffer = Unpooled.wrappedBuffer(image.getDataCopy());
|
||||
String computed = "data:" + image.getMime() + ";base64," +
|
||||
Base64.encode(buffer).toString(Charsets.UTF_8);
|
||||
return computed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WrappedByteBuf decode(byte[] encoded) {
|
||||
return new NettyByteBuf(Base64.decode(Unpooled.wrappedBuffer(encoded)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProtocolInjector getProtocolInjector(Plugin plugin, ListenerInvoker invoker, ErrorReporter reporter) {
|
||||
return new NettyProtocolInjector(plugin, invoker, reporter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WrappedByteBuf packetReader(DataInputStream input) {
|
||||
return new NettyByteBuf(NettyByteBufAdapter.packetReader(input));
|
||||
}
|
||||
|
||||
@Override
|
||||
public WrappedByteBuf packetWriter(DataOutputStream output) {
|
||||
return new NettyByteBuf(NettyByteBufAdapter.packetWriter(output));
|
||||
}
|
||||
|
||||
@Override
|
||||
public WrappedByteBuf copiedBuffer(byte[] array) {
|
||||
return new NettyByteBuf(Unpooled.copiedBuffer(array));
|
||||
}
|
||||
|
||||
@Override
|
||||
public WrappedByteBuf buffer() {
|
||||
return new NettyByteBuf(Unpooled.buffer());
|
||||
}
|
||||
}
|
||||
+234
@@ -0,0 +1,234 @@
|
||||
/**
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2015 dmulloy2
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
package com.comphenix.protocol.compat.netty.independent;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
public class NettyBootstrapList implements List<Object> {
|
||||
private List<Object> delegate;
|
||||
private ChannelHandler handler;
|
||||
|
||||
/**
|
||||
* Construct a new bootstrap list.
|
||||
* @param delegate - the delegate.
|
||||
* @param handler - the channel handler to add.
|
||||
*/
|
||||
public NettyBootstrapList(List<Object> delegate, ChannelHandler handler) {
|
||||
this.delegate = delegate;
|
||||
this.handler = handler;
|
||||
|
||||
// Process all existing bootstraps
|
||||
for (Object item : this) {
|
||||
processElement(item);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized boolean add(Object element) {
|
||||
processElement(element);
|
||||
return delegate.add(element);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized boolean addAll(Collection<? extends Object> collection) {
|
||||
List<Object> copy = Lists.newArrayList(collection);
|
||||
|
||||
// Process the collection before we pass it on
|
||||
for (Object element : copy) {
|
||||
processElement(element);
|
||||
}
|
||||
return delegate.addAll(copy);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized Object set(int index, Object element) {
|
||||
Object old = delegate.set(index, element);
|
||||
|
||||
// Handle the old future, and the newly inserted future
|
||||
if (old != element) {
|
||||
unprocessElement(old);
|
||||
processElement(element);
|
||||
}
|
||||
return old;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a single element.
|
||||
* @param element - the element.
|
||||
*/
|
||||
protected void processElement(Object element) {
|
||||
if (element instanceof ChannelFuture) {
|
||||
processBootstrap((ChannelFuture) element);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unprocess a single element.
|
||||
* @param element - the element to unprocess.
|
||||
*/
|
||||
protected void unprocessElement(Object element) {
|
||||
if (element instanceof ChannelFuture) {
|
||||
unprocessBootstrap((ChannelFuture) element);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a single channel future.
|
||||
* @param future - the future.
|
||||
*/
|
||||
protected void processBootstrap(ChannelFuture future) {
|
||||
// Important: Must be addFirst()
|
||||
future.channel().pipeline().addFirst(handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Revert any changes we made to the channel future.
|
||||
* @param future - the future.
|
||||
*/
|
||||
protected void unprocessBootstrap(ChannelFuture future) {
|
||||
final Channel channel = future.channel();
|
||||
|
||||
// For thread safety - see ChannelInjector.close()
|
||||
channel.eventLoop().submit(new Callable<Object>() {
|
||||
@Override
|
||||
public Object call() throws Exception {
|
||||
channel.pipeline().remove(handler);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Close and revert all changes.
|
||||
*/
|
||||
public synchronized void close() {
|
||||
for (Object element : this)
|
||||
unprocessElement(element);
|
||||
}
|
||||
|
||||
// Boiler plate
|
||||
@Override
|
||||
public synchronized int size() {
|
||||
return delegate.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized boolean isEmpty() {
|
||||
return delegate.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(Object o) {
|
||||
return delegate.contains(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized Iterator<Object> iterator() {
|
||||
return delegate.iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized Object[] toArray() {
|
||||
return delegate.toArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized <T> T[] toArray(T[] a) {
|
||||
return delegate.toArray(a);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized boolean remove(Object o) {
|
||||
return delegate.remove(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized boolean containsAll(Collection<?> c) {
|
||||
return delegate.containsAll(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized boolean addAll(int index, Collection<? extends Object> c) {
|
||||
return delegate.addAll(index, c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized boolean removeAll(Collection<?> c) {
|
||||
return delegate.removeAll(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized boolean retainAll(Collection<?> c) {
|
||||
return delegate.retainAll(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void clear() {
|
||||
delegate.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized Object get(int index) {
|
||||
return delegate.get(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void add(int index, Object element) {
|
||||
delegate.add(index, element);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized Object remove(int index) {
|
||||
return delegate.remove(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized int indexOf(Object o) {
|
||||
return delegate.indexOf(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized int lastIndexOf(Object o) {
|
||||
return delegate.lastIndexOf(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized ListIterator<Object> listIterator() {
|
||||
return delegate.listIterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized ListIterator<Object> listIterator(int index) {
|
||||
return delegate.listIterator(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized List<Object> subList(int fromIndex, int toIndex) {
|
||||
return delegate.subList(fromIndex, toIndex);
|
||||
}
|
||||
// End boiler plate
|
||||
}
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2015 dmulloy2
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
package com.comphenix.protocol.compat.netty.independent;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
import com.comphenix.protocol.compat.netty.WrappedByteBuf;
|
||||
|
||||
/**
|
||||
* @author dmulloy2
|
||||
*/
|
||||
|
||||
public class NettyByteBuf implements WrappedByteBuf {
|
||||
private WeakReference<ByteBuf> handle;
|
||||
|
||||
public NettyByteBuf(ByteBuf handle) {
|
||||
this.handle = new WeakReference<ByteBuf>(handle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeBytes(ObjectInputStream input, int id) throws IOException {
|
||||
handle.get().writeBytes(input, id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getHandle() {
|
||||
return handle.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readableBytes() {
|
||||
return handle.get().readableBytes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readBytes(ObjectOutputStream output, int readableBytes) throws IOException {
|
||||
handle.get().readBytes(output, readableBytes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readBytes(byte[] data) {
|
||||
handle.get().readBytes(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeByte(byte b) {
|
||||
handle.get().writeByte(b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeByte(int i) {
|
||||
handle.get().writeByte(i);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeBytes(byte[] bytes) {
|
||||
handle.get().writeBytes(bytes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] array() {
|
||||
return handle.get().array();
|
||||
}
|
||||
}
|
||||
+392
@@ -0,0 +1,392 @@
|
||||
/**
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2015 dmulloy2
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
package com.comphenix.protocol.compat.netty.independent;
|
||||
|
||||
import io.netty.buffer.AbstractByteBuf;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufAllocator;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.channels.Channels;
|
||||
import java.nio.channels.GatheringByteChannel;
|
||||
import java.nio.channels.ScatteringByteChannel;
|
||||
import java.nio.channels.WritableByteChannel;
|
||||
|
||||
import com.comphenix.protocol.reflect.accessors.Accessors;
|
||||
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
import com.google.common.io.ByteStreams;
|
||||
|
||||
/**
|
||||
* Construct a ByteBuf around an input stream and an output stream.
|
||||
* <p>
|
||||
* Note that as streams usually don't support seeking, this implementation will ignore
|
||||
* all indexing in the byte buffer.
|
||||
* @author Kristian
|
||||
*/
|
||||
public class NettyByteBufAdapter extends AbstractByteBuf {
|
||||
private DataInputStream input;
|
||||
private DataOutputStream output;
|
||||
|
||||
// For modifying the reader or writer index
|
||||
private static FieldAccessor READER_INDEX;
|
||||
private static FieldAccessor WRITER_INDEX;
|
||||
|
||||
private static final int CAPACITY = Short.MAX_VALUE;
|
||||
|
||||
private NettyByteBufAdapter(DataInputStream input, DataOutputStream output) {
|
||||
// Just pick a figure
|
||||
super(CAPACITY);
|
||||
this.input = input;
|
||||
this.output = output;
|
||||
|
||||
// Prepare accessors
|
||||
try {
|
||||
if (READER_INDEX == null) {
|
||||
READER_INDEX = Accessors.getFieldAccessor(AbstractByteBuf.class.getDeclaredField("readerIndex"));
|
||||
}
|
||||
if (WRITER_INDEX == null) {
|
||||
WRITER_INDEX = Accessors.getFieldAccessor(AbstractByteBuf.class.getDeclaredField("writerIndex"));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Cannot initialize ByteBufAdapter.", e);
|
||||
}
|
||||
|
||||
// "Infinite" reading/writing
|
||||
if (input == null)
|
||||
READER_INDEX.set(this, Integer.MAX_VALUE);
|
||||
if (output == null)
|
||||
WRITER_INDEX.set(this, Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new Minecraft packet serializer using the current byte buf adapter.
|
||||
* @param input - the input stream.
|
||||
* @return A packet serializer with a wrapped byte buf adapter.
|
||||
*/
|
||||
public static ByteBuf packetReader(DataInputStream input) {
|
||||
return (ByteBuf) MinecraftReflection.getPacketDataSerializer(new NettyByteBufAdapter(input, null));
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new Minecraft packet deserializer using the current byte buf adapter.
|
||||
* @param output - the output stream.
|
||||
* @return A packet serializer with a wrapped byte buf adapter.
|
||||
*/
|
||||
public static ByteBuf packetWriter(DataOutputStream output) {
|
||||
return (ByteBuf) MinecraftReflection.getPacketDataSerializer(new NettyByteBufAdapter(null, output));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int refCnt() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean release() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean release(int paramInt) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte _getByte(int paramInt) {
|
||||
try {
|
||||
return input.readByte();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Cannot read input.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected short _getShort(int paramInt) {
|
||||
try {
|
||||
return input.readShort();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Cannot read input.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int _getUnsignedMedium(int paramInt) {
|
||||
try {
|
||||
return input.readUnsignedShort();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Cannot read input.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int _getInt(int paramInt) {
|
||||
try {
|
||||
return input.readInt();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Cannot read input.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected long _getLong(int paramInt) {
|
||||
try {
|
||||
return input.readLong();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Cannot read input.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void _setByte(int index, int value) {
|
||||
try {
|
||||
output.writeByte(value);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Cannot write output.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void _setShort(int index, int value) {
|
||||
try {
|
||||
output.writeShort(value);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Cannot write output.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void _setMedium(int index, int value) {
|
||||
try {
|
||||
output.writeShort(value);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Cannot write output.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void _setInt(int index, int value) {
|
||||
try {
|
||||
output.writeInt(value);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Cannot write output.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void _setLong(int index, long value) {
|
||||
try {
|
||||
output.writeLong(value);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Cannot write output.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int capacity() {
|
||||
return CAPACITY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf capacity(int paramInt) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBufAllocator alloc() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteOrder order() {
|
||||
return ByteOrder.LITTLE_ENDIAN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf unwrap() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDirect() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf getBytes(int index, ByteBuf dst, int dstIndex, int length) {
|
||||
try {
|
||||
for (int i = 0; i < length; i++) {
|
||||
dst.setByte(dstIndex + i, input.read());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Cannot read input.", e);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf getBytes(int index, byte[] dst, int dstIndex, int length) {
|
||||
try {
|
||||
input.read(dst, dstIndex, length);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Cannot read input.", e);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf getBytes(int index, ByteBuffer dst) {
|
||||
try {
|
||||
dst.put(ByteStreams.toByteArray(input));
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Cannot read input.", e);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf getBytes(int index, OutputStream dst, int length) throws IOException {
|
||||
ByteStreams.copy(ByteStreams.limit(input, length), dst);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBytes(int index, GatheringByteChannel out, int length) throws IOException {
|
||||
byte[] data = ByteStreams.toByteArray(ByteStreams.limit(input, length));
|
||||
|
||||
out.write(ByteBuffer.wrap(data));
|
||||
return data.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setBytes(int index, ByteBuf src, int srcIndex, int length) {
|
||||
byte[] buffer = new byte[length];
|
||||
src.getBytes(srcIndex, buffer);
|
||||
|
||||
try {
|
||||
output.write(buffer);
|
||||
return this;
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Cannot write output.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setBytes(int index, byte[] src, int srcIndex, int length) {
|
||||
try {
|
||||
output.write(src, srcIndex, length);
|
||||
return this;
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Cannot write output.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setBytes(int index, ByteBuffer src) {
|
||||
try {
|
||||
WritableByteChannel channel = Channels.newChannel(output);
|
||||
|
||||
channel.write(src);
|
||||
return this;
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Cannot write output.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int setBytes(int index, InputStream in, int length) throws IOException {
|
||||
InputStream limit = ByteStreams.limit(in, length);
|
||||
ByteStreams.copy(limit, output);
|
||||
return length - limit.available();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int setBytes(int index, ScatteringByteChannel in, int length) throws IOException {
|
||||
ByteBuffer buffer = ByteBuffer.allocate(length);
|
||||
WritableByteChannel channel = Channels.newChannel(output);
|
||||
|
||||
int count = in.read(buffer);
|
||||
channel.write(buffer);
|
||||
return count;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf copy(int index, int length) {
|
||||
throw new UnsupportedOperationException("Cannot seek in input stream.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int nioBufferCount() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer nioBuffer(int paramInt1, int paramInt2) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer internalNioBuffer(int paramInt1, int paramInt2) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer[] nioBuffers(int paramInt1, int paramInt2) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasArray() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] array() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int arrayOffset() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasMemoryAddress() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long memoryAddress() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf retain(int paramInt) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf retain() {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2015 dmulloy2
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
package com.comphenix.protocol.compat.netty.independent;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
|
||||
import com.comphenix.protocol.compat.netty.WrappedChannel;
|
||||
|
||||
/**
|
||||
* @author dmulloy2
|
||||
*/
|
||||
|
||||
public class NettyChannel implements WrappedChannel {
|
||||
private final Channel channel;
|
||||
|
||||
public NettyChannel(Channel channel) {
|
||||
this.channel = channel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeAndFlush(Object packet) {
|
||||
channel.writeAndFlush(packet);
|
||||
}
|
||||
}
|
||||
+932
@@ -0,0 +1,932 @@
|
||||
/**
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2015 dmulloy2
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
package com.comphenix.protocol.compat.netty.independent;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
import io.netty.channel.ChannelPipeline;
|
||||
import io.netty.channel.ChannelPromise;
|
||||
import io.netty.channel.socket.SocketChannel;
|
||||
import io.netty.handler.codec.ByteToMessageDecoder;
|
||||
import io.netty.handler.codec.MessageToByteEncoder;
|
||||
import io.netty.util.concurrent.GenericFutureListener;
|
||||
import io.netty.util.internal.TypeParameterMatcher;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketAddress;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Deque;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
import net.sf.cglib.proxy.Factory;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.PacketType.Protocol;
|
||||
import com.comphenix.protocol.ProtocolLibrary;
|
||||
import com.comphenix.protocol.compat.netty.ChannelInjector;
|
||||
import com.comphenix.protocol.compat.netty.WrappedByteBuf;
|
||||
import com.comphenix.protocol.compat.netty.WrappedChannel;
|
||||
import com.comphenix.protocol.error.Report;
|
||||
import com.comphenix.protocol.error.ReportType;
|
||||
import com.comphenix.protocol.events.ConnectionSide;
|
||||
import com.comphenix.protocol.events.NetworkMarker;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.injector.NetworkProcessor;
|
||||
import com.comphenix.protocol.injector.netty.ChannelListener;
|
||||
import com.comphenix.protocol.injector.netty.NettyNetworkMarker;
|
||||
import com.comphenix.protocol.injector.netty.WirePacket;
|
||||
import com.comphenix.protocol.injector.server.SocketInjector;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.comphenix.protocol.reflect.VolatileField;
|
||||
import com.comphenix.protocol.reflect.accessors.Accessors;
|
||||
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
|
||||
import com.comphenix.protocol.reflect.accessors.MethodAccessor;
|
||||
import com.comphenix.protocol.utility.MinecraftFields;
|
||||
import com.comphenix.protocol.utility.MinecraftMethods;
|
||||
import com.comphenix.protocol.utility.MinecraftProtocolVersion;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
import com.comphenix.protocol.wrappers.WrappedGameProfile;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.MapMaker;
|
||||
|
||||
/**
|
||||
* Represents a channel injector.
|
||||
* @author Kristian
|
||||
*/
|
||||
public class NettyChannelInjector extends ByteToMessageDecoder implements ChannelInjector {
|
||||
public static final ReportType REPORT_CANNOT_INTERCEPT_SERVER_PACKET = new ReportType("Unable to intercept a written server packet.");
|
||||
public static final ReportType REPORT_CANNOT_INTERCEPT_CLIENT_PACKET = new ReportType("Unable to intercept a read client packet.");
|
||||
public static final ReportType REPORT_CANNOT_EXECUTE_IN_CHANNEL_THREAD = new ReportType("Cannot execute code in channel thread.");
|
||||
public static final ReportType REPORT_CANNOT_FIND_GET_VERSION = new ReportType("Cannot find getVersion() in NetworkMananger");
|
||||
public static final ReportType REPORT_CANNOT_SEND_PACKET = new ReportType("Unable to send packet %s to %s");
|
||||
|
||||
/**
|
||||
* Indicates that a packet has bypassed packet listeners.
|
||||
*/
|
||||
private static final PacketEvent BYPASSED_PACKET = new PacketEvent(NettyChannelInjector.class);
|
||||
|
||||
// The login packet
|
||||
private static Class<?> PACKET_LOGIN_CLIENT = null;
|
||||
private static FieldAccessor LOGIN_GAME_PROFILE = null;
|
||||
|
||||
// Saved accessors
|
||||
private static MethodAccessor DECODE_BUFFER;
|
||||
private static MethodAccessor ENCODE_BUFFER;
|
||||
private static FieldAccessor ENCODER_TYPE_MATCHER;
|
||||
|
||||
// For retrieving the protocol
|
||||
private static FieldAccessor PROTOCOL_ACCESSOR;
|
||||
|
||||
// For retrieving the protocol version
|
||||
private static MethodAccessor PROTOCOL_VERSION;
|
||||
|
||||
// The factory that created this injector
|
||||
private NettyInjectionFactory factory;
|
||||
|
||||
// The player, or temporary player
|
||||
private Player player;
|
||||
private Player updated;
|
||||
private String playerName;
|
||||
|
||||
// The player connection
|
||||
private Object playerConnection;
|
||||
|
||||
// The current network manager and channel
|
||||
private final Object networkManager;
|
||||
private final Channel originalChannel;
|
||||
private VolatileField channelField;
|
||||
|
||||
// Known network markers
|
||||
private ConcurrentMap<Object, NetworkMarker> packetMarker = new MapMaker().weakKeys().makeMap();
|
||||
|
||||
/**
|
||||
* Indicate that this packet has been processed by event listeners.
|
||||
* <p>
|
||||
* This must never be set outside the channel pipeline's thread.
|
||||
*/
|
||||
private PacketEvent currentEvent;
|
||||
|
||||
/**
|
||||
* A packet event that should be processed by the write method.
|
||||
*/
|
||||
private PacketEvent finalEvent;
|
||||
|
||||
/**
|
||||
* A flag set by the main thread to indiciate that a packet should not be processed.
|
||||
*/
|
||||
private final ThreadLocal<Boolean> scheduleProcessPackets = new ThreadLocal<Boolean>() {
|
||||
@Override
|
||||
protected Boolean initialValue() {
|
||||
return true;
|
||||
};
|
||||
};
|
||||
|
||||
// Other handlers
|
||||
private ByteToMessageDecoder vanillaDecoder;
|
||||
private MessageToByteEncoder<Object> vanillaEncoder;
|
||||
|
||||
private Deque<PacketEvent> finishQueue = new ArrayDeque<PacketEvent>();
|
||||
|
||||
// The channel listener
|
||||
private ChannelListener channelListener;
|
||||
|
||||
// Processing network markers
|
||||
private NetworkProcessor processor;
|
||||
|
||||
// Closed
|
||||
private boolean injected;
|
||||
private boolean closed;
|
||||
|
||||
/**
|
||||
* Construct a new channel injector.
|
||||
* @param player - the current player, or temporary player.
|
||||
* @param networkManager - its network manager.
|
||||
* @param channel - its channel.
|
||||
* @param channelListener - a listener.
|
||||
* @param factory - the factory that created this injector
|
||||
*/
|
||||
public NettyChannelInjector(Player player, Object networkManager, Channel channel, ChannelListener channelListener, NettyInjectionFactory factory) {
|
||||
this.player = Preconditions.checkNotNull(player, "player cannot be NULL");
|
||||
this.networkManager = Preconditions.checkNotNull(networkManager, "networkMananger cannot be NULL");
|
||||
this.originalChannel = Preconditions.checkNotNull(channel, "channel cannot be NULL");
|
||||
this.channelListener = Preconditions.checkNotNull(channelListener, "channelListener cannot be NULL");
|
||||
this.factory = Preconditions.checkNotNull(factory, "factory cannot be NULL");
|
||||
this.processor = new NetworkProcessor(ProtocolLibrary.getErrorReporter());
|
||||
|
||||
// Get the channel field
|
||||
this.channelField = new VolatileField(FuzzyReflection.fromObject(networkManager, true).getFieldByType("channel", Channel.class),
|
||||
networkManager, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the version of the current protocol.
|
||||
* @return The version.
|
||||
*/
|
||||
@Override
|
||||
public int getProtocolVersion() {
|
||||
MethodAccessor accessor = PROTOCOL_VERSION;
|
||||
if (accessor == null) {
|
||||
try {
|
||||
accessor = Accessors.getMethodAccessor(networkManager.getClass(), "getVersion");
|
||||
} catch (Throwable ex) {
|
||||
}
|
||||
}
|
||||
|
||||
if (accessor != null) {
|
||||
return (Integer) accessor.invoke(networkManager);
|
||||
} else {
|
||||
return MinecraftProtocolVersion.getCurrentVersion();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public boolean inject() {
|
||||
synchronized (networkManager) {
|
||||
if (closed)
|
||||
return false;
|
||||
if (originalChannel instanceof Factory)
|
||||
return false;
|
||||
if (!originalChannel.isActive())
|
||||
return false;
|
||||
|
||||
// Main thread? We should synchronize with the channel thread, otherwise we might see a
|
||||
// pipeline with only some of the handlers removed
|
||||
if (Bukkit.isPrimaryThread()) {
|
||||
// Just like in the close() method, we'll avoid blocking the main thread
|
||||
executeInChannelThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
inject();
|
||||
}
|
||||
});
|
||||
return false; // We don't know
|
||||
}
|
||||
|
||||
// Don't inject the same channel twice
|
||||
if (findChannelHandler(originalChannel, NettyChannelInjector.class) != null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get the vanilla decoder, so we don't have to replicate the work
|
||||
vanillaDecoder = (ByteToMessageDecoder) originalChannel.pipeline().get("decoder");
|
||||
vanillaEncoder = (MessageToByteEncoder<Object>) originalChannel.pipeline().get("encoder");
|
||||
|
||||
if (vanillaDecoder == null)
|
||||
throw new IllegalArgumentException("Unable to find vanilla decoder in " + originalChannel.pipeline() );
|
||||
if (vanillaEncoder == null)
|
||||
throw new IllegalArgumentException("Unable to find vanilla encoder in " + originalChannel.pipeline() );
|
||||
patchEncoder(vanillaEncoder);
|
||||
|
||||
if (DECODE_BUFFER == null)
|
||||
DECODE_BUFFER = Accessors.getMethodAccessor(vanillaDecoder.getClass(),
|
||||
"decode", ChannelHandlerContext.class, ByteBuf.class, List.class);
|
||||
if (ENCODE_BUFFER == null)
|
||||
ENCODE_BUFFER = Accessors.getMethodAccessor(vanillaEncoder.getClass(),
|
||||
"encode", ChannelHandlerContext.class, Object.class, ByteBuf.class);
|
||||
|
||||
// Intercept sent packets
|
||||
MessageToByteEncoder<Object> protocolEncoder = new MessageToByteEncoder<Object>() {
|
||||
@Override
|
||||
protected void encode(ChannelHandlerContext ctx, Object packet, ByteBuf output) throws Exception {
|
||||
if (packet instanceof WirePacket) {
|
||||
// Special case for wire format
|
||||
NettyChannelInjector.this.encodeWirePacket((WirePacket) packet, new NettyByteBuf(output));
|
||||
} else {
|
||||
NettyChannelInjector.this.encode(ctx, packet, output);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(ChannelHandlerContext ctx, Object packet, ChannelPromise promise) throws Exception {
|
||||
super.write(ctx, packet, promise);
|
||||
NettyChannelInjector.this.finalWrite(ctx, packet, promise);
|
||||
}
|
||||
};
|
||||
|
||||
// Intercept recieved packets
|
||||
ChannelInboundHandlerAdapter finishHandler = new ChannelInboundHandlerAdapter() {
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
||||
// Execute context first
|
||||
ctx.fireChannelRead(msg);
|
||||
NettyChannelInjector.this.finishRead(ctx, msg);
|
||||
}
|
||||
};
|
||||
|
||||
// Insert our handlers - note that we effectively replace the vanilla encoder/decoder
|
||||
originalChannel.pipeline().addBefore("decoder", "protocol_lib_decoder", this);
|
||||
originalChannel.pipeline().addBefore("protocol_lib_decoder", "protocol_lib_finish", finishHandler);
|
||||
originalChannel.pipeline().addAfter("encoder", "protocol_lib_encoder", protocolEncoder);
|
||||
|
||||
// Intercept all write methods
|
||||
channelField.setValue(new NettyChannelProxy(originalChannel, MinecraftReflection.getPacketClass()) {
|
||||
// Compatibility with Spigot 1.8
|
||||
private final NettyPipelineProxy pipelineProxy = new NettyPipelineProxy(originalChannel.pipeline(), this) {
|
||||
@Override
|
||||
public ChannelPipeline addBefore(String baseName, String name, ChannelHandler handler) {
|
||||
// Correct the position of the decoder
|
||||
if ("decoder".equals(baseName)) {
|
||||
if (super.get("protocol_lib_decoder") != null && guessCompression(handler)) {
|
||||
super.addBefore("protocol_lib_decoder", name, handler);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
return super.addBefore(baseName, name, handler);
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public ChannelPipeline pipeline() {
|
||||
return pipelineProxy;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected <T> Callable<T> onMessageScheduled(final Callable<T> callable, FieldAccessor packetAccessor) {
|
||||
final PacketEvent event = handleScheduled(callable, packetAccessor);
|
||||
|
||||
// Handle cancelled events
|
||||
if (event != null && event.isCancelled())
|
||||
return null;
|
||||
|
||||
return new Callable<T>() {
|
||||
@Override
|
||||
public T call() throws Exception {
|
||||
T result = null;
|
||||
|
||||
// This field must only be updated in the pipeline thread
|
||||
currentEvent = event;
|
||||
result = callable.call();
|
||||
currentEvent = null;
|
||||
return result;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Runnable onMessageScheduled(final Runnable runnable, FieldAccessor packetAccessor) {
|
||||
final PacketEvent event = handleScheduled(runnable, packetAccessor);
|
||||
|
||||
// Handle cancelled events
|
||||
if (event != null && event.isCancelled())
|
||||
return null;
|
||||
|
||||
return new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
currentEvent = event;
|
||||
runnable.run();
|
||||
currentEvent = null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected PacketEvent handleScheduled(Object instance, FieldAccessor accessor) {
|
||||
// Let the filters handle this packet
|
||||
Object original = accessor.get(instance);
|
||||
|
||||
// See if we've been instructed not to process packets
|
||||
if (!scheduleProcessPackets.get()) {
|
||||
NetworkMarker marker = getMarker(original);
|
||||
|
||||
if (marker != null) {
|
||||
PacketEvent result = new PacketEvent(NettyChannelInjector.class);
|
||||
result.setNetworkMarker(marker);
|
||||
return result;
|
||||
} else {
|
||||
return BYPASSED_PACKET;
|
||||
}
|
||||
}
|
||||
PacketEvent event = processSending(original);
|
||||
|
||||
if (event != null && !event.isCancelled()) {
|
||||
Object changed = event.getPacket().getHandle();
|
||||
|
||||
// Change packet to be scheduled
|
||||
if (original != changed)
|
||||
accessor.set(instance, changed);
|
||||
};
|
||||
return event != null ? event : BYPASSED_PACKET;
|
||||
}
|
||||
});
|
||||
|
||||
injected = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the given object is a compressor or decompressor.
|
||||
* @param handler - object to test.
|
||||
* @return TRUE if it is, FALSE if not or unknown.
|
||||
*/
|
||||
private boolean guessCompression(ChannelHandler handler) {
|
||||
String className = handler != null ? handler.getClass().getCanonicalName() : "";
|
||||
return className.contains("Compressor") || className.contains("Decompressor");
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a given message on the packet listeners.
|
||||
* @param message - the message/packet.
|
||||
* @return The resulting message/packet.
|
||||
*/
|
||||
private PacketEvent processSending(Object message) {
|
||||
return channelListener.onPacketSending(NettyChannelInjector.this, message, getMarker(message));
|
||||
}
|
||||
|
||||
/**
|
||||
* This method patches the encoder so that it skips already created packets.
|
||||
* @param encoder - the encoder to patch.
|
||||
*/
|
||||
private void patchEncoder(MessageToByteEncoder<Object> encoder) {
|
||||
if (ENCODER_TYPE_MATCHER == null) {
|
||||
ENCODER_TYPE_MATCHER = Accessors.getFieldAccessor(encoder.getClass(), "matcher", true);
|
||||
}
|
||||
ENCODER_TYPE_MATCHER.set(encoder, TypeParameterMatcher.get(MinecraftReflection.getPacketClass()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
||||
if (channelListener.isDebug())
|
||||
cause.printStackTrace();
|
||||
super.exceptionCaught(ctx, cause);
|
||||
}
|
||||
|
||||
protected void encodeWirePacket(WirePacket packet, WrappedByteBuf output) throws Exception {
|
||||
packet.writeId(output);
|
||||
packet.writeBytes(output);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode a packet to a byte buffer, taking over for the standard Minecraft encoder.
|
||||
* @param ctx - the current context.
|
||||
* @param packet - the packet to encode to a byte array.
|
||||
* @param output - the output byte array.
|
||||
* @throws Exception If anything went wrong.
|
||||
*/
|
||||
protected void encode(ChannelHandlerContext ctx, Object packet, ByteBuf output) throws Exception {
|
||||
NetworkMarker marker = null;
|
||||
PacketEvent event = currentEvent;
|
||||
|
||||
try {
|
||||
// Skip every kind of non-filtered packet
|
||||
if (!scheduleProcessPackets.get()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// This packet has not been seen by the main thread
|
||||
if (event == null) {
|
||||
Class<?> clazz = packet.getClass();
|
||||
|
||||
// Schedule the transmission on the main thread instead
|
||||
if (channelListener.hasMainThreadListener(clazz)) {
|
||||
// Delay the packet
|
||||
scheduleMainThread(packet);
|
||||
packet = null;
|
||||
|
||||
} else {
|
||||
event = processSending(packet);
|
||||
|
||||
// Handle the output
|
||||
if (event != null) {
|
||||
packet = !event.isCancelled() ? event.getPacket().getHandle() : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (event != null) {
|
||||
// Retrieve marker without accidentally constructing it
|
||||
marker = NetworkMarker.getNetworkMarker(event);
|
||||
}
|
||||
|
||||
// Process output handler
|
||||
if (packet != null && event != null && NetworkMarker.hasOutputHandlers(marker)) {
|
||||
ByteBuf packetBuffer = ctx.alloc().buffer();
|
||||
ENCODE_BUFFER.invoke(vanillaEncoder, ctx, packet, packetBuffer);
|
||||
|
||||
// Let each handler prepare the actual output
|
||||
byte[] data = processor.processOutput(event, marker, getBytes(packetBuffer));
|
||||
|
||||
// Write the result
|
||||
output.writeBytes(data);
|
||||
packet = null;
|
||||
|
||||
// Sent listeners?
|
||||
finalEvent = event;
|
||||
return;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
channelListener.getReporter().reportDetailed(this,
|
||||
Report.newBuilder(REPORT_CANNOT_INTERCEPT_SERVER_PACKET).callerParam(packet).error(e).build());
|
||||
} finally {
|
||||
// Attempt to handle the packet nevertheless
|
||||
if (packet != null) {
|
||||
ENCODE_BUFFER.invoke(vanillaEncoder, ctx, packet, output);
|
||||
finalEvent = event;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when a packet has been written to the channel.
|
||||
* @param ctx - current context.
|
||||
* @param packet - the packet that has been written.
|
||||
* @param promise - a promise.
|
||||
*/
|
||||
protected void finalWrite(ChannelHandlerContext ctx, Object packet, ChannelPromise promise) {
|
||||
PacketEvent event = finalEvent;
|
||||
|
||||
if (event != null) {
|
||||
// Necessary to prevent infinite loops
|
||||
finalEvent = null;
|
||||
currentEvent = null;
|
||||
|
||||
processor.invokePostEvent(event, NetworkMarker.getNetworkMarker(event));
|
||||
}
|
||||
}
|
||||
|
||||
private void scheduleMainThread(final Object packetCopy) {
|
||||
// Don't use BukkitExecutors for this - it has a bit of overhead
|
||||
Bukkit.getScheduler().scheduleSyncDelayedTask(factory.getPlugin(), new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
invokeSendPacket(packetCopy);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext ctx, ByteBuf byteBuffer, List<Object> packets) throws Exception {
|
||||
byteBuffer.markReaderIndex();
|
||||
DECODE_BUFFER.invoke(vanillaDecoder, ctx, byteBuffer, packets);
|
||||
|
||||
try {
|
||||
// Reset queue
|
||||
finishQueue.clear();
|
||||
|
||||
for (ListIterator<Object> it = packets.listIterator(); it.hasNext(); ) {
|
||||
Object input = it.next();
|
||||
Class<?> packetClass = input.getClass();
|
||||
NetworkMarker marker = null;
|
||||
|
||||
// Special case!
|
||||
handleLogin(packetClass, input);
|
||||
|
||||
if (channelListener.includeBuffer(packetClass)) {
|
||||
byteBuffer.resetReaderIndex();
|
||||
marker = new NettyNetworkMarker(ConnectionSide.CLIENT_SIDE, getBytes(byteBuffer));
|
||||
}
|
||||
|
||||
PacketEvent output = channelListener.onPacketReceiving(this, input, marker);
|
||||
|
||||
// Handle packet changes
|
||||
if (output != null) {
|
||||
if (output.isCancelled()) {
|
||||
it.remove();
|
||||
continue;
|
||||
} else if (output.getPacket().getHandle() != input) {
|
||||
it.set(output.getPacket().getHandle());
|
||||
}
|
||||
|
||||
finishQueue.addLast(output);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
channelListener.getReporter().reportDetailed(this,
|
||||
Report.newBuilder(REPORT_CANNOT_INTERCEPT_CLIENT_PACKET).callerParam(byteBuffer).error(e).build());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked after our decoder.
|
||||
* @param ctx - current context.
|
||||
* @param msg - the current packet.
|
||||
*/
|
||||
protected void finishRead(ChannelHandlerContext ctx, Object msg) {
|
||||
// Assume same order
|
||||
PacketEvent event = finishQueue.pollFirst();
|
||||
|
||||
if (event != null) {
|
||||
NetworkMarker marker = NetworkMarker.getNetworkMarker(event);
|
||||
|
||||
if (marker != null) {
|
||||
processor.invokePostEvent(event, marker);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when we may need to handle the login packet.
|
||||
* @param packetClass - the packet class.
|
||||
* @param packet - the packet.
|
||||
*/
|
||||
protected void handleLogin(Class<?> packetClass, Object packet) {
|
||||
Class<?> loginClass = PACKET_LOGIN_CLIENT;
|
||||
FieldAccessor loginClient = LOGIN_GAME_PROFILE;
|
||||
|
||||
// Initialize packet class and login
|
||||
if (loginClass == null) {
|
||||
loginClass = PacketType.Login.Client.START.getPacketClass();
|
||||
PACKET_LOGIN_CLIENT = loginClass;
|
||||
}
|
||||
if (loginClient == null) {
|
||||
loginClient = Accessors.getFieldAccessor(PACKET_LOGIN_CLIENT, MinecraftReflection.getGameProfileClass(), true);
|
||||
LOGIN_GAME_PROFILE = loginClient;
|
||||
}
|
||||
|
||||
// See if we are dealing with the login packet
|
||||
if (loginClass.equals(packetClass)) {
|
||||
// GameProfile profile = (GameProfile) loginClient.get(packet);
|
||||
WrappedGameProfile profile = WrappedGameProfile.fromHandle(loginClient.get(packet));
|
||||
|
||||
// Save the channel injector
|
||||
factory.cacheInjector(profile.getName(), this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelActive(ChannelHandlerContext ctx) throws Exception {
|
||||
super.channelActive(ctx);
|
||||
|
||||
// See NetworkManager.channelActive(ChannelHandlerContext) for why
|
||||
if (channelField != null) {
|
||||
channelField.refreshValue();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve every byte in the given byte buffer.
|
||||
* @param buffer - the buffer.
|
||||
* @return The bytes.
|
||||
*/
|
||||
private byte[] getBytes(ByteBuf buffer) {
|
||||
byte[] data = new byte[buffer.readableBytes()];
|
||||
|
||||
buffer.readBytes(data);
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnect the current player.
|
||||
* @param message - the disconnect message, if possible.
|
||||
*/
|
||||
private void disconnect(String message) {
|
||||
// If we're logging in, we can only close the channel
|
||||
if (playerConnection == null || player instanceof Factory) {
|
||||
originalChannel.disconnect();
|
||||
} else {
|
||||
// Call the disconnect method
|
||||
try {
|
||||
MinecraftMethods.getDisconnectMethod(playerConnection.getClass()).
|
||||
invoke(playerConnection, message);
|
||||
} catch (Exception e) {
|
||||
throw new IllegalArgumentException("Unable to invoke disconnect method.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendServerPacket(Object packet, NetworkMarker marker, boolean filtered) {
|
||||
saveMarker(packet, marker);
|
||||
|
||||
try {
|
||||
scheduleProcessPackets.set(filtered);
|
||||
invokeSendPacket(packet);
|
||||
} finally {
|
||||
scheduleProcessPackets.set(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke the sendPacket method in Minecraft.
|
||||
* @param packet - the packet to send.
|
||||
*/
|
||||
private void invokeSendPacket(Object packet) {
|
||||
// Attempt to send the packet with NetworkMarker.handle(), or the PlayerConnection if its active
|
||||
try {
|
||||
if (player instanceof Factory) {
|
||||
MinecraftMethods.getNetworkManagerHandleMethod().invoke(networkManager, packet, new GenericFutureListener[0]);
|
||||
} else {
|
||||
MinecraftMethods.getSendPacketMethod().invoke(getPlayerConnection(), packet);
|
||||
}
|
||||
} catch (Throwable ex) {
|
||||
ProtocolLibrary.getErrorReporter().reportWarning(factory.getPlugin(),
|
||||
Report.newBuilder(REPORT_CANNOT_SEND_PACKET).messageParam(packet, playerName).error(ex).build());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void recieveClientPacket(final Object packet) {
|
||||
// TODO: Ensure the packet listeners are executed in the channel thread.
|
||||
|
||||
// Execute this in the channel thread
|
||||
Runnable action = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
MinecraftMethods.getNetworkManagerReadPacketMethod().invoke(networkManager, null, packet);
|
||||
} catch (Exception e) {
|
||||
// Inform the user
|
||||
ProtocolLibrary.getErrorReporter().reportMinimal(factory.getPlugin(), "recieveClientPacket", e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Execute in the worker thread
|
||||
if (originalChannel.eventLoop().inEventLoop()) {
|
||||
action.run();
|
||||
} else {
|
||||
originalChannel.eventLoop().execute(action);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Protocol getCurrentProtocol() {
|
||||
if (PROTOCOL_ACCESSOR == null) {
|
||||
PROTOCOL_ACCESSOR = Accessors.getFieldAccessor(
|
||||
networkManager.getClass(), MinecraftReflection.getEnumProtocolClass(), true);
|
||||
}
|
||||
return Protocol.fromVanilla((Enum<?>) PROTOCOL_ACCESSOR.get(networkManager));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the player connection of the current player.
|
||||
* @return The player connection.
|
||||
*/
|
||||
private Object getPlayerConnection() {
|
||||
if (playerConnection == null) {
|
||||
playerConnection = MinecraftFields.getPlayerConnection(player);
|
||||
}
|
||||
return playerConnection;
|
||||
}
|
||||
|
||||
@Override
|
||||
public NetworkMarker getMarker(Object packet) {
|
||||
return packetMarker.get(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveMarker(Object packet, NetworkMarker marker) {
|
||||
if (marker != null) {
|
||||
packetMarker.put(packet, marker);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Player getPlayer() {
|
||||
if (player == null && playerName != null) {
|
||||
return Bukkit.getPlayer(playerName);
|
||||
}
|
||||
|
||||
return player;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the player instance.
|
||||
* @param player - current instance.
|
||||
*/
|
||||
@Override
|
||||
public void setPlayer(Player player) {
|
||||
this.player = player;
|
||||
this.playerName = player.getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the updated player instance.
|
||||
* @param updated - updated instance.
|
||||
*/
|
||||
@Override
|
||||
public void setUpdatedPlayer(Player updated) {
|
||||
this.updated = updated;
|
||||
this.playerName = updated.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInjected() {
|
||||
return injected;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if this channel has been closed and cleaned up.
|
||||
* @return TRUE if it has, FALSE otherwise.
|
||||
*/
|
||||
@Override
|
||||
public boolean isClosed() {
|
||||
return closed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (!closed) {
|
||||
closed = true;
|
||||
|
||||
if (injected) {
|
||||
channelField.revertValue();
|
||||
|
||||
// Calling remove() in the main thread will block the main thread, which may lead
|
||||
// to a deadlock:
|
||||
// http://pastebin.com/L3SBVKzp
|
||||
//
|
||||
// ProtocolLib executes this close() method through a PlayerQuitEvent in the main thread,
|
||||
// which has implicitly aquired a lock on SimplePluginManager (see SimplePluginManager.callEvent(Event)).
|
||||
// Unfortunately, the remove() method will schedule the removal on one of the Netty worker threads if
|
||||
// it's called from a different thread, blocking until the removal has been confirmed.
|
||||
//
|
||||
// This is bad enough (Rule #1: Don't block the main thread), but the real trouble starts if the same
|
||||
// worker thread happens to be handling a server ping connection when this removal task is scheduled.
|
||||
// In that case, it may attempt to invoke an asynchronous ServerPingEvent (see PacketStatusListener)
|
||||
// using SimplePluginManager.callEvent(). But, since this has already been locked by the main thread,
|
||||
// we end up with a deadlock. The main thread is waiting for the worker thread to process the task, and
|
||||
// the worker thread is waiting for the main thread to finish executing PlayerQuitEvent.
|
||||
//
|
||||
// TL;DR: Concurrency is hard.
|
||||
executeInChannelThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
String[] handlers = new String[] {
|
||||
"protocol_lib_decoder", "protocol_lib_finish", "protocol_lib_encoder"
|
||||
};
|
||||
|
||||
for (String handler : handlers) {
|
||||
try {
|
||||
originalChannel.pipeline().remove(handler);
|
||||
} catch (NoSuchElementException e) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Clear cache
|
||||
factory.invalidate(player);
|
||||
|
||||
// Clear player instances
|
||||
// Should help fix memory leaks
|
||||
this.player = null;
|
||||
this.updated = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a specific command in the channel thread.
|
||||
* <p>
|
||||
* Exceptions are printed through the standard error reporter mechanism.
|
||||
* @param command - the command to execute.
|
||||
*/
|
||||
private void executeInChannelThread(final Runnable command) {
|
||||
originalChannel.eventLoop().execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
command.run();
|
||||
} catch (Exception e) {
|
||||
ProtocolLibrary.getErrorReporter().reportDetailed(NettyChannelInjector.this,
|
||||
Report.newBuilder(REPORT_CANNOT_EXECUTE_IN_CHANNEL_THREAD).error(e).build());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the first channel handler that is assignable to a given type.
|
||||
* @param channel - the channel.
|
||||
* @param clazz - the type.
|
||||
* @return The first handler, or NULL.
|
||||
*/
|
||||
public static ChannelHandler findChannelHandler(Channel channel, Class<?> clazz) {
|
||||
for (Entry<String, ChannelHandler> entry : channel.pipeline()) {
|
||||
if (clazz.isAssignableFrom(entry.getValue().getClass())) {
|
||||
return entry.getValue();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a socket injector that foreards to the current channel injector.
|
||||
* @author Kristian
|
||||
*/
|
||||
public static class ChannelSocketInjector implements SocketInjector {
|
||||
private final NettyChannelInjector injector;
|
||||
|
||||
public ChannelSocketInjector(NettyChannelInjector injector) {
|
||||
this.injector = Preconditions.checkNotNull(injector, "injector cannot be NULL");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket getSocket() throws IllegalAccessException {
|
||||
return NettySocketAdapter.adapt((SocketChannel) injector.originalChannel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SocketAddress getAddress() throws IllegalAccessException {
|
||||
return injector.originalChannel.remoteAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disconnect(String message) throws InvocationTargetException {
|
||||
injector.disconnect(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendServerPacket(Object packet, NetworkMarker marker, boolean filtered) throws InvocationTargetException {
|
||||
injector.sendServerPacket(packet, marker, filtered);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Player getPlayer() {
|
||||
return injector.getPlayer();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Player getUpdatedPlayer() {
|
||||
return injector.updated;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void transferState(SocketInjector delegate) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUpdatedPlayer(Player updatedPlayer) {
|
||||
injector.setPlayer(updatedPlayer);
|
||||
}
|
||||
|
||||
public NettyChannelInjector getChannelInjector() {
|
||||
return injector;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public WrappedChannel getChannel() {
|
||||
return new NettyChannel(originalChannel);
|
||||
}
|
||||
}
|
||||
+334
@@ -0,0 +1,334 @@
|
||||
/**
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2015 dmulloy2
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
package com.comphenix.protocol.compat.netty.independent;
|
||||
|
||||
import io.netty.buffer.ByteBufAllocator;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelConfig;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelMetadata;
|
||||
import io.netty.channel.ChannelPipeline;
|
||||
import io.netty.channel.ChannelProgressivePromise;
|
||||
import io.netty.channel.ChannelPromise;
|
||||
import io.netty.channel.EventLoop;
|
||||
import io.netty.util.Attribute;
|
||||
import io.netty.util.AttributeKey;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.net.SocketAddress;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import com.comphenix.protocol.reflect.accessors.Accessors;
|
||||
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
public abstract class NettyChannelProxy implements Channel {
|
||||
// Mark that a certain object does not contain a message field
|
||||
private static final FieldAccessor MARK_NO_MESSAGE = new FieldAccessor() {
|
||||
@Override
|
||||
public void set(Object instance, Object value) { }
|
||||
@Override
|
||||
public Object get(Object instance) { return null; }
|
||||
@Override
|
||||
public Field getField() { return null; };
|
||||
};
|
||||
|
||||
// Looking up packets in inner classes
|
||||
private static Map<Class<?>, FieldAccessor> MESSAGE_LOOKUP = Maps.newConcurrentMap();
|
||||
|
||||
// The underlying channel
|
||||
protected Channel delegate;
|
||||
protected Class<?> messageClass;
|
||||
|
||||
// Event loop proxy
|
||||
private transient NettyEventLoopProxy loopProxy;
|
||||
|
||||
public NettyChannelProxy(Channel delegate, Class<?> messageClass) {
|
||||
this.delegate = delegate;
|
||||
this.messageClass = messageClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when a packet is scheduled for transmission in the event loop.
|
||||
* @param <T> Type
|
||||
* @param callable - callable to schedule for execution.
|
||||
* @param packetAccessor - accessor for modifying the packet in the callable.
|
||||
* @return The callable that will be scheduled, or NULL to cancel.
|
||||
*/
|
||||
protected abstract <T> Callable<T> onMessageScheduled(Callable<T> callable, FieldAccessor packetAccessor);
|
||||
|
||||
/**
|
||||
* Invoked when a packet is scheduled for transmission in the event loop.
|
||||
* @param runnable - the runnable that contains a packet to be scheduled.
|
||||
* @param packetAccessor - accessor for modifying the packet in the runnable.
|
||||
* @return The runnable that will be scheduled, or NULL to cancel.
|
||||
*/
|
||||
protected abstract Runnable onMessageScheduled(Runnable runnable, FieldAccessor packetAccessor);
|
||||
|
||||
@Override
|
||||
public <T> Attribute<T> attr(AttributeKey<T> paramAttributeKey) {
|
||||
return delegate.attr(paramAttributeKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture bind(SocketAddress paramSocketAddress) {
|
||||
return delegate.bind(paramSocketAddress);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelPipeline pipeline() {
|
||||
return delegate.pipeline();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture connect(SocketAddress paramSocketAddress) {
|
||||
return delegate.connect(paramSocketAddress);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBufAllocator alloc() {
|
||||
return delegate.alloc();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelPromise newPromise() {
|
||||
return delegate.newPromise();
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventLoop eventLoop() {
|
||||
if (loopProxy == null) {
|
||||
loopProxy = new NettyEventLoopProxy() {
|
||||
@Override
|
||||
protected EventLoop getDelegate() {
|
||||
return delegate.eventLoop();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Runnable schedulingRunnable(final Runnable runnable) {
|
||||
final FieldAccessor accessor = getMessageAccessor(runnable);
|
||||
|
||||
if (accessor != null) {
|
||||
Runnable result = onMessageScheduled(runnable, accessor);;
|
||||
return result != null ? result : getEmptyRunnable();
|
||||
}
|
||||
return runnable;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected <T> Callable<T> schedulingCallable(Callable<T> callable) {
|
||||
FieldAccessor accessor = getMessageAccessor(callable);
|
||||
|
||||
if (accessor != null) {
|
||||
Callable<T> result = onMessageScheduled(callable, accessor);;
|
||||
return result != null ? result : NettyEventLoopProxy.<T>getEmptyCallable();
|
||||
}
|
||||
return callable;
|
||||
}
|
||||
};
|
||||
}
|
||||
return loopProxy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a way to access the packet field of an object.
|
||||
* @param value - the object.
|
||||
* @return The packet field accessor, or NULL if not found.
|
||||
*/
|
||||
private FieldAccessor getMessageAccessor(Object value) {
|
||||
Class<?> clazz = value.getClass();
|
||||
FieldAccessor accessor = MESSAGE_LOOKUP.get(clazz);
|
||||
|
||||
if (accessor == null) {
|
||||
try {
|
||||
accessor = Accessors.getFieldAccessor(clazz, messageClass, true);
|
||||
} catch (IllegalArgumentException e) {
|
||||
accessor = MARK_NO_MESSAGE;
|
||||
}
|
||||
// Save the result
|
||||
MESSAGE_LOOKUP.put(clazz, accessor);
|
||||
}
|
||||
return accessor != MARK_NO_MESSAGE ? accessor : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture connect(SocketAddress paramSocketAddress1,
|
||||
SocketAddress paramSocketAddress2) {
|
||||
return delegate.connect(paramSocketAddress1, paramSocketAddress2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelProgressivePromise newProgressivePromise() {
|
||||
return delegate.newProgressivePromise();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Channel parent() {
|
||||
return delegate.parent();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelConfig config() {
|
||||
return delegate.config();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture newSucceededFuture() {
|
||||
return delegate.newSucceededFuture();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOpen() {
|
||||
return delegate.isOpen();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture disconnect() {
|
||||
return delegate.disconnect();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRegistered() {
|
||||
return delegate.isRegistered();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture newFailedFuture(Throwable paramThrowable) {
|
||||
return delegate.newFailedFuture(paramThrowable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture close() {
|
||||
return delegate.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isActive() {
|
||||
return delegate.isActive();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public ChannelFuture deregister() {
|
||||
return delegate.deregister();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelPromise voidPromise() {
|
||||
return delegate.voidPromise();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelMetadata metadata() {
|
||||
return delegate.metadata();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture bind(SocketAddress paramSocketAddress,
|
||||
ChannelPromise paramChannelPromise) {
|
||||
return delegate.bind(paramSocketAddress, paramChannelPromise);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SocketAddress localAddress() {
|
||||
return delegate.localAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SocketAddress remoteAddress() {
|
||||
return delegate.remoteAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture connect(SocketAddress paramSocketAddress,
|
||||
ChannelPromise paramChannelPromise) {
|
||||
return delegate.connect(paramSocketAddress, paramChannelPromise);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture closeFuture() {
|
||||
return delegate.closeFuture();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isWritable() {
|
||||
return delegate.isWritable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Channel flush() {
|
||||
return delegate.flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture connect(SocketAddress paramSocketAddress1,
|
||||
SocketAddress paramSocketAddress2, ChannelPromise paramChannelPromise) {
|
||||
return delegate.connect(paramSocketAddress1, paramSocketAddress2, paramChannelPromise);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Channel read() {
|
||||
return delegate.read();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Unsafe unsafe() {
|
||||
return delegate.unsafe();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture disconnect(ChannelPromise paramChannelPromise) {
|
||||
return delegate.disconnect(paramChannelPromise);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture close(ChannelPromise paramChannelPromise) {
|
||||
return delegate.close(paramChannelPromise);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public ChannelFuture deregister(ChannelPromise paramChannelPromise) {
|
||||
return delegate.deregister(paramChannelPromise);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture write(Object paramObject) {
|
||||
return delegate.write(paramObject);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture write(Object paramObject, ChannelPromise paramChannelPromise) {
|
||||
return delegate.write(paramObject, paramChannelPromise);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture writeAndFlush(Object paramObject, ChannelPromise paramChannelPromise) {
|
||||
return delegate.writeAndFlush(paramObject, paramChannelPromise);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture writeAndFlush(Object paramObject) {
|
||||
return delegate.writeAndFlush(paramObject);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Channel o) {
|
||||
return delegate.compareTo(o);
|
||||
}
|
||||
}
|
||||
+259
@@ -0,0 +1,259 @@
|
||||
/**
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2015 dmulloy2
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
package com.comphenix.protocol.compat.netty.independent;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelPromise;
|
||||
import io.netty.channel.EventLoop;
|
||||
import io.netty.channel.EventLoopGroup;
|
||||
import io.netty.util.concurrent.EventExecutor;
|
||||
import io.netty.util.concurrent.Future;
|
||||
import io.netty.util.concurrent.ProgressivePromise;
|
||||
import io.netty.util.concurrent.Promise;
|
||||
import io.netty.util.concurrent.ScheduledFuture;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
/**
|
||||
* An event loop proxy.
|
||||
* @author Kristian.
|
||||
*/
|
||||
abstract class NettyEventLoopProxy implements EventLoop {
|
||||
private static final Runnable EMPTY_RUNNABLE = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// Do nothing
|
||||
}
|
||||
};
|
||||
private static final Callable<?> EMPTY_CALLABLE = new Callable<Object>() {
|
||||
@Override
|
||||
public Object call() throws Exception {
|
||||
return null;
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve the underlying event loop.
|
||||
* @return The event loop.
|
||||
*/
|
||||
protected abstract EventLoop getDelegate();
|
||||
|
||||
/**
|
||||
* Retrieve a callable that does nothing but return NULL.
|
||||
* @return The empty callable.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> Callable<T> getEmptyCallable() {
|
||||
return (Callable<T>) EMPTY_CALLABLE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a runnable that does nothing.
|
||||
* @return A NO-OP runnable.
|
||||
*/
|
||||
public static Runnable getEmptyRunnable() {
|
||||
return EMPTY_RUNNABLE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when a runnable is being scheduled.
|
||||
* @param runnable - the runnable that is scheduling.
|
||||
* @return The runnable to schedule instead. Cannot be NULL.
|
||||
*/
|
||||
protected abstract Runnable schedulingRunnable(Runnable runnable);
|
||||
|
||||
/**
|
||||
* Invoked when a callable is being scheduled.
|
||||
* @param runnable - the callable that is scheduling.
|
||||
* @return The callable to schedule instead. Cannot be NULL.
|
||||
*/
|
||||
protected abstract <T> Callable<T> schedulingCallable(Callable<T> callable);
|
||||
|
||||
@Override
|
||||
public void execute(Runnable command) {
|
||||
getDelegate().execute(schedulingRunnable(command));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Future<T> submit(Callable<T> action) {
|
||||
return getDelegate().submit(schedulingCallable(action));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Future<T> submit(Runnable action, T arg1) {
|
||||
return getDelegate().submit(schedulingRunnable(action), arg1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<?> submit(Runnable action) {
|
||||
return getDelegate().submit(schedulingRunnable(action));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <V> ScheduledFuture<V> schedule(Callable<V> action, long arg1, TimeUnit arg2) {
|
||||
return getDelegate().schedule(schedulingCallable(action), arg1, arg2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScheduledFuture<?> schedule(Runnable action, long arg1, TimeUnit arg2) {
|
||||
return getDelegate().schedule(schedulingRunnable(action), arg1, arg2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScheduledFuture<?> scheduleAtFixedRate(Runnable action, long arg1, long arg2, TimeUnit arg3) {
|
||||
return getDelegate().scheduleAtFixedRate(schedulingRunnable(action), arg1, arg2, arg3);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable action, long arg1, long arg2, TimeUnit arg3) {
|
||||
return getDelegate().scheduleWithFixedDelay(schedulingRunnable(action), arg1, arg2, arg3);
|
||||
}
|
||||
|
||||
// Boiler plate:
|
||||
@Override
|
||||
public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
|
||||
return getDelegate().awaitTermination(timeout, unit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean inEventLoop() {
|
||||
return getDelegate().inEventLoop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean inEventLoop(Thread arg0) {
|
||||
return getDelegate().inEventLoop(arg0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isShutdown() {
|
||||
return getDelegate().isShutdown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTerminated() {
|
||||
return getDelegate().isTerminated();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> List<java.util.concurrent.Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
|
||||
throws InterruptedException {
|
||||
return getDelegate().invokeAll(tasks);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> List<java.util.concurrent.Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout,
|
||||
TimeUnit unit) throws InterruptedException {
|
||||
return getDelegate().invokeAll(tasks, timeout, unit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException,
|
||||
ExecutionException {
|
||||
return getDelegate().invokeAny(tasks);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
|
||||
throws InterruptedException, ExecutionException, TimeoutException {
|
||||
return getDelegate().invokeAny(tasks, timeout, unit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isShuttingDown() {
|
||||
return getDelegate().isShuttingDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<EventExecutor> iterator() {
|
||||
return getDelegate().iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <V> Future<V> newFailedFuture(Throwable arg0) {
|
||||
return getDelegate().newFailedFuture(arg0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventLoop next() {
|
||||
return ((EventLoopGroup) getDelegate()).next();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <V> ProgressivePromise<V> newProgressivePromise() {
|
||||
return getDelegate().newProgressivePromise();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <V> Promise<V> newPromise() {
|
||||
return getDelegate().newPromise();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <V> Future<V> newSucceededFuture(V arg0) {
|
||||
return getDelegate().newSucceededFuture(arg0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventLoopGroup parent() {
|
||||
return getDelegate().parent();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture register(Channel arg0, ChannelPromise arg1) {
|
||||
return getDelegate().register(arg0, arg1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture register(Channel arg0) {
|
||||
return getDelegate().register(arg0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<?> shutdownGracefully() {
|
||||
return getDelegate().shutdownGracefully();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<?> shutdownGracefully(long arg0, long arg1, TimeUnit arg2) {
|
||||
return getDelegate().shutdownGracefully(arg0, arg1, arg2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<?> terminationFuture() {
|
||||
return getDelegate().terminationFuture();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public void shutdown() {
|
||||
getDelegate().shutdown();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public List<Runnable> shutdownNow() {
|
||||
return getDelegate().shutdownNow();
|
||||
}
|
||||
}
|
||||
+239
@@ -0,0 +1,239 @@
|
||||
/**
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2015 dmulloy2
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
package com.comphenix.protocol.compat.netty.independent;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import com.comphenix.protocol.compat.netty.independent.NettyChannelInjector.ChannelSocketInjector;
|
||||
import com.comphenix.protocol.injector.netty.ChannelListener;
|
||||
import com.comphenix.protocol.injector.netty.ClosedInjector;
|
||||
import com.comphenix.protocol.injector.netty.Injector;
|
||||
import com.comphenix.protocol.injector.server.SocketInjector;
|
||||
import com.comphenix.protocol.injector.server.TemporaryPlayerFactory;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.comphenix.protocol.utility.MinecraftFields;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
import com.google.common.collect.MapMaker;
|
||||
|
||||
/**
|
||||
* Represents an injector factory.
|
||||
* <p>
|
||||
* Note that the factory will return {@link ClosedInjector} when the factory is closed.
|
||||
* @author Kristian
|
||||
*/
|
||||
public class NettyInjectionFactory {
|
||||
// This should work as long as the injectors are, uh, injected
|
||||
private final ConcurrentMap<Player, Injector> playerLookup = new MapMaker().weakKeys().weakValues().makeMap();
|
||||
private final ConcurrentMap<String, Injector> nameLookup = new MapMaker().weakValues().makeMap();
|
||||
|
||||
// Whether or not the factory is closed
|
||||
private volatile boolean closed;
|
||||
|
||||
// The current plugin
|
||||
private final Plugin plugin;
|
||||
|
||||
public NettyInjectionFactory(Plugin plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the main plugin associated with this injection factory.
|
||||
* @return The main plugin.
|
||||
*/
|
||||
public Plugin getPlugin() {
|
||||
return plugin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct or retrieve a channel injector from an existing Bukkit player.
|
||||
* @param player - the existing Bukkit player.
|
||||
* @param listener - the listener.
|
||||
* @return A new injector, an existing injector associated with this player, or a closed injector.
|
||||
*/
|
||||
@Nonnull
|
||||
public Injector fromPlayer(Player player, ChannelListener listener) {
|
||||
if (closed)
|
||||
return new ClosedInjector(player);
|
||||
Injector injector = playerLookup.get(player);
|
||||
|
||||
// Find a temporary injector as well
|
||||
if (injector == null)
|
||||
injector = getTemporaryInjector(player);
|
||||
if (injector != null && !injector.isClosed())
|
||||
return injector;
|
||||
|
||||
Object networkManager = MinecraftFields.getNetworkManager(player);
|
||||
|
||||
// Must be a temporary Bukkit player
|
||||
if (networkManager == null) {
|
||||
return fromName(player.getName(), player);
|
||||
}
|
||||
Channel channel = FuzzyReflection.getFieldValue(networkManager, Channel.class, true);
|
||||
|
||||
// See if a channel has already been created
|
||||
injector = (NettyChannelInjector) NettyChannelInjector.findChannelHandler(channel, NettyChannelInjector.class);
|
||||
|
||||
if (injector != null) {
|
||||
// Update the player instance
|
||||
playerLookup.remove(injector.getPlayer());
|
||||
injector.setPlayer(player);
|
||||
} else {
|
||||
injector = new NettyChannelInjector(player, networkManager, channel, listener, this);
|
||||
}
|
||||
|
||||
// Cache injector and return
|
||||
cacheInjector(player, injector);
|
||||
return injector;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a cached injector from a name.
|
||||
* <p>
|
||||
* The injector may be NULL if the plugin has been reloaded during a player login.
|
||||
* @param name - the name.
|
||||
* @param player - the player.
|
||||
* @return The cached injector, or a closed injector if it could not be found.
|
||||
*/
|
||||
public Injector fromName(String name, Player player) {
|
||||
if (!closed) {
|
||||
Injector injector = nameLookup.get(name);
|
||||
|
||||
// We can only retrieve cached injectors
|
||||
if (injector != null) {
|
||||
// Update instance
|
||||
injector.setUpdatedPlayer(player);
|
||||
return injector;
|
||||
}
|
||||
}
|
||||
return new ClosedInjector(player);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new channel injector for the given channel.
|
||||
* @param channel - the channel.
|
||||
* @param listener - the listener.
|
||||
* @param playerFactory - a temporary player creator.
|
||||
* @return The channel injector, or a closed injector.
|
||||
*/
|
||||
@Nonnull
|
||||
public Injector fromChannel(Channel channel, ChannelListener listener, TemporaryPlayerFactory playerFactory) {
|
||||
if (closed)
|
||||
return new ClosedInjector(null);
|
||||
|
||||
Object networkManager = findNetworkManager(channel);
|
||||
Player temporaryPlayer = playerFactory.createTemporaryPlayer(Bukkit.getServer());
|
||||
NettyChannelInjector injector = new NettyChannelInjector(temporaryPlayer, networkManager, channel, listener, this);
|
||||
|
||||
// Initialize temporary player
|
||||
TemporaryPlayerFactory.setInjectorInPlayer(temporaryPlayer, new ChannelSocketInjector(injector));
|
||||
return injector;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidate a cached injector.
|
||||
* @param player - the associated player.
|
||||
* @return The cached injector, or NULL if nothing was cached.
|
||||
*/
|
||||
public Injector invalidate(Player player) {
|
||||
Injector injector = playerLookup.remove(player);
|
||||
|
||||
nameLookup.remove(player.getName());
|
||||
return injector;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cache an injector by player.
|
||||
* @param player - the player.
|
||||
* @param injector - the injector to cache.
|
||||
* @return The previously cached injector.
|
||||
*/
|
||||
public Injector cacheInjector(Player player, Injector injector) {
|
||||
nameLookup.put(player.getName(), injector);
|
||||
return playerLookup.put(player, injector);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cache an injector by name alone.
|
||||
* @param name - the name to lookup.
|
||||
* @param injector - the injector.
|
||||
* @return The cached injector.
|
||||
*/
|
||||
public Injector cacheInjector(String name, Injector injector) {
|
||||
return nameLookup.put(name, injector);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the associated channel injector.
|
||||
* @param player - the temporary player, or normal Bukkit player.
|
||||
* @return The associated injector, or NULL if this is a Bukkit player.
|
||||
*/
|
||||
private NettyChannelInjector getTemporaryInjector(Player player) {
|
||||
SocketInjector injector = TemporaryPlayerFactory.getInjectorFromPlayer(player);
|
||||
|
||||
if (injector != null) {
|
||||
return ((ChannelSocketInjector) injector).getChannelInjector();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the network manager in a channel's pipeline.
|
||||
* @param channel - the channel.
|
||||
* @return The network manager.
|
||||
*/
|
||||
private Object findNetworkManager(Channel channel) {
|
||||
// Find the network manager
|
||||
Object networkManager = NettyChannelInjector.findChannelHandler(channel, MinecraftReflection.getNetworkManagerClass());
|
||||
|
||||
if (networkManager != null)
|
||||
return networkManager;
|
||||
throw new IllegalArgumentException("Unable to find NetworkManager in " + channel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the factory is closed.
|
||||
* <p>
|
||||
* If it is, all new injectors will be closed by default.
|
||||
* @return TRUE if it is closed, FALSE otherwise.
|
||||
*/
|
||||
public boolean isClosed() {
|
||||
return closed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Close all injectors created by this factory, and cease the creation of new injections.
|
||||
*/
|
||||
public synchronized void close() {
|
||||
if (!closed) {
|
||||
closed = true;
|
||||
|
||||
// Close everything
|
||||
for (Injector injector : playerLookup.values())
|
||||
injector.close();
|
||||
for (Injector injector : nameLookup.values())
|
||||
injector.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
+371
@@ -0,0 +1,371 @@
|
||||
/**
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2015 dmulloy2
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
package com.comphenix.protocol.compat.netty.independent;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelPipeline;
|
||||
import io.netty.channel.ChannelPromise;
|
||||
import io.netty.util.concurrent.EventExecutorGroup;
|
||||
|
||||
import java.net.SocketAddress;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
/**
|
||||
* A pipeline proxy.
|
||||
* @author Kristian
|
||||
*/
|
||||
public class NettyPipelineProxy implements ChannelPipeline {
|
||||
protected final ChannelPipeline pipeline;
|
||||
protected final Channel channel;
|
||||
|
||||
public NettyPipelineProxy(ChannelPipeline pipeline, Channel channel) {
|
||||
this.pipeline = pipeline;
|
||||
this.channel = channel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelPipeline addAfter(EventExecutorGroup arg0, String arg1, String arg2, ChannelHandler arg3) {
|
||||
pipeline.addAfter(arg0, arg1, arg2, arg3);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelPipeline addAfter(String arg0, String arg1, ChannelHandler arg2) {
|
||||
pipeline.addAfter(arg0, arg1, arg2);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelPipeline addBefore(EventExecutorGroup arg0, String arg1, String arg2, ChannelHandler arg3) {
|
||||
pipeline.addBefore(arg0, arg1, arg2, arg3);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelPipeline addBefore(String arg0, String arg1, ChannelHandler arg2) {
|
||||
pipeline.addBefore(arg0, arg1, arg2);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelPipeline addFirst(ChannelHandler... arg0) {
|
||||
pipeline.addFirst(arg0);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelPipeline addFirst(EventExecutorGroup arg0, ChannelHandler... arg1) {
|
||||
pipeline.addFirst(arg0, arg1);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelPipeline addFirst(EventExecutorGroup arg0, String arg1, ChannelHandler arg2) {
|
||||
pipeline.addFirst(arg0, arg1, arg2);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelPipeline addFirst(String arg0, ChannelHandler arg1) {
|
||||
pipeline.addFirst(arg0, arg1);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelPipeline addLast(ChannelHandler... arg0) {
|
||||
pipeline.addLast(arg0);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelPipeline addLast(EventExecutorGroup arg0, ChannelHandler... arg1) {
|
||||
pipeline.addLast(arg0, arg1);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelPipeline addLast(EventExecutorGroup arg0, String arg1, ChannelHandler arg2) {
|
||||
pipeline.addLast(arg0, arg1, arg2);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelPipeline addLast(String arg0, ChannelHandler arg1) {
|
||||
pipeline.addLast(arg0, arg1);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture bind(SocketAddress arg0, ChannelPromise arg1) {
|
||||
return pipeline.bind(arg0, arg1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture bind(SocketAddress arg0) {
|
||||
return pipeline.bind(arg0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Channel channel() {
|
||||
return channel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture close() {
|
||||
return pipeline.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture close(ChannelPromise arg0) {
|
||||
return pipeline.close(arg0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture connect(SocketAddress arg0, ChannelPromise arg1) {
|
||||
return pipeline.connect(arg0, arg1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture connect(SocketAddress arg0, SocketAddress arg1, ChannelPromise arg2) {
|
||||
return pipeline.connect(arg0, arg1, arg2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture connect(SocketAddress arg0, SocketAddress arg1) {
|
||||
return pipeline.connect(arg0, arg1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture connect(SocketAddress arg0) {
|
||||
return pipeline.connect(arg0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelHandlerContext context(ChannelHandler arg0) {
|
||||
return pipeline.context(arg0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelHandlerContext context(Class<? extends ChannelHandler> arg0) {
|
||||
return pipeline.context(arg0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelHandlerContext context(String arg0) {
|
||||
return pipeline.context(arg0);
|
||||
}
|
||||
|
||||
// We have to call the depreciated methods to properly implement the proxy
|
||||
@Override
|
||||
public ChannelFuture deregister() {
|
||||
return pipeline.deregister();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture deregister(ChannelPromise arg0) {
|
||||
return pipeline.deregister(arg0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelPipeline fireChannelUnregistered() {
|
||||
pipeline.fireChannelUnregistered();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture disconnect() {
|
||||
return pipeline.disconnect();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture disconnect(ChannelPromise arg0) {
|
||||
return pipeline.disconnect(arg0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelPipeline fireChannelActive() {
|
||||
pipeline.fireChannelActive();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelPipeline fireChannelInactive() {
|
||||
pipeline.fireChannelInactive();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelPipeline fireChannelRead(Object arg0) {
|
||||
pipeline.fireChannelRead(arg0);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelPipeline fireChannelReadComplete() {
|
||||
pipeline.fireChannelReadComplete();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelPipeline fireChannelRegistered() {
|
||||
pipeline.fireChannelRegistered();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelPipeline fireChannelWritabilityChanged() {
|
||||
pipeline.fireChannelWritabilityChanged();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelPipeline fireExceptionCaught(Throwable arg0) {
|
||||
pipeline.fireExceptionCaught(arg0);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelPipeline fireUserEventTriggered(Object arg0) {
|
||||
pipeline.fireUserEventTriggered(arg0);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelHandler first() {
|
||||
return pipeline.first();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelHandlerContext firstContext() {
|
||||
return pipeline.firstContext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelPipeline flush() {
|
||||
pipeline.flush();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends ChannelHandler> T get(Class<T> arg0) {
|
||||
return pipeline.get(arg0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelHandler get(String arg0) {
|
||||
return pipeline.get(arg0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Entry<String, ChannelHandler>> iterator() {
|
||||
return pipeline.iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelHandler last() {
|
||||
return pipeline.last();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelHandlerContext lastContext() {
|
||||
return pipeline.lastContext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> names() {
|
||||
return pipeline.names();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelPipeline read() {
|
||||
pipeline.read();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelPipeline remove(ChannelHandler arg0) {
|
||||
pipeline.remove(arg0);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends ChannelHandler> T remove(Class<T> arg0) {
|
||||
return pipeline.remove(arg0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelHandler remove(String arg0) {
|
||||
return pipeline.remove(arg0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelHandler removeFirst() {
|
||||
return pipeline.removeFirst();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelHandler removeLast() {
|
||||
return pipeline.removeLast();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelPipeline replace(ChannelHandler arg0, String arg1, ChannelHandler arg2) {
|
||||
pipeline.replace(arg0, arg1, arg2);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends ChannelHandler> T replace(Class<T> arg0, String arg1, ChannelHandler arg2) {
|
||||
return pipeline.replace(arg0, arg1, arg2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelHandler replace(String arg0, String arg1, ChannelHandler arg2) {
|
||||
return pipeline.replace(arg0, arg1, arg2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, ChannelHandler> toMap() {
|
||||
return pipeline.toMap();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture write(Object arg0, ChannelPromise arg1) {
|
||||
return pipeline.write(arg0, arg1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture write(Object arg0) {
|
||||
return pipeline.write(arg0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture writeAndFlush(Object arg0, ChannelPromise arg1) {
|
||||
return pipeline.writeAndFlush(arg0, arg1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture writeAndFlush(Object arg0) {
|
||||
return pipeline.writeAndFlush(arg0);
|
||||
}
|
||||
}
|
||||
+441
@@ -0,0 +1,441 @@
|
||||
/**
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2015 dmulloy2
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
package com.comphenix.protocol.compat.netty.independent;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInboundHandler;
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.compat.netty.ChannelInjector;
|
||||
import com.comphenix.protocol.compat.netty.ProtocolInjector;
|
||||
import com.comphenix.protocol.compat.netty.WrappedChannel;
|
||||
import com.comphenix.protocol.concurrency.PacketTypeSet;
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.error.Report;
|
||||
import com.comphenix.protocol.error.ReportType;
|
||||
import com.comphenix.protocol.events.ConnectionSide;
|
||||
import com.comphenix.protocol.events.ListenerOptions;
|
||||
import com.comphenix.protocol.events.NetworkMarker;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.injector.ListenerInvoker;
|
||||
import com.comphenix.protocol.injector.netty.ChannelListener;
|
||||
import com.comphenix.protocol.injector.netty.Injector;
|
||||
import com.comphenix.protocol.injector.netty.NettyNetworkMarker;
|
||||
import com.comphenix.protocol.injector.packet.PacketInjector;
|
||||
import com.comphenix.protocol.injector.packet.PacketRegistry;
|
||||
import com.comphenix.protocol.injector.player.PlayerInjectionHandler;
|
||||
import com.comphenix.protocol.injector.server.TemporaryPlayerFactory;
|
||||
import com.comphenix.protocol.injector.spigot.AbstractPacketInjector;
|
||||
import com.comphenix.protocol.injector.spigot.AbstractPlayerHandler;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.comphenix.protocol.reflect.VolatileField;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
public class NettyProtocolInjector implements ProtocolInjector {
|
||||
public static final ReportType REPORT_CANNOT_INJECT_INCOMING_CHANNEL = new ReportType("Unable to inject incoming channel %s.");
|
||||
|
||||
private volatile boolean injected;
|
||||
private volatile boolean closed;
|
||||
|
||||
// The temporary player factory
|
||||
private TemporaryPlayerFactory playerFactory = new TemporaryPlayerFactory();
|
||||
private List<VolatileField> bootstrapFields = Lists.newArrayList();
|
||||
|
||||
// The channel injector factory
|
||||
private NettyInjectionFactory injectionFactory;
|
||||
|
||||
// List of network managers
|
||||
private volatile List<Object> networkManagers;
|
||||
|
||||
// Different sending filters
|
||||
private PacketTypeSet sendingFilters = new PacketTypeSet();
|
||||
private PacketTypeSet reveivedFilters = new PacketTypeSet();
|
||||
|
||||
// Packets that must be executed on the main thread
|
||||
private PacketTypeSet mainThreadFilters = new PacketTypeSet();
|
||||
|
||||
// Which packets are buffered
|
||||
private PacketTypeSet bufferedPackets = new PacketTypeSet();
|
||||
private ListenerInvoker invoker;
|
||||
|
||||
// Handle errors
|
||||
private ErrorReporter reporter;
|
||||
private boolean debug;
|
||||
|
||||
public NettyProtocolInjector(Plugin plugin, ListenerInvoker invoker, ErrorReporter reporter) {
|
||||
this.injectionFactory = new NettyInjectionFactory(plugin);
|
||||
this.invoker = invoker;
|
||||
this.reporter = reporter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDebug() {
|
||||
return debug;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether or not the debug mode is enabled.
|
||||
* @param debug - TRUE if it is, FALSE otherwise.
|
||||
*/
|
||||
@Override
|
||||
public void setDebug(boolean debug) {
|
||||
this.debug = debug;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inject into the spigot connection class.
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public synchronized void inject() {
|
||||
if (injected)
|
||||
throw new IllegalStateException("Cannot inject twice.");
|
||||
try {
|
||||
FuzzyReflection fuzzyServer = FuzzyReflection.fromClass(MinecraftReflection.getMinecraftServerClass());
|
||||
List<Method> serverConnectionMethods = fuzzyServer.getMethodListByParameters(MinecraftReflection.getServerConnectionClass(), new Class[] {});
|
||||
|
||||
// Get the server connection
|
||||
Object server = fuzzyServer.getSingleton();
|
||||
Object serverConnection = null;
|
||||
|
||||
for (Method method : serverConnectionMethods) {
|
||||
try {
|
||||
serverConnection = method.invoke(server);
|
||||
|
||||
// Continue until we get a server connection
|
||||
if (serverConnection != null) {
|
||||
break;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// Try the next though
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
// Handle connected channels
|
||||
final ChannelInboundHandler endInitProtocol = new ChannelInitializer<Channel>() {
|
||||
@Override
|
||||
protected void initChannel(Channel channel) throws Exception {
|
||||
try {
|
||||
// This can take a while, so we need to stop the main thread from interfering
|
||||
synchronized (networkManagers) {
|
||||
injectionFactory.fromChannel(channel, NettyProtocolInjector.this, playerFactory).inject();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
reporter.reportDetailed(NettyProtocolInjector.this, Report.newBuilder(REPORT_CANNOT_INJECT_INCOMING_CHANNEL).messageParam(channel).error(e));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// This is executed before Minecraft's channel handler
|
||||
final ChannelInboundHandler beginInitProtocol = new ChannelInitializer<Channel>() {
|
||||
@Override
|
||||
protected void initChannel(Channel channel) throws Exception {
|
||||
// Our only job is to add init protocol
|
||||
channel.pipeline().addLast(endInitProtocol);
|
||||
}
|
||||
};
|
||||
|
||||
// Add our handler to newly created channels
|
||||
final ChannelHandler connectionHandler = new ChannelInboundHandlerAdapter() {
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
||||
Channel channel = (Channel) msg;
|
||||
|
||||
// Prepare to initialize ths channel
|
||||
channel.pipeline().addFirst(beginInitProtocol);
|
||||
ctx.fireChannelRead(msg);
|
||||
}
|
||||
};
|
||||
|
||||
// Get the current NetworkMananger list
|
||||
networkManagers = (List<Object>) FuzzyReflection.fromObject(serverConnection, true).invokeMethod(null, "getNetworkManagers", List.class, serverConnection);
|
||||
|
||||
// Insert ProtocolLib's connection interceptor
|
||||
bootstrapFields = getBootstrapFields(serverConnection);
|
||||
|
||||
for (VolatileField field : bootstrapFields) {
|
||||
final List<Object> list = (List<Object>) field.getValue();
|
||||
|
||||
// We don't have to override this list
|
||||
if (list == networkManagers) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Synchronize with each list before we attempt to replace them.
|
||||
field.setValue(new NettyBootstrapList(list, connectionHandler));
|
||||
}
|
||||
|
||||
injected = true;
|
||||
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Unable to inject channel futures.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasListener(Class<?> packetClass) {
|
||||
return reveivedFilters.contains(packetClass) || sendingFilters.contains(packetClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasMainThreadListener(Class<?> packetClass) {
|
||||
return mainThreadFilters.contains(packetClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ErrorReporter getReporter() {
|
||||
return reporter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inject our packet handling into a specific player.
|
||||
* @param player Player to inject into
|
||||
*/
|
||||
public void injectPlayer(Player player) {
|
||||
injectionFactory.fromPlayer(player, this).inject();
|
||||
}
|
||||
|
||||
private List<VolatileField> getBootstrapFields(Object serverConnection) {
|
||||
List<VolatileField> result = Lists.newArrayList();
|
||||
|
||||
// Find and (possibly) proxy every list
|
||||
for (Field field : FuzzyReflection.fromObject(serverConnection, true).getFieldListByType(List.class)) {
|
||||
VolatileField volatileField = new VolatileField(field, serverConnection, true).toSynchronized();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Object> list = (List<Object>) volatileField.getValue();
|
||||
|
||||
if (list.size() == 0 || list.get(0) instanceof ChannelFuture) {
|
||||
result.add(volatileField);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up any remaning injections.
|
||||
*/
|
||||
@Override
|
||||
public synchronized void close() {
|
||||
if (!closed) {
|
||||
closed = true;
|
||||
|
||||
for (VolatileField field : bootstrapFields) {
|
||||
Object value = field.getValue();
|
||||
|
||||
// Undo the processed channels, if any
|
||||
if (value instanceof NettyBootstrapList) {
|
||||
((NettyBootstrapList) value).close();
|
||||
}
|
||||
field.revertValue();
|
||||
}
|
||||
// Uninject all the players
|
||||
injectionFactory.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketEvent onPacketSending(Injector injector, Object packet, NetworkMarker marker) {
|
||||
Class<?> clazz = packet.getClass();
|
||||
|
||||
if (sendingFilters.contains(clazz) || marker != null) {
|
||||
try {
|
||||
PacketContainer container = new PacketContainer(PacketRegistry.getPacketType(clazz), packet);
|
||||
return packetQueued(container, injector.getPlayer(), marker);
|
||||
} catch (LinkageError e) {
|
||||
// So far this has been seen when the jar is shared
|
||||
System.err.println("[ProtocolLib] Encountered a LinkageError. Make sure you\'re not using this jar for multiple server instances!");
|
||||
System.err.println("[ProtocolLib] If you\'re getting this error for other reasons, please report it!");
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
// Don't change anything
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketEvent onPacketReceiving(Injector injector, Object packet, NetworkMarker marker) {
|
||||
Class<?> clazz = packet.getClass();
|
||||
|
||||
if (reveivedFilters.contains(clazz) || marker != null) {
|
||||
PacketContainer container = new PacketContainer(PacketRegistry.getPacketType(clazz), packet);
|
||||
return packetReceived(container, injector.getPlayer(), marker);
|
||||
}
|
||||
|
||||
// Don't change anything
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean includeBuffer(Class<?> packetClass) {
|
||||
return bufferedPackets.contains(packetClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to inform the event listeners of a queued packet.
|
||||
* @param packet - the packet that is to be sent.
|
||||
* @param receiver - the receiver of this packet.
|
||||
* @return The packet event that was used.
|
||||
*/
|
||||
private PacketEvent packetQueued(PacketContainer packet, Player receiver, NetworkMarker marker) {
|
||||
PacketEvent event = PacketEvent.fromServer(this, packet, marker, receiver);
|
||||
|
||||
invoker.invokePacketSending(event);
|
||||
return event;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to inform the event listeners of a received packet.
|
||||
* @param packet - the packet that has been receieved.
|
||||
* @param sender - the client packet.
|
||||
* @param marker - the network marker.
|
||||
* @return The packet event that was used.
|
||||
*/
|
||||
private PacketEvent packetReceived(PacketContainer packet, Player sender, NetworkMarker marker) {
|
||||
PacketEvent event = PacketEvent.fromClient(this, packet, marker, sender);
|
||||
|
||||
invoker.invokePacketRecieving(event);
|
||||
return event;
|
||||
}
|
||||
|
||||
// Server side
|
||||
@Override
|
||||
public PlayerInjectionHandler getPlayerInjector() {
|
||||
return new AbstractPlayerHandler(sendingFilters) {
|
||||
private ChannelListener listener = NettyProtocolInjector.this;
|
||||
|
||||
@Override
|
||||
public int getProtocolVersion(Player player) {
|
||||
return injectionFactory.fromPlayer(player, listener).getProtocolVersion();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updatePlayer(Player player) {
|
||||
injectionFactory.fromPlayer(player, listener).inject();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectPlayer(Player player, ConflictStrategy strategy) {
|
||||
injectionFactory.fromPlayer(player, listener).inject();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean uninjectPlayer(InetSocketAddress address) {
|
||||
// Ignore this too
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addPacketHandler(PacketType type, Set<ListenerOptions> options) {
|
||||
if (options != null && !type.forceAsync() && !options.contains(ListenerOptions.ASYNC))
|
||||
mainThreadFilters.addType(type);
|
||||
super.addPacketHandler(type, options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removePacketHandler(PacketType type) {
|
||||
mainThreadFilters.removeType(type);
|
||||
super.removePacketHandler(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean uninjectPlayer(Player player) {
|
||||
// Just let Netty clean this up
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendServerPacket(Player receiver, PacketContainer packet, NetworkMarker marker, boolean filters) throws InvocationTargetException {
|
||||
injectionFactory.fromPlayer(receiver, listener).sendServerPacket(packet.getHandle(), marker, filters);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasMainThreadListener(PacketType type) {
|
||||
return mainThreadFilters.contains(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void recieveClientPacket(Player player, Object mcPacket) throws IllegalAccessException, InvocationTargetException {
|
||||
injectionFactory.fromPlayer(player, listener).recieveClientPacket(mcPacket);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketEvent handlePacketRecieved(PacketContainer packet, InputStream input, byte[] buffered) {
|
||||
// Ignore this
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleDisconnect(Player player) {
|
||||
injectionFactory.fromPlayer(player, listener).close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public WrappedChannel getChannel(Player player) {
|
||||
Injector injector = injectionFactory.fromPlayer(player, listener);
|
||||
if (injector instanceof ChannelInjector) {
|
||||
return ((ChannelInjector) injector).getChannel();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a view of this protocol injector as a packet injector.
|
||||
* @return The packet injector.
|
||||
*/
|
||||
// Client side
|
||||
@Override
|
||||
public PacketInjector getPacketInjector() {
|
||||
return new AbstractPacketInjector(reveivedFilters) {
|
||||
@Override
|
||||
public PacketEvent packetRecieved(PacketContainer packet, Player client, byte[] buffered) {
|
||||
NetworkMarker marker = buffered != null ? new NettyNetworkMarker(ConnectionSide.CLIENT_SIDE, buffered) : null;
|
||||
injectionFactory.fromPlayer(client, NettyProtocolInjector.this).saveMarker(packet.getHandle(), marker);
|
||||
return packetReceived(packet, client, marker);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void inputBuffersChanged(Set<PacketType> set) {
|
||||
bufferedPackets = new PacketTypeSet(set);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
/**
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2015 dmulloy2
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
package com.comphenix.protocol.compat.netty.independent;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.PacketType.Protocol;
|
||||
import com.comphenix.protocol.PacketType.Sender;
|
||||
import com.comphenix.protocol.injector.netty.ProtocolRegistry;
|
||||
import com.comphenix.protocol.injector.packet.MapContainer;
|
||||
import com.comphenix.protocol.reflect.StructureModifier;
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
/**
|
||||
* @author dmulloy2
|
||||
*/
|
||||
|
||||
public class NettyProtocolRegistry extends ProtocolRegistry {
|
||||
|
||||
@Override
|
||||
protected synchronized void initialize() {
|
||||
Object[] protocols = enumProtocol.getEnumConstants();
|
||||
|
||||
// ID to Packet class maps
|
||||
Map<Object, Map<Integer, Class<?>>> serverMaps = Maps.newLinkedHashMap();
|
||||
Map<Object, Map<Integer, Class<?>>> clientMaps = Maps.newLinkedHashMap();
|
||||
|
||||
Register result = new Register();
|
||||
StructureModifier<Object> modifier = null;
|
||||
|
||||
// Iterate through the protocols
|
||||
for (Object protocol : protocols) {
|
||||
if (modifier == null)
|
||||
modifier = new StructureModifier<Object>(protocol.getClass().getSuperclass(), false);
|
||||
StructureModifier<Map<Object, Map<Integer, Class<?>>>> maps = modifier.withTarget(protocol).withType(Map.class);
|
||||
for (Entry<Object, Map<Integer, Class<?>>> entry : maps.read(0).entrySet()) {
|
||||
String direction = entry.getKey().toString();
|
||||
if (direction.contains("CLIENTBOUND")) { // Sent by Server
|
||||
serverMaps.put(protocol, entry.getValue());
|
||||
} else if (direction.contains("SERVERBOUND")) { // Sent by Client
|
||||
clientMaps.put(protocol, entry.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Maps we have to occationally check have changed
|
||||
for (Map<Integer, Class<?>> map : serverMaps.values()) {
|
||||
result.containers.add(new MapContainer(map));
|
||||
}
|
||||
|
||||
for (Map<Integer, Class<?>> map : clientMaps.values()) {
|
||||
result.containers.add(new MapContainer(map));
|
||||
}
|
||||
|
||||
for (int i = 0; i < protocols.length; i++) {
|
||||
Object protocol = protocols[i];
|
||||
Enum<?> enumProtocol = (Enum<?>) protocol;
|
||||
Protocol equivalent = Protocol.fromVanilla(enumProtocol);
|
||||
|
||||
// Associate known types
|
||||
if (serverMaps.containsKey(protocol))
|
||||
associatePackets(result, serverMaps.get(protocol), equivalent, Sender.SERVER);
|
||||
if (clientMaps.containsKey(protocol))
|
||||
associatePackets(result, clientMaps.get(protocol), equivalent, Sender.CLIENT);
|
||||
}
|
||||
|
||||
// Exchange (thread safe, as we have only one writer)
|
||||
this.register = result;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void associatePackets(Register register, Map<Integer, Class<?>> lookup, Protocol protocol, Sender sender) {
|
||||
for (Entry<Integer, Class<?>> entry : lookup.entrySet()) {
|
||||
PacketType type = PacketType.fromCurrent(protocol, sender, entry.getKey(), entry.getValue());
|
||||
|
||||
try {
|
||||
register.typeToClass.put(type, entry.getValue());
|
||||
|
||||
if (sender == Sender.SERVER)
|
||||
register.serverPackets.add(type);
|
||||
if (sender == Sender.CLIENT)
|
||||
register.clientPackets.add(type);
|
||||
} catch (IllegalArgumentException ex) {
|
||||
// Sometimes this happens with fake packets, just ignore it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+265
@@ -0,0 +1,265 @@
|
||||
/**
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2015 dmulloy2
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
package com.comphenix.protocol.compat.netty.independent;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelOption;
|
||||
import io.netty.channel.socket.SocketChannel;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.InetAddress;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketAddress;
|
||||
import java.net.SocketException;
|
||||
|
||||
/**
|
||||
* This class wraps a Netty {@link Channel} in a {@link Socket}. It overrides
|
||||
* all methods in {@link Socket} to ensure that calls are not mistakingly made
|
||||
* to the unsupported super socket. All operations that can be sanely applied to
|
||||
* a {@link Channel} are implemented here. Those which cannot will throw an
|
||||
* {@link UnsupportedOperationException}.
|
||||
*/
|
||||
// Thanks MD5. :)
|
||||
public class NettySocketAdapter extends Socket {
|
||||
private final SocketChannel ch;
|
||||
|
||||
private NettySocketAdapter(SocketChannel ch) {
|
||||
this.ch = ch;
|
||||
}
|
||||
|
||||
public static NettySocketAdapter adapt(SocketChannel ch) {
|
||||
return new NettySocketAdapter(ch);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bind(SocketAddress bindpoint) throws IOException {
|
||||
ch.bind(bindpoint).syncUninterruptibly();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void close() throws IOException {
|
||||
ch.close().syncUninterruptibly();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connect(SocketAddress endpoint) throws IOException {
|
||||
ch.connect(endpoint).syncUninterruptibly();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connect(SocketAddress endpoint, int timeout) throws IOException {
|
||||
ch.config().setConnectTimeoutMillis(timeout);
|
||||
ch.connect(endpoint).syncUninterruptibly();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return obj instanceof NettySocketAdapter && ch.equals(((NettySocketAdapter) obj).ch);
|
||||
}
|
||||
|
||||
@Override
|
||||
public java.nio.channels.SocketChannel getChannel() {
|
||||
throw new UnsupportedOperationException("Operation not supported on Channel wrapper.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public InetAddress getInetAddress() {
|
||||
return ch.remoteAddress().getAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getInputStream() throws IOException {
|
||||
throw new UnsupportedOperationException("Operation not supported on Channel wrapper.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getKeepAlive() throws SocketException {
|
||||
return ch.config().getOption(ChannelOption.SO_KEEPALIVE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InetAddress getLocalAddress() {
|
||||
return ch.localAddress().getAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLocalPort() {
|
||||
return ch.localAddress().getPort();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SocketAddress getLocalSocketAddress() {
|
||||
return ch.localAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getOOBInline() throws SocketException {
|
||||
throw new UnsupportedOperationException("Operation not supported on Channel wrapper.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public OutputStream getOutputStream() throws IOException {
|
||||
throw new UnsupportedOperationException("Operation not supported on Channel wrapper.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPort() {
|
||||
return ch.remoteAddress().getPort();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized int getReceiveBufferSize() throws SocketException {
|
||||
return ch.config().getOption(ChannelOption.SO_RCVBUF);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SocketAddress getRemoteSocketAddress() {
|
||||
return ch.remoteAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getReuseAddress() throws SocketException {
|
||||
return ch.config().getOption(ChannelOption.SO_REUSEADDR);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized int getSendBufferSize() throws SocketException {
|
||||
return ch.config().getOption(ChannelOption.SO_SNDBUF);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSoLinger() throws SocketException {
|
||||
return ch.config().getOption(ChannelOption.SO_LINGER);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized int getSoTimeout() throws SocketException {
|
||||
throw new UnsupportedOperationException("Operation not supported on Channel wrapper.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getTcpNoDelay() throws SocketException {
|
||||
return ch.config().getOption(ChannelOption.TCP_NODELAY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTrafficClass() throws SocketException {
|
||||
return ch.config().getOption(ChannelOption.IP_TOS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return ch.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBound() {
|
||||
return ch.localAddress() != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isClosed() {
|
||||
return !ch.isOpen();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConnected() {
|
||||
return ch.isActive();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInputShutdown() {
|
||||
return ch.isInputShutdown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOutputShutdown() {
|
||||
return ch.isOutputShutdown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendUrgentData(int data) throws IOException {
|
||||
throw new UnsupportedOperationException("Operation not supported on Channel wrapper.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setKeepAlive(boolean on) throws SocketException {
|
||||
ch.config().setOption(ChannelOption.SO_KEEPALIVE, on);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOOBInline(boolean on) throws SocketException {
|
||||
throw new UnsupportedOperationException("Operation not supported on Channel wrapper.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPerformancePreferences(int connectionTime, int latency, int bandwidth) {
|
||||
throw new UnsupportedOperationException("Operation not supported on Channel wrapper.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void setReceiveBufferSize(int size) throws SocketException {
|
||||
ch.config().setOption(ChannelOption.SO_RCVBUF, size);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setReuseAddress(boolean on) throws SocketException {
|
||||
ch.config().setOption(ChannelOption.SO_REUSEADDR, on);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void setSendBufferSize(int size) throws SocketException {
|
||||
ch.config().setOption(ChannelOption.SO_SNDBUF, size);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSoLinger(boolean on, int linger) throws SocketException {
|
||||
ch.config().setOption(ChannelOption.SO_LINGER, linger);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void setSoTimeout(int timeout) throws SocketException {
|
||||
throw new UnsupportedOperationException("Operation not supported on Channel wrapper.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTcpNoDelay(boolean on) throws SocketException {
|
||||
ch.config().setOption(ChannelOption.TCP_NODELAY, on);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTrafficClass(int tc) throws SocketException {
|
||||
ch.config().setOption(ChannelOption.IP_TOS, tc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdownInput() throws IOException {
|
||||
throw new UnsupportedOperationException("Operation not supported on Channel wrapper.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdownOutput() throws IOException {
|
||||
ch.shutdownOutput().syncUninterruptibly();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return ch.toString();
|
||||
}
|
||||
}
|
||||
+144
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol.concurrency;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.events.ListeningWhitelist;
|
||||
import com.comphenix.protocol.injector.PrioritizedListener;
|
||||
import com.google.common.collect.Iterables;
|
||||
|
||||
/**
|
||||
* A thread-safe implementation of a listener multimap.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public abstract class AbstractConcurrentListenerMultimap<TListener> {
|
||||
// The core of our map
|
||||
private ConcurrentMap<PacketType, SortedCopyOnWriteArray<PrioritizedListener<TListener>>> mapListeners;
|
||||
|
||||
public AbstractConcurrentListenerMultimap() {
|
||||
mapListeners = new ConcurrentHashMap<PacketType, SortedCopyOnWriteArray<PrioritizedListener<TListener>>>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a listener to its requested list of packet receivers.
|
||||
* @param listener - listener with a list of packets to receive notifications for.
|
||||
* @param whitelist - the packet whitelist to use.
|
||||
*/
|
||||
public void addListener(TListener listener, ListeningWhitelist whitelist) {
|
||||
PrioritizedListener<TListener> prioritized = new PrioritizedListener<TListener>(listener, whitelist.getPriority());
|
||||
|
||||
for (PacketType type : whitelist.getTypes()) {
|
||||
addListener(type, prioritized);
|
||||
}
|
||||
}
|
||||
|
||||
// Add the listener to a specific packet notifcation list
|
||||
private void addListener(PacketType type, PrioritizedListener<TListener> listener) {
|
||||
SortedCopyOnWriteArray<PrioritizedListener<TListener>> list = mapListeners.get(type);
|
||||
|
||||
// We don't want to create this for every lookup
|
||||
if (list == null) {
|
||||
// It would be nice if we could use a PriorityBlockingQueue, but it doesn't preseve iterator order,
|
||||
// which is a essential feature for our purposes.
|
||||
final SortedCopyOnWriteArray<PrioritizedListener<TListener>> value = new SortedCopyOnWriteArray<PrioritizedListener<TListener>>();
|
||||
|
||||
// We may end up creating multiple multisets, but we'll agree on which to use
|
||||
list = mapListeners.putIfAbsent(type, value);
|
||||
|
||||
if (list == null) {
|
||||
list = value;
|
||||
}
|
||||
}
|
||||
|
||||
// Thread safe
|
||||
list.add(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the given listener from the packet event list.
|
||||
* @param listener - listener to remove.
|
||||
* @param whitelist - the packet whitelist that was used.
|
||||
* @return Every packet ID that was removed due to no listeners.
|
||||
*/
|
||||
public List<PacketType> removeListener(TListener listener, ListeningWhitelist whitelist) {
|
||||
List<PacketType> removedPackets = new ArrayList<PacketType>();
|
||||
|
||||
// Again, not terribly efficient. But adding or removing listeners should be a rare event.
|
||||
for (PacketType type : whitelist.getTypes()) {
|
||||
SortedCopyOnWriteArray<PrioritizedListener<TListener>> list = mapListeners.get(type);
|
||||
|
||||
// Remove any listeners
|
||||
if (list != null) {
|
||||
// Don't remove from newly created lists
|
||||
if (list.size() > 0) {
|
||||
// Remove this listener. Note that priority is generally ignored.
|
||||
list.remove(new PrioritizedListener<TListener>(listener, whitelist.getPriority()));
|
||||
|
||||
if (list.size() == 0) {
|
||||
mapListeners.remove(type);
|
||||
removedPackets.add(type);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Move on to the next
|
||||
}
|
||||
return removedPackets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the registered listeners, in order from the lowest to the highest priority.
|
||||
* <p>
|
||||
* The returned list is thread-safe and doesn't require synchronization.
|
||||
* @param type - packet type.
|
||||
* @return Registered listeners.
|
||||
*/
|
||||
public Collection<PrioritizedListener<TListener>> getListener(PacketType type) {
|
||||
return mapListeners.get(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve every listener.
|
||||
* @return Every listener.
|
||||
*/
|
||||
public Iterable<PrioritizedListener<TListener>> values() {
|
||||
return Iterables.concat(mapListeners.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve every registered packet type:
|
||||
* @return Registered packet type.
|
||||
*/
|
||||
public Set<PacketType> keySet() {
|
||||
return mapListeners.keySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all packet listeners.
|
||||
*/
|
||||
protected void clearListeners() {
|
||||
mapListeners.clear();
|
||||
}
|
||||
}
|
||||
+495
@@ -0,0 +1,495 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol.concurrency;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.NavigableMap;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import com.comphenix.protocol.compat.guava.Guava;
|
||||
import com.google.common.base.Objects;
|
||||
import com.google.common.collect.Range;
|
||||
|
||||
/**
|
||||
* Represents a generic store of intervals and associated values. No two intervals
|
||||
* can overlap in this representation.
|
||||
* <p>
|
||||
* Note that this implementation is not thread safe.
|
||||
*
|
||||
* @author Kristian
|
||||
*
|
||||
* @param <TKey> - type of the key. Must implement Comparable.
|
||||
* @param <TValue> - type of the value to associate.
|
||||
*/
|
||||
public abstract class AbstractIntervalTree<TKey extends Comparable<TKey>, TValue> {
|
||||
|
||||
protected enum State {
|
||||
OPEN,
|
||||
CLOSE,
|
||||
BOTH
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a range and a value in this interval tree.
|
||||
*/
|
||||
public class Entry implements Map.Entry<Range<TKey>, TValue> {
|
||||
private EndPoint left;
|
||||
private EndPoint right;
|
||||
|
||||
Entry(EndPoint left, EndPoint right) {
|
||||
if (left == null)
|
||||
throw new IllegalAccessError("left cannot be NUll");
|
||||
if (right == null)
|
||||
throw new IllegalAccessError("right cannot be NUll");
|
||||
if (left.key.compareTo(right.key) > 0)
|
||||
throw new IllegalArgumentException(
|
||||
"Left key (" + left.key + ") cannot be greater than the right key (" + right.key + ")");
|
||||
|
||||
this.left = left;
|
||||
this.right = right;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Range<TKey> getKey() {
|
||||
return Guava.closedRange(left.key, right.key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TValue getValue() {
|
||||
return left.value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TValue setValue(TValue value) {
|
||||
TValue old = left.value;
|
||||
|
||||
// Set both end points
|
||||
left.value = value;
|
||||
right.value = value;
|
||||
return old;
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
// Quick equality check
|
||||
if (obj == this) {
|
||||
return true;
|
||||
} else if (obj instanceof AbstractIntervalTree.Entry) {
|
||||
return Objects.equal(left.key, ((AbstractIntervalTree.Entry) obj).left.key) &&
|
||||
Objects.equal(right.key, ((AbstractIntervalTree.Entry) obj).right.key) &&
|
||||
Objects.equal(left.value, ((AbstractIntervalTree.Entry) obj).left.value);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hashCode(left.key, right.key, left.value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("Value %s at [%s, %s]", left.value, left.key, right.key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a single end point (open, close or both) of a range.
|
||||
*/
|
||||
protected class EndPoint {
|
||||
|
||||
// Whether or not the end-point is opening a range, closing a range or both.
|
||||
public State state;
|
||||
|
||||
// The value this range contains
|
||||
public TValue value;
|
||||
|
||||
// The key of this end point
|
||||
public TKey key;
|
||||
|
||||
public EndPoint(State state, TKey key, TValue value) {
|
||||
this.state = state;
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
// To quickly look up ranges we'll index them by endpoints
|
||||
protected NavigableMap<TKey, EndPoint> bounds = new TreeMap<TKey, EndPoint>();
|
||||
|
||||
/**
|
||||
* Removes every interval that intersects with the given range.
|
||||
* @param lowerBound - lowest value to remove.
|
||||
* @param upperBound - highest value to remove.
|
||||
* @return Intervals that were removed
|
||||
*/
|
||||
public Set<Entry> remove(TKey lowerBound, TKey upperBound) {
|
||||
return remove(lowerBound, upperBound, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes every interval that intersects with the given range.
|
||||
* @param lowerBound - lowest value to remove.
|
||||
* @param upperBound - highest value to remove.
|
||||
* @param preserveDifference - whether or not to preserve the intervals that are partially outside.
|
||||
* @return Intervals that were removed
|
||||
*/
|
||||
public Set<Entry> remove(TKey lowerBound, TKey upperBound, boolean preserveDifference) {
|
||||
checkBounds(lowerBound, upperBound);
|
||||
NavigableMap<TKey, EndPoint> range = bounds.subMap(lowerBound, true, upperBound, true);
|
||||
|
||||
EndPoint first = getNextEndPoint(lowerBound, true);
|
||||
EndPoint last = getPreviousEndPoint(upperBound, true);
|
||||
|
||||
// Used while resizing intervals
|
||||
EndPoint previous = null;
|
||||
EndPoint next = null;
|
||||
|
||||
Set<Entry> resized = new HashSet<Entry>();
|
||||
Set<Entry> removed = new HashSet<Entry>();
|
||||
|
||||
// Remove the previous element too. A close end-point must be preceded by an OPEN end-point.
|
||||
if (first != null && first.state == State.CLOSE) {
|
||||
previous = getPreviousEndPoint(first.key, false);
|
||||
|
||||
// Add the interval back
|
||||
if (previous != null) {
|
||||
removed.add(getEntry(previous, first));
|
||||
}
|
||||
}
|
||||
|
||||
// Get the closing element too.
|
||||
if (last != null && last.state == State.OPEN) {
|
||||
next = getNextEndPoint(last.key, false);
|
||||
|
||||
if (next != null) {
|
||||
removed.add(getEntry(last, next));
|
||||
}
|
||||
}
|
||||
|
||||
// Now remove both ranges
|
||||
removeEntrySafely(previous, first);
|
||||
removeEntrySafely(last, next);
|
||||
|
||||
// Add new resized intervals
|
||||
if (preserveDifference) {
|
||||
if (previous != null) {
|
||||
resized.add(putUnsafe(previous.key, decrementKey(lowerBound), previous.value));
|
||||
}
|
||||
if (next != null) {
|
||||
resized.add(putUnsafe(incrementKey(upperBound), next.key, next.value));
|
||||
}
|
||||
}
|
||||
|
||||
// Get the removed entries too
|
||||
getEntries(removed, range);
|
||||
invokeEntryRemoved(removed);
|
||||
|
||||
if (preserveDifference) {
|
||||
invokeEntryAdded(resized);
|
||||
}
|
||||
|
||||
// Remove the range as well
|
||||
range.clear();
|
||||
return removed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the entry from a given set of end points.
|
||||
* @param left - leftmost end point.
|
||||
* @param right - rightmost end point.
|
||||
* @return The associated entry.
|
||||
*/
|
||||
protected Entry getEntry(EndPoint left, EndPoint right) {
|
||||
if (left == null)
|
||||
throw new IllegalArgumentException("left endpoint cannot be NULL.");
|
||||
if (right == null)
|
||||
throw new IllegalArgumentException("right endpoint cannot be NULL.");
|
||||
|
||||
// Make sure the order is correct
|
||||
if (right.key.compareTo(left.key) < 0) {
|
||||
return getEntry(right, left);
|
||||
} else {
|
||||
return new Entry(left, right);
|
||||
}
|
||||
}
|
||||
|
||||
private void removeEntrySafely(EndPoint left, EndPoint right) {
|
||||
if (left != null && right != null) {
|
||||
bounds.remove(left.key);
|
||||
bounds.remove(right.key);
|
||||
}
|
||||
}
|
||||
|
||||
// Adds a given end point
|
||||
protected EndPoint addEndPoint(TKey key, TValue value, State state) {
|
||||
EndPoint endPoint = bounds.get(key);
|
||||
|
||||
if (endPoint != null) {
|
||||
endPoint.state = State.BOTH;
|
||||
} else {
|
||||
endPoint = new EndPoint(state, key, value);
|
||||
bounds.put(key, endPoint);
|
||||
}
|
||||
return endPoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Associates a given interval of keys with a certain value. Any previous
|
||||
* association will be overwritten in the given interval.
|
||||
* <p>
|
||||
* Overlapping intervals are not permitted. A key can only be associated with a single value.
|
||||
*
|
||||
* @param lowerBound - the minimum key (inclusive).
|
||||
* @param upperBound - the maximum key (inclusive).
|
||||
* @param value - the value, or NULL to reset this range.
|
||||
*/
|
||||
public void put(TKey lowerBound, TKey upperBound, TValue value) {
|
||||
// While we don't permit overlapping intervals, we'll still allow overwriting existing intervals.
|
||||
remove(lowerBound, upperBound, true);
|
||||
invokeEntryAdded(putUnsafe(lowerBound, upperBound, value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Associates a given interval without performing any interval checks.
|
||||
* @param lowerBound - the minimum key (inclusive).
|
||||
* @param upperBound - the maximum key (inclusive).
|
||||
* @param value - the value, or NULL to reset the range.
|
||||
*/
|
||||
private Entry putUnsafe(TKey lowerBound, TKey upperBound, TValue value) {
|
||||
// OK. Add the end points now
|
||||
if (value != null) {
|
||||
EndPoint left = addEndPoint(lowerBound, value, State.OPEN);
|
||||
EndPoint right = addEndPoint(upperBound, value, State.CLOSE);
|
||||
|
||||
return new Entry(left, right);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to verify the validity of the given interval.
|
||||
* @param lowerBound - lower bound (inclusive).
|
||||
* @param upperBound - upper bound (inclusive).
|
||||
*/
|
||||
private void checkBounds(TKey lowerBound, TKey upperBound) {
|
||||
if (lowerBound == null)
|
||||
throw new IllegalAccessError("lowerbound cannot be NULL.");
|
||||
if (upperBound == null)
|
||||
throw new IllegalAccessError("upperBound cannot be NULL.");
|
||||
if (upperBound.compareTo(lowerBound) < 0)
|
||||
throw new IllegalArgumentException("upperBound cannot be less than lowerBound.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the given key is within an interval.
|
||||
* @param key - key to check.
|
||||
* @return TRUE if the given key is within an interval in this tree, FALSE otherwise.
|
||||
*/
|
||||
public boolean containsKey(TKey key) {
|
||||
return getEndPoint(key) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enumerates over every range in this interval tree.
|
||||
* @return Number of ranges.
|
||||
*/
|
||||
public Set<Entry> entrySet() {
|
||||
// Don't mind the Java noise
|
||||
Set<Entry> result = new HashSet<Entry>();
|
||||
getEntries(result, bounds);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove every interval.
|
||||
*/
|
||||
public void clear() {
|
||||
if (!bounds.isEmpty()) {
|
||||
remove(bounds.firstKey(), bounds.lastKey());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a map of end points into a set of entries.
|
||||
* @param destination - set of entries.
|
||||
* @param map - a map of end points.
|
||||
*/
|
||||
private void getEntries(Set<Entry> destination, NavigableMap<TKey, EndPoint> map) {
|
||||
Map.Entry<TKey, EndPoint> last = null;
|
||||
|
||||
for (Map.Entry<TKey, EndPoint> entry : map.entrySet()) {
|
||||
switch (entry.getValue().state) {
|
||||
case BOTH:
|
||||
EndPoint point = entry.getValue();
|
||||
destination.add(new Entry(point, point));
|
||||
break;
|
||||
case CLOSE:
|
||||
if (last != null) {
|
||||
destination.add(new Entry(last.getValue(), entry.getValue()));
|
||||
}
|
||||
break;
|
||||
case OPEN:
|
||||
// We don't know the full range yet
|
||||
last = entry;
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Illegal open/close state detected.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts every range from the given tree into the current tree.
|
||||
* @param other - the other tree to read from.
|
||||
*/
|
||||
public void putAll(AbstractIntervalTree<TKey, TValue> other) {
|
||||
// Naively copy every range.
|
||||
for (Entry entry : other.entrySet()) {
|
||||
put(entry.left.key, entry.right.key, entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the value of the range that matches the given key, or NULL if nothing was found.
|
||||
* @param key - the level to read for.
|
||||
* @return The correct amount of experience, or NULL if nothing was recorded.
|
||||
*/
|
||||
public TValue get(TKey key) {
|
||||
EndPoint point = getEndPoint(key);
|
||||
|
||||
if (point != null)
|
||||
return point.value;
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the left-most end-point associated with this key.
|
||||
* @param key - key to search for.
|
||||
* @return The end point found, or NULL.
|
||||
*/
|
||||
protected EndPoint getEndPoint(TKey key) {
|
||||
EndPoint ends = bounds.get(key);
|
||||
|
||||
if (ends != null) {
|
||||
// Always return the end point to the left
|
||||
if (ends.state == State.CLOSE) {
|
||||
Map.Entry<TKey, EndPoint> left = bounds.floorEntry(decrementKey(key));
|
||||
return left != null ? left.getValue() : null;
|
||||
|
||||
} else {
|
||||
return ends;
|
||||
}
|
||||
|
||||
} else {
|
||||
// We need to determine if the point intersects with a range
|
||||
Map.Entry<TKey, EndPoint> left = bounds.floorEntry(key);
|
||||
|
||||
// We only need to check to the left
|
||||
if (left != null && left.getValue().state == State.OPEN) {
|
||||
return left.getValue();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the previous end point of a given key.
|
||||
* @param point - the point to search with.
|
||||
* @param inclusive - whether or not to include the current point in the search.
|
||||
* @return The previous end point of a given given key, or NULL if not found.
|
||||
*/
|
||||
protected EndPoint getPreviousEndPoint(TKey point, boolean inclusive) {
|
||||
if (point != null) {
|
||||
Map.Entry<TKey, EndPoint> previous = bounds.floorEntry(inclusive ? point : decrementKey(point));
|
||||
|
||||
if (previous != null)
|
||||
return previous.getValue();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the next end point of a given key.
|
||||
* @param point - the point to search with.
|
||||
* @param inclusive - whether or not to include the current point in the search.
|
||||
* @return The next end point of a given given key, or NULL if not found.
|
||||
*/
|
||||
protected EndPoint getNextEndPoint(TKey point, boolean inclusive) {
|
||||
if (point != null) {
|
||||
Map.Entry<TKey, EndPoint> next = bounds.ceilingEntry(inclusive ? point : incrementKey(point));
|
||||
|
||||
if (next != null)
|
||||
return next.getValue();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void invokeEntryAdded(Entry added) {
|
||||
if (added != null) {
|
||||
onEntryAdded(added);
|
||||
}
|
||||
}
|
||||
|
||||
private void invokeEntryAdded(Set<Entry> added) {
|
||||
for (Entry entry : added) {
|
||||
onEntryAdded(entry);
|
||||
}
|
||||
}
|
||||
|
||||
private void invokeEntryRemoved(Set<Entry> removed) {
|
||||
for (Entry entry : removed) {
|
||||
onEntryRemoved(entry);
|
||||
}
|
||||
}
|
||||
|
||||
// Listeners for added or removed entries
|
||||
/**
|
||||
* Invoked when an entry is added.
|
||||
* @param added - the entry that was added.
|
||||
*/
|
||||
protected void onEntryAdded(Entry added) { }
|
||||
|
||||
/**
|
||||
* Invoked when an entry is removed.
|
||||
* @param removed - the removed entry.
|
||||
*/
|
||||
protected void onEntryRemoved(Entry removed) { }
|
||||
|
||||
// Helpers for decrementing or incrementing key values
|
||||
/**
|
||||
* Decrement the given key by one unit.
|
||||
* @param key - the key that should be decremented.
|
||||
* @return The new decremented key.
|
||||
*/
|
||||
protected abstract TKey decrementKey(TKey key);
|
||||
|
||||
/**
|
||||
* Increment the given key by one unit.
|
||||
* @param key - the key that should be incremented.
|
||||
* @return The new incremented key.
|
||||
*/
|
||||
protected abstract TKey incrementKey(TKey key);
|
||||
}
|
||||
@@ -0,0 +1,266 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol.concurrency;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import com.comphenix.protocol.utility.SafeCacheBuilder;
|
||||
import com.google.common.cache.CacheLoader;
|
||||
import com.google.common.cache.RemovalCause;
|
||||
import com.google.common.cache.RemovalListener;
|
||||
import com.google.common.cache.RemovalNotification;
|
||||
|
||||
/**
|
||||
* A map that supports blocking on read operations. Null keys are not supported.
|
||||
* <p>
|
||||
* Values are stored as weak references, and will be automatically removed once they've all been dereferenced.
|
||||
* <p>
|
||||
* @author Kristian
|
||||
*
|
||||
* @param <TKey> - type of the key.
|
||||
* @param <TValue> - type of the value.
|
||||
*/
|
||||
public class BlockingHashMap<TKey, TValue> {
|
||||
// Map of values
|
||||
private final ConcurrentMap<TKey, TValue> backingMap;
|
||||
|
||||
// Map of locked objects
|
||||
private final ConcurrentMap<TKey, Object> locks;
|
||||
|
||||
/**
|
||||
* Retrieve a cache loader that will always throw an exception.
|
||||
* @param <TKey> Type of the key
|
||||
* @param <TValue> Type of the value
|
||||
* @return An invalid cache loader.
|
||||
*/
|
||||
public static <TKey, TValue> CacheLoader<TKey, TValue> newInvalidCacheLoader() {
|
||||
return new CacheLoader<TKey, TValue>() {
|
||||
@Override
|
||||
public TValue load(TKey key) throws Exception {
|
||||
throw new IllegalStateException("Illegal use. Access the map directly instead.");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a new map.
|
||||
*/
|
||||
public BlockingHashMap() {
|
||||
backingMap = SafeCacheBuilder.<TKey, TValue>newBuilder().
|
||||
weakValues().
|
||||
removalListener(
|
||||
new RemovalListener<TKey, TValue>() {
|
||||
@Override
|
||||
public void onRemoval(RemovalNotification<TKey, TValue> entry) {
|
||||
// Clean up locks too
|
||||
if (entry.getCause() != RemovalCause.REPLACED) {
|
||||
locks.remove(entry.getKey());
|
||||
}
|
||||
}
|
||||
}).
|
||||
build(BlockingHashMap.<TKey, TValue> newInvalidCacheLoader());
|
||||
|
||||
// Normal concurrent hash map
|
||||
locks = new ConcurrentHashMap<TKey, Object>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a new map.
|
||||
* @param <TKey> Type of the key
|
||||
* @param <TValue> Type of the value
|
||||
* @return The created map.
|
||||
*/
|
||||
public static <TKey, TValue> BlockingHashMap<TKey, TValue> create() {
|
||||
return new BlockingHashMap<TKey, TValue>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits until a value has been associated with the given key, and then retrieves that value.
|
||||
* @param key - the key whose associated value is to be returned
|
||||
* @return The value to which the specified key is mapped.
|
||||
* @throws InterruptedException If the current thread got interrupted while waiting.
|
||||
*/
|
||||
public TValue get(TKey key) throws InterruptedException {
|
||||
if (key == null)
|
||||
throw new IllegalArgumentException("key cannot be NULL.");
|
||||
|
||||
TValue value = backingMap.get(key);
|
||||
|
||||
// Only lock if no value is available
|
||||
if (value == null) {
|
||||
final Object lock = getLock(key);
|
||||
|
||||
synchronized (lock) {
|
||||
while (value == null) {
|
||||
lock.wait();
|
||||
value = backingMap.get(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits until a value has been associated with the given key, and then retrieves that value.
|
||||
* @param key - the key whose associated value is to be returned
|
||||
* @param timeout - the amount of time to wait until an association has been made.
|
||||
* @param unit - unit of timeout.
|
||||
* @return The value to which the specified key is mapped, or NULL if the timeout elapsed.
|
||||
* @throws InterruptedException If the current thread got interrupted while waiting.
|
||||
*/
|
||||
public TValue get(TKey key, long timeout, TimeUnit unit) throws InterruptedException {
|
||||
return get(key, timeout, unit, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits until a value has been associated with the given key, and then retrieves that value.
|
||||
* <p>
|
||||
* If timeout is zero, this method will return immediately if it can't find an socket injector.
|
||||
*
|
||||
* @param key - the key whose associated value is to be returned
|
||||
* @param timeout - the amount of time to wait until an association has been made.
|
||||
* @param unit - unit of timeout.
|
||||
* @param ignoreInterrupted - TRUE if we should ignore the thread being interrupted, FALSE otherwise.
|
||||
* @return The value to which the specified key is mapped, or NULL if the timeout elapsed.
|
||||
* @throws InterruptedException If the current thread got interrupted while waiting.
|
||||
*/
|
||||
public TValue get(TKey key, long timeout, TimeUnit unit, boolean ignoreInterrupted) throws InterruptedException {
|
||||
if (key == null)
|
||||
throw new IllegalArgumentException("key cannot be NULL.");
|
||||
if (unit == null)
|
||||
throw new IllegalArgumentException("Unit cannot be NULL.");
|
||||
if (timeout < 0)
|
||||
throw new IllegalArgumentException("Timeout cannot be less than zero.");
|
||||
|
||||
TValue value = backingMap.get(key);
|
||||
|
||||
// Only lock if no value is available
|
||||
if (value == null && timeout > 0) {
|
||||
final Object lock = getLock(key);
|
||||
final long stopTimeNS = System.nanoTime() + unit.toNanos(timeout);
|
||||
|
||||
// Don't exceed the timeout
|
||||
synchronized (lock) {
|
||||
while (value == null) {
|
||||
try {
|
||||
long remainingTime = stopTimeNS - System.nanoTime();
|
||||
|
||||
if (remainingTime > 0) {
|
||||
TimeUnit.NANOSECONDS.timedWait(lock, remainingTime);
|
||||
value = backingMap.get(key);
|
||||
} else {
|
||||
// Timeout elapsed
|
||||
break;
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
// This is fairly dangerous - but we might HAVE to block the thread
|
||||
if (!ignoreInterrupted)
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Associate a given key with the given value.
|
||||
* <p>
|
||||
* Wakes up any blocking getters on this specific key.
|
||||
*
|
||||
* @param key - the key to associate.
|
||||
* @param value - the value.
|
||||
* @return The previously associated value.
|
||||
*/
|
||||
public TValue put(TKey key, TValue value) {
|
||||
if (value == null)
|
||||
throw new IllegalArgumentException("This map doesn't support NULL values.");
|
||||
|
||||
final TValue previous = backingMap.put(key, value);
|
||||
final Object lock = getLock(key);
|
||||
|
||||
// Inform our readers about this change
|
||||
synchronized (lock) {
|
||||
lock.notifyAll();
|
||||
return previous;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If and only if a key is not present in the map will it be associated with the given value.
|
||||
* @param key - the key to associate.
|
||||
* @param value - the value to associate.
|
||||
* @return The previous value this key has been associated with.
|
||||
*/
|
||||
public TValue putIfAbsent(TKey key, TValue value) {
|
||||
if (value == null)
|
||||
throw new IllegalArgumentException("This map doesn't support NULL values.");
|
||||
|
||||
final TValue previous = backingMap.putIfAbsent(key, value);
|
||||
|
||||
// No need to unlock readers if we haven't changed anything
|
||||
if (previous == null) {
|
||||
final Object lock = getLock(key);
|
||||
|
||||
synchronized (lock) {
|
||||
lock.notifyAll();
|
||||
}
|
||||
}
|
||||
return previous;
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return backingMap.size();
|
||||
}
|
||||
|
||||
public Collection<TValue> values() {
|
||||
return backingMap.values();
|
||||
}
|
||||
|
||||
public Set<TKey> keys() {
|
||||
return backingMap.keySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Atomically retrieve the lock associated with a given key.
|
||||
* @param key - the current key.
|
||||
* @return An asssociated lock.
|
||||
*/
|
||||
private Object getLock(TKey key) {
|
||||
Object lock = locks.get(key);
|
||||
|
||||
if (lock == null) {
|
||||
Object created = new Object();
|
||||
|
||||
// Do this atomically
|
||||
lock = locks.putIfAbsent(key, created);
|
||||
|
||||
// If we succeeded, use the latch we created - otherwise, use the already inserted latch
|
||||
if (lock == null) {
|
||||
lock = created;
|
||||
}
|
||||
}
|
||||
|
||||
return lock;
|
||||
}
|
||||
}
|
||||
+332
@@ -0,0 +1,332 @@
|
||||
package com.comphenix.protocol.concurrency;
|
||||
|
||||
import java.util.AbstractMap;
|
||||
import java.util.AbstractSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import com.comphenix.protocol.utility.SafeCacheBuilder;
|
||||
import com.comphenix.protocol.utility.Util;
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.cache.CacheLoader;
|
||||
import com.google.common.cache.RemovalListener;
|
||||
import com.google.common.cache.RemovalNotification;
|
||||
import com.google.common.collect.AbstractIterator;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.util.concurrent.UncheckedExecutionException;
|
||||
|
||||
/**
|
||||
* Represents a concurrent player map.
|
||||
* <p>
|
||||
* This map may use player addresses as keys.
|
||||
* @author Kristian
|
||||
*/
|
||||
public class ConcurrentPlayerMap<TValue> extends AbstractMap<Player, TValue> implements ConcurrentMap<Player, TValue> {
|
||||
/**
|
||||
* Represents the different standard player keys,
|
||||
* @author Kristian
|
||||
*/
|
||||
public enum PlayerKey implements Function<Player, Object> {
|
||||
/**
|
||||
* Use a player's {@link Player#getAddress()} as key in the map.
|
||||
*/
|
||||
ADDRESS {
|
||||
@Override
|
||||
public Object apply(Player player) {
|
||||
return player.getAddress();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Use a player's {@link Player#getName()} as key in the map.
|
||||
*/
|
||||
NAME {
|
||||
@Override
|
||||
public Object apply(Player player) {
|
||||
return player.getName();
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* An internal map of player keys to values.
|
||||
*/
|
||||
protected ConcurrentMap<Object, TValue> valueLookup = createValueMap();
|
||||
|
||||
/**
|
||||
* A cache of the associated keys for each player.
|
||||
*/
|
||||
|
||||
protected ConcurrentMap<Object, Player> keyLookup = createKeyCache();
|
||||
/**
|
||||
* The method used to retrieve a unique key for a player.
|
||||
*/
|
||||
protected final Function<Player, Object> keyMethod;
|
||||
|
||||
/**
|
||||
* Construct a new concurrent player map that uses each player's address as key.
|
||||
* @param <T> Parameter type
|
||||
* @return Concurrent player map.
|
||||
*/
|
||||
public static <T> ConcurrentPlayerMap<T> usingAddress() {
|
||||
return new ConcurrentPlayerMap<T>(PlayerKey.ADDRESS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new concurrent player map that uses each player's name as key.
|
||||
* @param <T> Parameter type
|
||||
* @return Concurrent player map.
|
||||
*/
|
||||
public static <T> ConcurrentPlayerMap<T> usingName() {
|
||||
return new ConcurrentPlayerMap<T>(PlayerKey.NAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new concurrent player map using the given standard key method.
|
||||
* @param standardMethod - the standard key method.
|
||||
*/
|
||||
public ConcurrentPlayerMap(PlayerKey standardMethod) {
|
||||
this.keyMethod = standardMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new concurrent player map using the given custom key method.
|
||||
* @param method - custom key method.
|
||||
*/
|
||||
public ConcurrentPlayerMap(Function<Player, Object> method) {
|
||||
this.keyMethod = method;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct the map that will store the associated values.
|
||||
* <p>
|
||||
* The default implementation uses a {@link ConcurrentHashMap}.
|
||||
* @return The value map.
|
||||
*/
|
||||
protected ConcurrentMap<Object, TValue> createValueMap() {
|
||||
return Maps.newConcurrentMap();
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a cache of keys and the associated player.
|
||||
* @return The key map.
|
||||
*/
|
||||
protected ConcurrentMap<Object, Player> createKeyCache() {
|
||||
return SafeCacheBuilder.newBuilder().
|
||||
weakValues().
|
||||
removalListener(
|
||||
new RemovalListener<Object, Player>() {
|
||||
@Override
|
||||
public void onRemoval(RemovalNotification<Object, Player> removed) {
|
||||
// We ignore explicit removal
|
||||
if (removed.wasEvicted()) {
|
||||
onCacheEvicted(removed.getKey());
|
||||
}
|
||||
}
|
||||
}).
|
||||
build(
|
||||
new CacheLoader<Object, Player>() {
|
||||
@Override
|
||||
public Player load(Object key) throws Exception {
|
||||
Player player = findOnlinePlayer(key);
|
||||
|
||||
if (player != null)
|
||||
return player;
|
||||
|
||||
// Per the contract, this method should not return NULL
|
||||
throw new IllegalArgumentException(
|
||||
"Unable to find a player associated with: " + key);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when an entry in the cache has been evicted, typically by the garbage collector.
|
||||
* @param key - the key.
|
||||
* @param player - the value that was evicted or collected.
|
||||
*/
|
||||
private void onCacheEvicted(Object key) {
|
||||
Player newPlayer = findOnlinePlayer(key);
|
||||
|
||||
if (newPlayer != null) {
|
||||
// Update the reference
|
||||
keyLookup.put(key, newPlayer);
|
||||
} else {
|
||||
valueLookup.remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find an online player from the given key.
|
||||
* @param key - a non-null key.
|
||||
* @return The player with the given key, or NULL if not found.
|
||||
*/
|
||||
protected Player findOnlinePlayer(Object key) {
|
||||
for (Player player : Util.getOnlinePlayers()) {
|
||||
if (key.equals(keyMethod.apply(player))) {
|
||||
return player;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup a player by key in the cache, optionally searching every online player.
|
||||
* @param key - the key of the player we are locating.
|
||||
* @return The player, or NULL if not found.
|
||||
*/
|
||||
protected Player lookupPlayer(Object key) {
|
||||
try {
|
||||
return keyLookup.get(key);
|
||||
} catch (UncheckedExecutionException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the key of a particular player, ensuring it is cached.
|
||||
* @param player - the player whose key we want to retrieve.
|
||||
* @return The key.
|
||||
*/
|
||||
protected Object cachePlayerKey(Player player) {
|
||||
Object key = keyMethod.apply(player);
|
||||
|
||||
keyLookup.put(key, player);
|
||||
return key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TValue put(Player key, TValue value) {
|
||||
return valueLookup.put(cachePlayerKey(key), value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TValue putIfAbsent(Player key, TValue value) {
|
||||
return valueLookup.putIfAbsent(cachePlayerKey(key), value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TValue replace(Player key, TValue value) {
|
||||
return valueLookup.replace(cachePlayerKey(key), value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean replace(Player key, TValue oldValue, TValue newValue) {
|
||||
return valueLookup.replace(cachePlayerKey(key), oldValue, newValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TValue remove(Object key) {
|
||||
if (key instanceof Player) {
|
||||
Object playerKey = keyMethod.apply((Player) key);
|
||||
|
||||
if (playerKey != null) {
|
||||
TValue value = valueLookup.remove(playerKey);
|
||||
|
||||
keyLookup.remove(playerKey);
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(Object key, Object value) {
|
||||
if (key instanceof Player) {
|
||||
Object playerKey = keyMethod.apply((Player) key);
|
||||
|
||||
if (playerKey != null && valueLookup.remove(playerKey, value)) {
|
||||
keyLookup.remove(playerKey);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TValue get(Object key) {
|
||||
if (key instanceof Player) {
|
||||
Object playerKey = keyMethod.apply((Player) key);
|
||||
return playerKey != null ? valueLookup.get(playerKey) : null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsKey(Object key) {
|
||||
if (key instanceof Player) {
|
||||
Object playerKey = keyMethod.apply((Player) key);
|
||||
return playerKey != null && valueLookup.containsKey(playerKey);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Entry<Player, TValue>> entrySet() {
|
||||
return new AbstractSet<Entry<Player,TValue>>() {
|
||||
@Override
|
||||
public Iterator<Entry<Player, TValue>> iterator() {
|
||||
return entryIterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return valueLookup.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
valueLookup.clear();
|
||||
keyLookup.clear();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve an iterator of entries that supports removal of elements.
|
||||
* @return Entry iterator.
|
||||
*/
|
||||
private Iterator<Entry<Player, TValue>> entryIterator() {
|
||||
// Skip entries with stale data
|
||||
final Iterator<Entry<Object, TValue>> source = valueLookup.entrySet().iterator();
|
||||
final AbstractIterator<Entry<Player,TValue>> filtered =
|
||||
new AbstractIterator<Entry<Player,TValue>>() {
|
||||
@Override
|
||||
protected Entry<Player, TValue> computeNext() {
|
||||
while (source.hasNext()) {
|
||||
Entry<Object, TValue> entry = source.next();
|
||||
Player player = lookupPlayer(entry.getKey());
|
||||
|
||||
if (player == null) {
|
||||
// Remove entries that cannot be found
|
||||
source.remove();
|
||||
keyLookup.remove(entry.getKey());
|
||||
} else {
|
||||
return new SimpleEntry<Player, TValue>(player, entry.getValue());
|
||||
}
|
||||
}
|
||||
return endOfData();
|
||||
}
|
||||
};
|
||||
|
||||
// We can't return AbstractIterator directly, as it doesn't permitt the remove() method
|
||||
return new Iterator<Entry<Player, TValue>>() {
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return filtered.hasNext();
|
||||
}
|
||||
@Override
|
||||
public Entry<Player, TValue> next() {
|
||||
return filtered.next();
|
||||
}
|
||||
@Override
|
||||
public void remove() {
|
||||
source.remove();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol.concurrency;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Represents a very quick integer set that uses a lookup table to store membership.
|
||||
* <p>
|
||||
* This class is intentionally missing a size method.
|
||||
* @author Kristian
|
||||
*/
|
||||
public class IntegerSet {
|
||||
private final boolean[] array;
|
||||
|
||||
/**
|
||||
* Initialize a lookup table with the given maximum number of elements.
|
||||
* <p>
|
||||
* This creates a set for elements in the range [0, count).
|
||||
* <p>
|
||||
* Formally, the current set must be a subset of [0, 1, 2, ..., count - 1].
|
||||
* @param maximumCount - maximum element value and count.
|
||||
*/
|
||||
public IntegerSet(int maximumCount) {
|
||||
this.array = new boolean[maximumCount];
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a lookup table with a given maximum and value list.
|
||||
* <p>
|
||||
* The provided elements must be in the range [0, count).
|
||||
* @param maximumCount - the maximum element value and count.
|
||||
* @param values - the elements to add to the set.
|
||||
*/
|
||||
public IntegerSet(int maximumCount, Collection<Integer> values) {
|
||||
this.array = new boolean[maximumCount];
|
||||
addAll(values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether or not the given element exists in the set.
|
||||
* @param element - the element to check. Must be in the range [0, count).
|
||||
* @return TRUE if the given element exists, FALSE otherwise.
|
||||
*/
|
||||
public boolean contains(int element) {
|
||||
return array[element];
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the given element to the set, or do nothing if it already exists.
|
||||
* @param element - element to add.
|
||||
* @throws ArrayIndexOutOfBoundsException If the given element is not in the range [0, count).
|
||||
*/
|
||||
public void add(int element) {
|
||||
array[element] = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the given collection of elements to the set.
|
||||
* @param packets - elements to add.
|
||||
*/
|
||||
public void addAll(Collection<Integer> packets) {
|
||||
for (Integer id : packets) {
|
||||
add(id);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the given element from the set, or do nothing if it's already removed.
|
||||
* @param element - element to remove.
|
||||
*/
|
||||
public void remove(int element) {
|
||||
// We don't actually care if the caller tries to remove an element outside the valid set
|
||||
if (element >= 0 && element < array.length)
|
||||
array[element] = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove every element from the set.
|
||||
*/
|
||||
public void clear() {
|
||||
Arrays.fill(array, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the current IntegerSet to an equivalent HashSet.
|
||||
* @return The resulting HashSet.
|
||||
*/
|
||||
public Set<Integer> toSet() {
|
||||
Set<Integer> elements = new HashSet<Integer>();
|
||||
|
||||
for (int i = 0; i < array.length; i++) {
|
||||
if (array[i])
|
||||
elements.add(i);
|
||||
}
|
||||
return elements;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
package com.comphenix.protocol.concurrency;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.injector.packet.PacketRegistry;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
/**
|
||||
* Represents a concurrent set of packet types.
|
||||
* @author Kristian
|
||||
*/
|
||||
public class PacketTypeSet {
|
||||
private Set<PacketType> types = Collections.newSetFromMap(Maps.<PacketType, Boolean>newConcurrentMap());
|
||||
private Set<Class<?>> classes = Collections.newSetFromMap(Maps.<Class<?>, Boolean>newConcurrentMap());
|
||||
|
||||
public PacketTypeSet() {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
public PacketTypeSet(Collection<? extends PacketType> values) {
|
||||
for (PacketType type : values) {
|
||||
addType(type);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a particular type to the set.
|
||||
* @param type - the type to add.
|
||||
*/
|
||||
public synchronized void addType(PacketType type) {
|
||||
Class<?> packetClass = getPacketClass(type);
|
||||
types.add(Preconditions.checkNotNull(type, "type cannot be NULL."));
|
||||
|
||||
if (packetClass != null) {
|
||||
classes.add(getPacketClass(type));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the given types to the set of packet types.
|
||||
* @param types - the types to add.
|
||||
*/
|
||||
public synchronized void addAll(Iterable<? extends PacketType> types) {
|
||||
for (PacketType type : types) {
|
||||
addType(type);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a particular type to the set.
|
||||
* @param type - the type to remove.
|
||||
*/
|
||||
public synchronized void removeType(PacketType type) {
|
||||
Class<?> packetClass = getPacketClass(type);
|
||||
types.remove(Preconditions.checkNotNull(type, "type cannot be NULL."));
|
||||
|
||||
if (packetClass != null) {
|
||||
classes.remove(getPacketClass(type));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the given types from the set.
|
||||
* @param types Types to remove
|
||||
*/
|
||||
public synchronized void removeAll(Iterable<? extends PacketType> types) {
|
||||
for (PacketType type : types) {
|
||||
removeType(type);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the packet class associated with a particular type.
|
||||
* @param type - the packet type.
|
||||
* @return The associated packet type.
|
||||
*/
|
||||
protected Class<?> getPacketClass(PacketType type) {
|
||||
return PacketRegistry.getPacketClassFromType(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the given packet type exists in the set.
|
||||
* @param type - the type to find.
|
||||
* @return TRUE if it does, FALSE otherwise.
|
||||
*/
|
||||
public boolean contains(PacketType type) {
|
||||
return types.contains(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a packet type with the given packet class exists in the set.
|
||||
* @param packetClass - the class to find.
|
||||
* @return TRUE if it does, FALSE otherwise.
|
||||
*/
|
||||
public boolean contains(Class<?> packetClass) {
|
||||
return classes.contains(packetClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the type of a packet is in the current set.
|
||||
* @param packet - the packet.
|
||||
* @return TRUE if it is, FALSE otherwise.
|
||||
*/
|
||||
public boolean containsPacket(Object packet) {
|
||||
if (packet == null)
|
||||
return false;
|
||||
return classes.contains(packet.getClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a view of this packet type set.
|
||||
* @return The packet type values.
|
||||
*/
|
||||
public Set<PacketType> values() {
|
||||
return types;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the number of entries in the set.
|
||||
* @return The number of entries.
|
||||
*/
|
||||
public int size() {
|
||||
return types.size();
|
||||
}
|
||||
|
||||
public synchronized void clear() {
|
||||
types.clear();
|
||||
classes.clear();
|
||||
}
|
||||
}
|
||||
+244
@@ -0,0 +1,244 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol.concurrency;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import com.google.common.base.Objects;
|
||||
import com.google.common.collect.Iterables;
|
||||
|
||||
/**
|
||||
* An implicitly sorted array list that preserves insertion order and maintains duplicates.
|
||||
* @param <T> - type of the elements in the list.
|
||||
*/
|
||||
public class SortedCopyOnWriteArray<T extends Comparable<T>> implements Collection<T> {
|
||||
// Prevent reordering
|
||||
private volatile List<T> list;
|
||||
|
||||
/**
|
||||
* Construct an empty sorted array.
|
||||
*/
|
||||
public SortedCopyOnWriteArray() {
|
||||
list = new ArrayList<T>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a sorted array from the given list. The elements will be automatically sorted.
|
||||
* @param wrapped - the collection whose elements are to be placed into the list.
|
||||
*/
|
||||
public SortedCopyOnWriteArray(Collection<T> wrapped) {
|
||||
this.list = new ArrayList<T>(wrapped);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a sorted array from the given list.
|
||||
* @param wrapped - the collection whose elements are to be placed into the list.
|
||||
* @param sort - TRUE to automatically sort the collection, FALSE if it is already sorted.
|
||||
*/
|
||||
public SortedCopyOnWriteArray(Collection<T> wrapped, boolean sort) {
|
||||
this.list = new ArrayList<T>(wrapped);
|
||||
|
||||
if (sort) {
|
||||
Collections.sort(list);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts the given element in the proper location.
|
||||
* @param value - element to insert.
|
||||
*/
|
||||
@Override
|
||||
public synchronized boolean add(T value) {
|
||||
// We use NULL as a special marker, so we don't allow it
|
||||
if (value == null)
|
||||
throw new IllegalArgumentException("value cannot be NULL");
|
||||
|
||||
List<T> copy = new ArrayList<T>();
|
||||
|
||||
for (T element : list) {
|
||||
// If the value is now greater than the current element, it should be placed right before it
|
||||
if (value != null && value.compareTo(element) < 0) {
|
||||
copy.add(value);
|
||||
value = null;
|
||||
}
|
||||
copy.add(element);
|
||||
}
|
||||
|
||||
// Don't forget to add it
|
||||
if (value != null)
|
||||
copy.add(value);
|
||||
|
||||
list = copy;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized boolean addAll(Collection<? extends T> values) {
|
||||
if (values == null)
|
||||
throw new IllegalArgumentException("values cannot be NULL");
|
||||
if (values.size() == 0)
|
||||
return false;
|
||||
|
||||
List<T> copy = new ArrayList<T>();
|
||||
|
||||
// Insert the new content and sort it
|
||||
copy.addAll(list);
|
||||
copy.addAll(values);
|
||||
Collections.sort(copy);
|
||||
|
||||
list = copy;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes from the list by making a new list with every element except the one given.
|
||||
* <p>
|
||||
* Objects will be compared using the given objects equals() method.
|
||||
* @param value - value to remove.
|
||||
*/
|
||||
@Override
|
||||
public synchronized boolean remove(Object value) {
|
||||
List<T> copy = new ArrayList<T>();
|
||||
boolean result = false;
|
||||
|
||||
// Note that there's not much to be gained from using BinarySearch, as we
|
||||
// have to copy (and thus read) the entire list regardless.
|
||||
|
||||
// Copy every element except the one given to us.
|
||||
for (T element : list) {
|
||||
if (!Objects.equal(value, element)) {
|
||||
copy.add(element);
|
||||
} else {
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
|
||||
list = copy;
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeAll(Collection<?> values) {
|
||||
// Special cases
|
||||
if (values == null)
|
||||
throw new IllegalArgumentException("values cannot be NULL");
|
||||
if (values.size() == 0)
|
||||
return false;
|
||||
|
||||
List<T> copy = new ArrayList<T>();
|
||||
|
||||
copy.addAll(list);
|
||||
copy.removeAll(values);
|
||||
|
||||
list = copy;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean retainAll(Collection<?> values) {
|
||||
// Special cases
|
||||
if (values == null)
|
||||
throw new IllegalArgumentException("values cannot be NULL");
|
||||
if (values.size() == 0)
|
||||
return false;
|
||||
|
||||
List<T> copy = new ArrayList<T>();
|
||||
|
||||
copy.addAll(list);
|
||||
copy.removeAll(values);
|
||||
|
||||
list = copy;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes from the list by making a copy of every element except the one with the given index.
|
||||
* @param index - index of the element to remove.
|
||||
*/
|
||||
public synchronized void remove(int index) {
|
||||
List<T> copy = new ArrayList<T>(list);
|
||||
|
||||
copy.remove(index);
|
||||
list = copy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves an element by index.
|
||||
* @param index - index of element to retrieve.
|
||||
* @return The element at the given location.
|
||||
*/
|
||||
public T get(int index) {
|
||||
return list.get(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the size of the list.
|
||||
* @return Size of the list.
|
||||
*/
|
||||
public int size() {
|
||||
return list.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves an iterator over the elements in the given list.
|
||||
* Warning: No not attempt to remove elements using the iterator.
|
||||
*/
|
||||
public Iterator<T> iterator() {
|
||||
return Iterables.unmodifiableIterable(list).iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
list = new ArrayList<T>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(Object value) {
|
||||
return list.contains(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsAll(Collection<?> values) {
|
||||
return list.containsAll(values);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return list.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] toArray() {
|
||||
return list.toArray();
|
||||
}
|
||||
|
||||
@SuppressWarnings("hiding")
|
||||
@Override
|
||||
public <T> T[] toArray(T[] a) {
|
||||
return list.toArray(a);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return list.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
package com.comphenix.protocol.error;
|
||||
|
||||
import java.io.PrintStream;
|
||||
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import com.comphenix.protocol.error.Report.ReportBuilder;
|
||||
import com.comphenix.protocol.reflect.PrettyPrinter;
|
||||
|
||||
/**
|
||||
* Represents a basic error reporter that prints error reports to the standard error stream.
|
||||
* <p>
|
||||
* Note that this implementation doesn't distinguish between {@link #reportWarning(Object, Report)}
|
||||
* and {@link #reportDetailed(Object, Report)} - they both have the exact same behavior.
|
||||
* @author Kristian
|
||||
*/
|
||||
public class BasicErrorReporter implements ErrorReporter {
|
||||
private final PrintStream output;
|
||||
|
||||
/**
|
||||
* Construct a new basic error reporter that prints directly the standard error stream.
|
||||
*/
|
||||
public BasicErrorReporter() {
|
||||
this(System.err);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a error reporter that prints to the given output stream.
|
||||
* @param output - the output stream.
|
||||
*/
|
||||
public BasicErrorReporter(PrintStream output) {
|
||||
this.output = output;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportMinimal(Plugin sender, String methodName, Throwable error) {
|
||||
output.println("Unhandled exception occured in " + methodName + " for " + sender.getName());
|
||||
error.printStackTrace(output);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportMinimal(Plugin sender, String methodName, Throwable error, Object... parameters) {
|
||||
reportMinimal(sender, methodName, error);
|
||||
|
||||
// Also print parameters
|
||||
printParameters(parameters);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportDebug(Object sender, Report report) {
|
||||
// We just have to swallow it
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportDebug(Object sender, ReportBuilder builder) {
|
||||
// As above
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportWarning(Object sender, Report report) {
|
||||
// Basic warning
|
||||
output.println("[" + sender.getClass().getSimpleName() + "] " + report.getReportMessage());
|
||||
|
||||
if (report.getException() != null) {
|
||||
report.getException().printStackTrace(output);
|
||||
}
|
||||
printParameters(report.getCallerParameters());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportWarning(Object sender, ReportBuilder reportBuilder) {
|
||||
reportWarning(sender, reportBuilder.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportDetailed(Object sender, Report report) {
|
||||
// No difference from warning
|
||||
reportWarning(sender, report);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportDetailed(Object sender, ReportBuilder reportBuilder) {
|
||||
reportWarning(sender, reportBuilder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Print the given parameters to the standard error stream.
|
||||
* @param parameters - the output parameters.
|
||||
*/
|
||||
private void printParameters(Object[] parameters) {
|
||||
if (parameters != null && parameters.length > 0) {
|
||||
output.println("Parameters: ");
|
||||
|
||||
try {
|
||||
for (Object parameter : parameters) {
|
||||
if (parameter == null)
|
||||
output.println("[NULL]");
|
||||
else
|
||||
output.println(PrettyPrinter.printObject(parameter));
|
||||
}
|
||||
} catch (IllegalAccessException e) {
|
||||
// Damn it
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package com.comphenix.protocol.error;
|
||||
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import com.comphenix.protocol.error.Report.ReportBuilder;
|
||||
|
||||
/**
|
||||
* Construct an error reporter that delegates to another error reporter.
|
||||
* @author Kristian
|
||||
*/
|
||||
public class DelegatedErrorReporter implements ErrorReporter {
|
||||
private final ErrorReporter delegated;
|
||||
|
||||
/**
|
||||
* Construct a new error reporter that forwards all reports to a given reporter.
|
||||
* @param delegated - the delegated reporter.
|
||||
*/
|
||||
public DelegatedErrorReporter(ErrorReporter delegated) {
|
||||
this.delegated = delegated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the underlying error reporter.
|
||||
* @return Underlying error reporter.
|
||||
*/
|
||||
public ErrorReporter getDelegated() {
|
||||
return delegated;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportMinimal(Plugin sender, String methodName, Throwable error) {
|
||||
delegated.reportMinimal(sender, methodName, error);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportMinimal(Plugin sender, String methodName, Throwable error, Object... parameters) {
|
||||
delegated.reportMinimal(sender, methodName, error, parameters);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportDebug(Object sender, Report report) {
|
||||
Report transformed = filterReport(sender, report, false);
|
||||
|
||||
if (transformed != null) {
|
||||
delegated.reportDebug(sender, transformed);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportWarning(Object sender, Report report) {
|
||||
Report transformed = filterReport(sender, report, false);
|
||||
|
||||
if (transformed != null) {
|
||||
delegated.reportWarning(sender, transformed);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportDetailed(Object sender, Report report) {
|
||||
Report transformed = filterReport(sender, report, true);
|
||||
|
||||
if (transformed != null) {
|
||||
delegated.reportDetailed(sender, transformed);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked before an error report is passed on to the underlying error reporter.
|
||||
* <p>
|
||||
* To cancel a report, return NULL.
|
||||
* @param sender - the sender instance or class.
|
||||
* @param report - the error report.
|
||||
* @param detailed - whether or not the report will be displayed in detail.
|
||||
* @return The report to pass on, or NULL to cancel it.
|
||||
*/
|
||||
protected Report filterReport(Object sender, Report report, boolean detailed) {
|
||||
return report;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportWarning(Object sender, ReportBuilder reportBuilder) {
|
||||
reportWarning(sender, reportBuilder.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportDetailed(Object sender, ReportBuilder reportBuilder) {
|
||||
reportDetailed(sender, reportBuilder.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportDebug(Object sender, ReportBuilder builder) {
|
||||
reportDebug(sender, builder.build());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,616 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol.error;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.apache.commons.lang.builder.ToStringBuilder;
|
||||
import org.apache.commons.lang.builder.ToStringStyle;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import com.comphenix.protocol.ProtocolLibrary;
|
||||
import com.comphenix.protocol.collections.ExpireHashMap;
|
||||
import com.comphenix.protocol.error.Report.ReportBuilder;
|
||||
import com.comphenix.protocol.events.PacketAdapter;
|
||||
import com.comphenix.protocol.reflect.PrettyPrinter;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.primitives.Primitives;
|
||||
|
||||
/**
|
||||
* Internal class used to handle exceptions.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public class DetailedErrorReporter implements ErrorReporter {
|
||||
/**
|
||||
* Report format for printing the current exception count.
|
||||
*/
|
||||
public static final ReportType REPORT_EXCEPTION_COUNT = new ReportType("Internal exception count: %s!");
|
||||
|
||||
public static final String SECOND_LEVEL_PREFIX = " ";
|
||||
public static final String DEFAULT_PREFIX = " ";
|
||||
public static final String DEFAULT_SUPPORT_URL = "https://github.com/dmulloy2/ProtocolLib/issues";
|
||||
|
||||
// Users that are informed about errors in the chat
|
||||
public static final String ERROR_PERMISSION = "protocol.info";
|
||||
|
||||
// We don't want to spam the server
|
||||
public static final int DEFAULT_MAX_ERROR_COUNT = 20;
|
||||
|
||||
// Prevent spam per plugin too
|
||||
private ConcurrentMap<String, AtomicInteger> warningCount = new ConcurrentHashMap<String, AtomicInteger>();
|
||||
|
||||
protected String prefix;
|
||||
protected String supportURL;
|
||||
|
||||
protected AtomicInteger internalErrorCount = new AtomicInteger();
|
||||
|
||||
protected int maxErrorCount;
|
||||
protected Logger logger;
|
||||
|
||||
protected WeakReference<Plugin> pluginReference;
|
||||
protected String pluginName;
|
||||
|
||||
// Whether or not Apache Commons is not present
|
||||
protected static boolean apacheCommonsMissing;
|
||||
|
||||
// Whether or not detailed errror reporting is enabled
|
||||
protected boolean detailedReporting;
|
||||
|
||||
// Map of global objects
|
||||
protected Map<String, Object> globalParameters = new HashMap<String, Object>();
|
||||
|
||||
// Reports to ignore
|
||||
private ExpireHashMap<Report, Boolean> rateLimited = new ExpireHashMap<Report, Boolean>();
|
||||
private Object rateLock = new Object();
|
||||
|
||||
/**
|
||||
* Create a default error reporting system.
|
||||
* @param plugin - the plugin owner.
|
||||
*/
|
||||
public DetailedErrorReporter(Plugin plugin) {
|
||||
this(plugin, DEFAULT_PREFIX, DEFAULT_SUPPORT_URL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a central error reporting system.
|
||||
* @param plugin - the plugin owner.
|
||||
* @param prefix - default line prefix.
|
||||
* @param supportURL - URL to report the error.
|
||||
*/
|
||||
public DetailedErrorReporter(Plugin plugin, String prefix, String supportURL) {
|
||||
this(plugin, prefix, supportURL, DEFAULT_MAX_ERROR_COUNT, getBukkitLogger());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a central error reporting system.
|
||||
* @param plugin - the plugin owner.
|
||||
* @param prefix - default line prefix.
|
||||
* @param supportURL - URL to report the error.
|
||||
* @param maxErrorCount - number of errors to print before giving up.
|
||||
* @param logger - current logger.
|
||||
*/
|
||||
public DetailedErrorReporter(Plugin plugin, String prefix, String supportURL, int maxErrorCount, Logger logger) {
|
||||
if (plugin == null)
|
||||
throw new IllegalArgumentException("Plugin cannot be NULL.");
|
||||
|
||||
this.pluginReference = new WeakReference<Plugin>(plugin);
|
||||
this.pluginName = getNameSafely(plugin);
|
||||
this.prefix = prefix;
|
||||
this.supportURL = supportURL;
|
||||
this.maxErrorCount = maxErrorCount;
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
private String getNameSafely(Plugin plugin) {
|
||||
try {
|
||||
return plugin.getName();
|
||||
} catch (LinkageError e) {
|
||||
return "ProtocolLib";
|
||||
}
|
||||
}
|
||||
|
||||
// Attempt to get the logger.
|
||||
private static Logger getBukkitLogger() {
|
||||
try {
|
||||
return Bukkit.getLogger();
|
||||
} catch (LinkageError e) {
|
||||
return Logger.getLogger("Minecraft");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if we're using detailed error reporting.
|
||||
* @return TRUE if we are, FALSE otherwise.
|
||||
*/
|
||||
public boolean isDetailedReporting() {
|
||||
return detailedReporting;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether or not to use detailed error reporting.
|
||||
* @param detailedReporting - TRUE to enable it, FALSE otherwise.
|
||||
*/
|
||||
public void setDetailedReporting(boolean detailedReporting) {
|
||||
this.detailedReporting = detailedReporting;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportMinimal(Plugin sender, String methodName, Throwable error, Object... parameters) {
|
||||
if (reportMinimalNoSpam(sender, methodName, error)) {
|
||||
// Print parameters, if they are given
|
||||
if (parameters != null && parameters.length > 0) {
|
||||
logger.log(Level.SEVERE, printParameters(parameters));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportMinimal(Plugin sender, String methodName, Throwable error) {
|
||||
reportMinimalNoSpam(sender, methodName, error);
|
||||
}
|
||||
|
||||
/**
|
||||
* Report a problem with a given method and plugin, ensuring that we don't exceed the maximum number of error reports.
|
||||
* @param sender - the component that observed this exception.
|
||||
* @param methodName - the method name.
|
||||
* @param error - the error itself.
|
||||
* @return TRUE if the error was printed, FALSE if it was suppressed.
|
||||
*/
|
||||
public boolean reportMinimalNoSpam(Plugin sender, String methodName, Throwable error) {
|
||||
String pluginName = PacketAdapter.getPluginName(sender);
|
||||
AtomicInteger counter = warningCount.get(pluginName);
|
||||
|
||||
// Thread safe pattern
|
||||
if (counter == null) {
|
||||
AtomicInteger created = new AtomicInteger();
|
||||
counter = warningCount.putIfAbsent(pluginName, created);
|
||||
|
||||
if (counter == null) {
|
||||
counter = created;
|
||||
}
|
||||
}
|
||||
|
||||
final int errorCount = counter.incrementAndGet();
|
||||
|
||||
// See if we should print the full error
|
||||
if (errorCount < getMaxErrorCount()) {
|
||||
logger.log(Level.SEVERE, "[" + pluginName + "] Unhandled exception occured in " +
|
||||
methodName + " for " + pluginName, error);
|
||||
return true;
|
||||
|
||||
} else {
|
||||
// Nope - only print the error count occationally
|
||||
if (isPowerOfTwo(errorCount)) {
|
||||
logger.log(Level.SEVERE, "[" + pluginName + "] Unhandled exception number " + errorCount + " occured in " +
|
||||
methodName + " for " + pluginName, error);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a given number is a power of two.
|
||||
* <p>
|
||||
* That is, if there exists an N such that 2^N = number.
|
||||
* @param number - the number to check.
|
||||
* @return TRUE if the given number is a power of two, FALSE otherwise.
|
||||
*/
|
||||
private boolean isPowerOfTwo(int number) {
|
||||
return (number & (number - 1)) == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportDebug(Object sender, ReportBuilder builder) {
|
||||
reportDebug(sender, Preconditions.checkNotNull(builder, "builder cannot be NULL").build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportDebug(Object sender, Report report) {
|
||||
if (logger.isLoggable(Level.FINE) && canReport(report)) {
|
||||
reportLevel(Level.FINE, sender, report);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportWarning(Object sender, ReportBuilder reportBuilder) {
|
||||
if (reportBuilder == null)
|
||||
throw new IllegalArgumentException("reportBuilder cannot be NULL.");
|
||||
|
||||
reportWarning(sender, reportBuilder.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportWarning(Object sender, Report report) {
|
||||
if (logger.isLoggable(Level.WARNING) && canReport(report)) {
|
||||
reportLevel(Level.WARNING, sender, report);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if we should print the given report.
|
||||
* <p>
|
||||
* The default implementation will check for rate limits.
|
||||
* @param report - the report to check.
|
||||
* @return TRUE if we should print it, FALSE otherwise.
|
||||
*/
|
||||
protected boolean canReport(Report report) {
|
||||
long rateLimit = report.getRateLimit();
|
||||
|
||||
// Check for rate limit
|
||||
if (rateLimit > 0) {
|
||||
synchronized (rateLock) {
|
||||
if (rateLimited.containsKey(report)) {
|
||||
return false;
|
||||
}
|
||||
rateLimited.put(report, true, rateLimit, TimeUnit.NANOSECONDS);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void reportLevel(Level level, Object sender, Report report) {
|
||||
String message = "[" + pluginName + "] [" + getSenderName(sender) + "] " + report.getReportMessage();
|
||||
|
||||
// Print the main warning
|
||||
if (report.getException() != null) {
|
||||
logger.log(level, message, report.getException());
|
||||
} else {
|
||||
logger.log(level, message);
|
||||
|
||||
// Remember the call stack
|
||||
if (detailedReporting) {
|
||||
printCallStack(level, logger);
|
||||
}
|
||||
}
|
||||
|
||||
// Parameters?
|
||||
if (report.hasCallerParameters()) {
|
||||
// Write it
|
||||
logger.log(level, printParameters(report.getCallerParameters()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the name of a sender class.
|
||||
* @param sender - sender object.
|
||||
* @return The name of the sender's class.
|
||||
*/
|
||||
private String getSenderName(Object sender) {
|
||||
if (sender != null)
|
||||
return ReportType.getSenderClass(sender).getSimpleName();
|
||||
else
|
||||
return "NULL";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportDetailed(Object sender, ReportBuilder reportBuilder) {
|
||||
reportDetailed(sender, reportBuilder.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportDetailed(Object sender, Report report) {
|
||||
final Plugin plugin = pluginReference.get();
|
||||
final int errorCount = internalErrorCount.incrementAndGet();
|
||||
|
||||
// Do not overtly spam the server!
|
||||
if (errorCount > getMaxErrorCount()) {
|
||||
// Only allow the error count at rare occations
|
||||
if (isPowerOfTwo(errorCount)) {
|
||||
// Permit it - but print the number of exceptions first
|
||||
reportWarning(this, Report.newBuilder(REPORT_EXCEPTION_COUNT).messageParam(errorCount).build());
|
||||
} else {
|
||||
// NEVER SPAM THE CONSOLE
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Secondary rate limit
|
||||
if (!canReport(report)) {
|
||||
return;
|
||||
}
|
||||
|
||||
StringWriter text = new StringWriter();
|
||||
PrintWriter writer = new PrintWriter(text);
|
||||
|
||||
// Helpful message
|
||||
writer.println("[" + pluginName + "] INTERNAL ERROR: " + report.getReportMessage());
|
||||
writer.println("If this problem hasn't already been reported, please open a ticket");
|
||||
writer.println("at " + supportURL + " with the following data:");
|
||||
|
||||
// Now, let us print important exception information
|
||||
writer.println("Stack Trace:");
|
||||
|
||||
if (report.getException() != null) {
|
||||
report.getException().printStackTrace(writer);
|
||||
|
||||
} else if (detailedReporting) {
|
||||
printCallStack(writer);
|
||||
}
|
||||
|
||||
// Data dump!
|
||||
writer.println("Dump:");
|
||||
|
||||
// Relevant parameters
|
||||
if (report.hasCallerParameters()) {
|
||||
printParameters(writer, report.getCallerParameters());
|
||||
}
|
||||
|
||||
// Global parameters
|
||||
for (String param : globalParameters()) {
|
||||
writer.println(SECOND_LEVEL_PREFIX + param + ":");
|
||||
writer.println(addPrefix(getStringDescription(getGlobalParameter(param)),
|
||||
SECOND_LEVEL_PREFIX + SECOND_LEVEL_PREFIX));
|
||||
}
|
||||
|
||||
// Now, for the sender itself
|
||||
writer.println("Sender:");
|
||||
writer.println(addPrefix(getStringDescription(sender), SECOND_LEVEL_PREFIX));
|
||||
|
||||
// And plugin
|
||||
if (plugin != null) {
|
||||
writer.println("Version:");
|
||||
writer.println(addPrefix(plugin.toString(), SECOND_LEVEL_PREFIX));
|
||||
}
|
||||
|
||||
// And java version
|
||||
writer.println("Java Version:");
|
||||
writer.println(addPrefix(System.getProperty("java.version"), SECOND_LEVEL_PREFIX));
|
||||
|
||||
// Add the server version too
|
||||
if (Bukkit.getServer() != null) {
|
||||
writer.println("Server:");
|
||||
writer.println(addPrefix(Bukkit.getServer().getVersion(), SECOND_LEVEL_PREFIX));
|
||||
|
||||
// Inform of this occurrence
|
||||
if (ERROR_PERMISSION != null) {
|
||||
Bukkit.getServer().broadcast(
|
||||
String.format("Error %s (%s) occured in %s.", report.getReportMessage(), report.getException(), sender),
|
||||
ERROR_PERMISSION
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure it is reported
|
||||
logger.severe(addPrefix(text.toString(), prefix));
|
||||
}
|
||||
|
||||
/**
|
||||
* Print the call stack to the given logger.
|
||||
* @param logger - the logger.
|
||||
*/
|
||||
private void printCallStack(Level level, Logger logger) {
|
||||
StringWriter text = new StringWriter();
|
||||
printCallStack(new PrintWriter(text));
|
||||
|
||||
// Print the exception
|
||||
logger.log(level, text.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Print the current call stack.
|
||||
* @param writer - the writer.
|
||||
*/
|
||||
private void printCallStack(PrintWriter writer) {
|
||||
Exception current = new Exception("Not an error! This is the call stack.");
|
||||
current.printStackTrace(writer);
|
||||
}
|
||||
|
||||
private String printParameters(Object... parameters) {
|
||||
StringWriter writer = new StringWriter();
|
||||
|
||||
// Print and retrieve the string buffer
|
||||
printParameters(new PrintWriter(writer), parameters);
|
||||
return writer.toString();
|
||||
}
|
||||
|
||||
private void printParameters(PrintWriter writer, Object[] parameters) {
|
||||
writer.println("Parameters: ");
|
||||
|
||||
// We *really* want to get as much information as possible
|
||||
for (Object param : parameters) {
|
||||
writer.println(addPrefix(getStringDescription(param), SECOND_LEVEL_PREFIX));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given prefix to every line in the text.
|
||||
* @param text - text to modify.
|
||||
* @param prefix - prefix added to every line in the text.
|
||||
* @return The modified text.
|
||||
*/
|
||||
protected String addPrefix(String text, String prefix) {
|
||||
return text.replaceAll("(?m)^", prefix);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a string representation of the given object.
|
||||
* @param value - object to convert.
|
||||
* @return String representation.
|
||||
*/
|
||||
public static String getStringDescription(Object value) {
|
||||
// We can't only rely on toString.
|
||||
if (value == null) {
|
||||
return "[NULL]";
|
||||
} if (isSimpleType(value) || value instanceof Class<?>) {
|
||||
return value.toString();
|
||||
} else {
|
||||
try {
|
||||
if (!apacheCommonsMissing)
|
||||
return (ToStringBuilder.reflectionToString(value, ToStringStyle.MULTI_LINE_STYLE, false, null));
|
||||
} catch (LinkageError ex) {
|
||||
// Apache is probably missing
|
||||
apacheCommonsMissing = true;
|
||||
} catch (Exception e) {
|
||||
// Don't use the error logger to log errors in error logging (that could lead to infinite loops)
|
||||
ProtocolLibrary.log(Level.WARNING, "Cannot convert to a String with Apache: " + e.getMessage());
|
||||
}
|
||||
|
||||
// Use our custom object printer instead
|
||||
try {
|
||||
return PrettyPrinter.printObject(value, value.getClass(), Object.class);
|
||||
} catch (IllegalAccessException e) {
|
||||
return "[Error: " + e.getMessage() + "]";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the given object is a wrapper for a primitive/simple type or not.
|
||||
* @param test - the object to test.
|
||||
* @return TRUE if this object is simple enough to simply be printed, FALSE othewise.
|
||||
*/
|
||||
protected static boolean isSimpleType(Object test) {
|
||||
return test instanceof String || Primitives.isWrapperType(test.getClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the current number of errors printed through {@link #reportDetailed(Object, Report)}.
|
||||
* @return Number of errors printed.
|
||||
*/
|
||||
public int getErrorCount() {
|
||||
return internalErrorCount.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the number of errors printed.
|
||||
* @param errorCount - new number of errors printed.
|
||||
*/
|
||||
public void setErrorCount(int errorCount) {
|
||||
internalErrorCount.set(errorCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the maximum number of errors we can print before we begin suppressing errors.
|
||||
* @return Maximum number of errors.
|
||||
*/
|
||||
public int getMaxErrorCount() {
|
||||
return maxErrorCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the maximum number of errors we can print before we begin suppressing errors.
|
||||
* @param maxErrorCount - new max count.
|
||||
*/
|
||||
public void setMaxErrorCount(int maxErrorCount) {
|
||||
this.maxErrorCount = maxErrorCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given global parameter. It will be included in every error report.
|
||||
* <p>
|
||||
* Both key and value must be non-null.
|
||||
* @param key - name of parameter.
|
||||
* @param value - the global parameter itself.
|
||||
*/
|
||||
public void addGlobalParameter(String key, Object value) {
|
||||
if (key == null)
|
||||
throw new IllegalArgumentException("key cannot be NULL.");
|
||||
if (value == null)
|
||||
throw new IllegalArgumentException("value cannot be NULL.");
|
||||
|
||||
globalParameters.put(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a global parameter by its key.
|
||||
* @param key - key of the parameter to retrieve.
|
||||
* @return The value of the global parameter, or NULL if not found.
|
||||
*/
|
||||
public Object getGlobalParameter(String key) {
|
||||
if (key == null)
|
||||
throw new IllegalArgumentException("key cannot be NULL.");
|
||||
|
||||
return globalParameters.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset all global parameters.
|
||||
*/
|
||||
public void clearGlobalParameters() {
|
||||
globalParameters.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a set of every registered global parameter.
|
||||
* @return Set of all registered global parameters.
|
||||
*/
|
||||
public Set<String> globalParameters() {
|
||||
return globalParameters.keySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the support URL that will be added to all detailed reports.
|
||||
* @return Support URL.
|
||||
*/
|
||||
public String getSupportURL() {
|
||||
return supportURL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the support URL that will be added to all detailed reports.
|
||||
* @param supportURL - the new support URL.
|
||||
*/
|
||||
public void setSupportURL(String supportURL) {
|
||||
this.supportURL = supportURL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the prefix to apply to every line in the error reports.
|
||||
* @return Error report prefix.
|
||||
*/
|
||||
public String getPrefix() {
|
||||
return prefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the prefix to apply to every line in the error reports.
|
||||
* @param prefix - new prefix.
|
||||
*/
|
||||
public void setPrefix(String prefix) {
|
||||
this.prefix = prefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the current logger that is used to print all reports.
|
||||
* @return The current logger.
|
||||
*/
|
||||
public Logger getLogger() {
|
||||
return logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current logger that is used to print all reports.
|
||||
* @param logger - new logger.
|
||||
*/
|
||||
public void setLogger(Logger logger) {
|
||||
this.logger = logger;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol.error;
|
||||
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import com.comphenix.protocol.error.Report.ReportBuilder;
|
||||
|
||||
/**
|
||||
* Represents an object that can forward an error {@link Report} to the display and permanent storage.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public interface ErrorReporter {
|
||||
/**
|
||||
* Prints a small minimal error report regarding an exception from another plugin.
|
||||
* @param sender - the other plugin.
|
||||
* @param methodName - name of the caller method.
|
||||
* @param error - the exception itself.
|
||||
*/
|
||||
public abstract void reportMinimal(Plugin sender, String methodName, Throwable error);
|
||||
|
||||
/**
|
||||
* Prints a small minimal error report regarding an exception from another plugin.
|
||||
* @param sender - the other plugin.
|
||||
* @param methodName - name of the caller method.
|
||||
* @param error - the exception itself.
|
||||
* @param parameters - any relevant parameters to print.
|
||||
*/
|
||||
public abstract void reportMinimal(Plugin sender, String methodName, Throwable error, Object... parameters);
|
||||
|
||||
/**
|
||||
* Prints a debug message from the current sender.
|
||||
* <p>
|
||||
* Most users will not see this message.
|
||||
* @param sender - the sender.
|
||||
* @param report - the report.
|
||||
*/
|
||||
public abstract void reportDebug(Object sender, Report report);
|
||||
|
||||
/**
|
||||
* Prints a debug message from the current sender.
|
||||
* @param sender - the sender.
|
||||
* @param builder - the report builder.
|
||||
*/
|
||||
public abstract void reportDebug(Object sender, ReportBuilder builder);
|
||||
|
||||
/**
|
||||
* Prints a warning message from the current plugin.
|
||||
* @param sender - the object containing the caller method.
|
||||
* @param report - an error report to include.
|
||||
*/
|
||||
public abstract void reportWarning(Object sender, Report report);
|
||||
|
||||
/**
|
||||
* Prints a warning message from the current plugin.
|
||||
* @param sender - the object containing the caller method.
|
||||
* @param reportBuilder - an error report builder that will be used to get the report.
|
||||
*/
|
||||
public abstract void reportWarning(Object sender, ReportBuilder reportBuilder);
|
||||
|
||||
/**
|
||||
* Prints a detailed error report about an unhandled exception.
|
||||
* @param sender - the object containing the caller method.
|
||||
* @param report - an error report to include.
|
||||
*/
|
||||
public abstract void reportDetailed(Object sender, Report report);
|
||||
|
||||
/**
|
||||
* Prints a detailed error report about an unhandled exception.
|
||||
* @param sender - the object containing the caller method.
|
||||
* @param reportBuilder - an error report builder that will be used to get the report.
|
||||
*/
|
||||
public abstract void reportDetailed(Object sender, ReportBuilder reportBuilder);
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
package com.comphenix.protocol.error;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URLDecoder;
|
||||
import java.security.CodeSource;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
|
||||
public final class PluginContext {
|
||||
// Determine plugin folder
|
||||
private static File pluginFolder;
|
||||
|
||||
private PluginContext() {
|
||||
// Not constructable
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the name of the plugin that called the last method(s) in the exception.
|
||||
* @param ex - the exception.
|
||||
* @return The name of the plugin, or NULL.
|
||||
*/
|
||||
public static String getPluginCaller(Exception ex) {
|
||||
StackTraceElement[] elements = ex.getStackTrace();
|
||||
String current = getPluginName(elements[0]);
|
||||
|
||||
for (int i = 1; i < elements.length; i++) {
|
||||
String caller = getPluginName(elements[i]);
|
||||
if (caller != null && !caller.equals(current)) {
|
||||
return caller;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup the plugin that this method invocation belongs to, and return its file name.
|
||||
* @param element - the method invocation.
|
||||
* @return Plugin name, or NULL if not found.
|
||||
*/
|
||||
public static String getPluginName(StackTraceElement element) {
|
||||
try {
|
||||
if (Bukkit.getServer() == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
CodeSource codeSource = Class.forName(element.getClassName()).getProtectionDomain().getCodeSource();
|
||||
if (codeSource != null) {
|
||||
String encoding = codeSource.getLocation().getPath();
|
||||
File path = new File(URLDecoder.decode(encoding, "UTF-8"));
|
||||
File plugins = getPluginFolder();
|
||||
|
||||
if (plugins != null && folderContains(plugins, path)) {
|
||||
return path.getName().replaceAll(".jar", "");
|
||||
}
|
||||
}
|
||||
} catch (Throwable ex) {
|
||||
// throw new RuntimeException("Cannot lookup plugin name.", ex);
|
||||
}
|
||||
|
||||
return null; // Cannot find it
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a folder contains the given file.
|
||||
* @param folder - the folder.
|
||||
* @param file - the file.
|
||||
* @return TRUE if it does, FALSE otherwise.
|
||||
*/
|
||||
private static boolean folderContains(File folder, File file) {
|
||||
Preconditions.checkNotNull(folder, "folder cannot be NULL");
|
||||
Preconditions.checkNotNull(file, "file cannot be NULL");
|
||||
|
||||
// Get absolute versions
|
||||
folder = folder.getAbsoluteFile();
|
||||
file = file.getAbsoluteFile();
|
||||
|
||||
while (file != null) {
|
||||
if (folder.equals(file))
|
||||
return true;
|
||||
file = file.getParentFile();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the folder that contains every plugin on the server.
|
||||
* @return Folder with every plugin, or NULL if Bukkit has not been initialized yet.
|
||||
*/
|
||||
private static File getPluginFolder() {
|
||||
File folder = pluginFolder;
|
||||
|
||||
if (folder == null && Bukkit.getServer() != null) {
|
||||
Plugin[] plugins = Bukkit.getPluginManager().getPlugins();
|
||||
|
||||
if (plugins.length > 0) {
|
||||
folder = plugins[0].getDataFolder().getParentFile();
|
||||
pluginFolder = folder;
|
||||
}
|
||||
}
|
||||
|
||||
return folder;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,240 @@
|
||||
package com.comphenix.protocol.error;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Represents a error or warning report.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public class Report {
|
||||
private final ReportType type;
|
||||
private final Throwable exception;
|
||||
private final Object[] messageParameters;
|
||||
private final Object[] callerParameters;
|
||||
|
||||
// Used to rate limit reports that are similar
|
||||
private final long rateLimit;
|
||||
|
||||
/**
|
||||
* Must be constructed through the factory method in Report.
|
||||
*/
|
||||
public static class ReportBuilder {
|
||||
private ReportType type;
|
||||
private Throwable exception;
|
||||
private Object[] messageParameters;
|
||||
private Object[] callerParameters;
|
||||
private long rateLimit;
|
||||
|
||||
private ReportBuilder() {
|
||||
// Don't allow
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current report type. Cannot be NULL.
|
||||
* @param type - report type.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public ReportBuilder type(ReportType type) {
|
||||
if (type == null)
|
||||
throw new IllegalArgumentException("Report type cannot be set to NULL.");
|
||||
this.type = type;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current exception that occurred.
|
||||
* @param exception - exception that occurred.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public ReportBuilder error(@Nullable Throwable exception) {
|
||||
this.exception = exception;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the message parameters that are used to construct a message text.
|
||||
* @param messageParameters - parameters for the report type.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public ReportBuilder messageParam(@Nullable Object... messageParameters) {
|
||||
this.messageParameters = messageParameters;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the parameters in the caller method. This is optional.
|
||||
* @param callerParameters - parameters of the caller method.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public ReportBuilder callerParam(@Nullable Object... callerParameters) {
|
||||
this.callerParameters = callerParameters;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the minimum number of nanoseconds to wait until a report of equal type and parameters
|
||||
* is allowed to be printed again.
|
||||
* @param rateLimit - number of nanoseconds, or 0 to disable. Cannot be negative.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public ReportBuilder rateLimit(long rateLimit) {
|
||||
if (rateLimit < 0)
|
||||
throw new IllegalArgumentException("Rate limit cannot be less than zero.");
|
||||
this.rateLimit = rateLimit;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the minimum time to wait until a report of equal type and parameters is allowed to be printed again.
|
||||
* @param rateLimit - the time, or 0 to disable. Cannot be negative.
|
||||
* @param rateUnit - the unit of the rate limit.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public ReportBuilder rateLimit(long rateLimit, TimeUnit rateUnit) {
|
||||
return rateLimit(TimeUnit.NANOSECONDS.convert(rateLimit, rateUnit));
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new report with the provided input.
|
||||
* @return A new report.
|
||||
*/
|
||||
public Report build() {
|
||||
return new Report(type, exception, messageParameters, callerParameters, rateLimit);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new report builder.
|
||||
* @param type - the initial report type.
|
||||
* @return Report builder.
|
||||
*/
|
||||
public static ReportBuilder newBuilder(ReportType type) {
|
||||
return new ReportBuilder().type(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new report with the given type and parameters.
|
||||
* @param exception - exception that occured in the caller method.
|
||||
* @param type - the report type that will be used to construct the message.
|
||||
* @param messageParameters - parameters used to construct the report message.
|
||||
* @param callerParameters - parameters from the caller method.
|
||||
*/
|
||||
protected Report(ReportType type, @Nullable Throwable exception,
|
||||
@Nullable Object[] messageParameters, @Nullable Object[] callerParameters) {
|
||||
this(type, exception, messageParameters, callerParameters, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new report with the given type and parameters.
|
||||
* @param exception - exception that occurred in the caller method.
|
||||
* @param type - the report type that will be used to construct the message.
|
||||
* @param messageParameters - parameters used to construct the report message.
|
||||
* @param callerParameters - parameters from the caller method.
|
||||
* @param rateLimit - minimum number of nanoseconds to wait until a report of equal type and parameters is allowed to be printed again.
|
||||
*/
|
||||
protected Report(ReportType type, @Nullable Throwable exception,
|
||||
@Nullable Object[] messageParameters, @Nullable Object[] callerParameters, long rateLimit) {
|
||||
if (type == null)
|
||||
throw new IllegalArgumentException("type cannot be NULL.");
|
||||
this.type = type;
|
||||
this.exception = exception;
|
||||
this.messageParameters = messageParameters;
|
||||
this.callerParameters = callerParameters;
|
||||
this.rateLimit = rateLimit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format the current report type with the provided message parameters.
|
||||
* @return The formated report message.
|
||||
*/
|
||||
public String getReportMessage() {
|
||||
return type.getMessage(messageParameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the message parameters that will be used to construc the report message.
|
||||
* <p>
|
||||
* This should not be confused with the method parameters of the caller method.
|
||||
* @return Message parameters.
|
||||
*/
|
||||
public Object[] getMessageParameters() {
|
||||
return messageParameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the parameters of the caller method. Optional - may be NULL.
|
||||
* @return Parameters or the caller method.
|
||||
*/
|
||||
public Object[] getCallerParameters() {
|
||||
return callerParameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the report type.
|
||||
* @return Report type.
|
||||
*/
|
||||
public ReportType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the associated exception, or NULL if not found.
|
||||
* @return Associated exception, or NULL.
|
||||
*/
|
||||
public Throwable getException() {
|
||||
return exception;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if we have any message parameters.
|
||||
* @return TRUE if there are any message parameters, FALSE otherwise.
|
||||
*/
|
||||
public boolean hasMessageParameters() {
|
||||
return messageParameters != null && messageParameters.length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if we have any caller parameters.
|
||||
* @return TRUE if there are any caller parameters, FALSE otherwise.
|
||||
*/
|
||||
public boolean hasCallerParameters() {
|
||||
return callerParameters != null && callerParameters.length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve desired minimum number of nanoseconds until a report of the same type and parameters should be reprinted.
|
||||
* <p>
|
||||
* Note that this may be ignored or modified by the error reporter. Zero indicates no rate limit.
|
||||
* @return The number of nanoseconds. Never negative.
|
||||
*/
|
||||
public long getRateLimit() {
|
||||
return rateLimit;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + Arrays.hashCode(callerParameters);
|
||||
result = prime * result + Arrays.hashCode(messageParameters);
|
||||
result = prime * result + ((type == null) ? 0 : type.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj instanceof Report) {
|
||||
Report other = (Report) obj;
|
||||
return type == other.type &&
|
||||
Arrays.equals(callerParameters, other.callerParameters) &&
|
||||
Arrays.equals(messageParameters, other.messageParameters);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
package com.comphenix.protocol.error;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.comphenix.protocol.reflect.FieldAccessException;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.comphenix.protocol.reflect.fuzzy.FuzzyFieldContract;
|
||||
|
||||
/**
|
||||
* Represents a strongly-typed report. Subclasses should be immutable.
|
||||
* <p>
|
||||
* By convention, a report must be declared as a static field publicly accessible from the sender class.
|
||||
* @author Kristian
|
||||
*/
|
||||
public class ReportType {
|
||||
private final String errorFormat;
|
||||
|
||||
// Used to store the report name
|
||||
protected String reportName;
|
||||
|
||||
/**
|
||||
* Construct a new report type.
|
||||
* @param errorFormat - string used to format the underlying report.
|
||||
*/
|
||||
public ReportType(String errorFormat) {
|
||||
this.errorFormat = errorFormat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the given report to a string, using the provided parameters.
|
||||
* @param parameters - parameters to insert, or NULL to insert nothing.
|
||||
* @return The full report in string format.
|
||||
*/
|
||||
public String getMessage(Object[] parameters) {
|
||||
if (parameters == null || parameters.length == 0)
|
||||
return toString();
|
||||
else
|
||||
return String.format(errorFormat, parameters);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return errorFormat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the class of the given sender.
|
||||
* <p>
|
||||
* If the sender is already a Class, we return it.
|
||||
* @param sender - the sender to look up.
|
||||
* @return The class of the sender.
|
||||
*/
|
||||
public static Class<?> getSenderClass(Object sender) {
|
||||
if (sender == null)
|
||||
throw new IllegalArgumentException("sender cannot be NUll.");
|
||||
else if (sender instanceof Class<?>)
|
||||
return (Class<?>) sender;
|
||||
else
|
||||
return sender.getClass();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the full canonical name of a given report type.
|
||||
* <p>
|
||||
* Note that the sender may be a class (for static callers), in which
|
||||
* case it will be used directly instead of its getClass() method.
|
||||
* <p>
|
||||
* It is thus not advisable for class classes to report reports.
|
||||
* @param sender - the sender, or its class.
|
||||
* @param type - the report type.
|
||||
* @return The full canonical name.
|
||||
*/
|
||||
public static String getReportName(Object sender, ReportType type) {
|
||||
if (sender == null)
|
||||
throw new IllegalArgumentException("sender cannot be NUll.");
|
||||
return getReportName(getSenderClass(sender), type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the full canonical name of a given report type.
|
||||
* <p>
|
||||
* This is in the format <i>canonical_name_of_class#report_type</i>
|
||||
* @param clazz - the sender class.
|
||||
* @param type - the report instance.
|
||||
* @return The full canonical name.
|
||||
*/
|
||||
private static String getReportName(Class<?> sender, ReportType type) {
|
||||
if (sender == null)
|
||||
throw new IllegalArgumentException("sender cannot be NUll.");
|
||||
|
||||
// Whether or not we need to retrieve the report name again
|
||||
if (type.reportName == null) {
|
||||
for (Field field : getReportFields(sender)) {
|
||||
try {
|
||||
field.setAccessible(true);
|
||||
|
||||
if (field.get(null) == type) {
|
||||
// We got the right field!
|
||||
return type.reportName = field.getDeclaringClass().getCanonicalName() + "#" + field.getName();
|
||||
}
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new FieldAccessException("Unable to read field " + field, e);
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Cannot find report name for " + type);
|
||||
}
|
||||
return type.reportName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all publicly associated reports.
|
||||
* @param sender - sender class.
|
||||
* @return All associated reports.
|
||||
*/
|
||||
public static ReportType[] getReports(Class<?> sender) {
|
||||
if (sender == null)
|
||||
throw new IllegalArgumentException("sender cannot be NULL.");
|
||||
List<ReportType> result = new ArrayList<ReportType>();
|
||||
|
||||
// Go through all the fields
|
||||
for (Field field : getReportFields(sender)) {
|
||||
try {
|
||||
field.setAccessible(true);
|
||||
result.add((ReportType) field.get(null));
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new FieldAccessException("Unable to read field " + field, e);
|
||||
}
|
||||
}
|
||||
return result.toArray(new ReportType[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all publicly associated report fields.
|
||||
* @param clazz - sender class.
|
||||
* @return All associated report fields.
|
||||
*/
|
||||
private static List<Field> getReportFields(Class<?> clazz) {
|
||||
return FuzzyReflection.fromClass(clazz).getFieldList(
|
||||
FuzzyFieldContract.newBuilder().
|
||||
requireModifier(Modifier.STATIC).
|
||||
typeDerivedOf(ReportType.class).
|
||||
build()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.comphenix.protocol.error;
|
||||
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import com.comphenix.protocol.error.Report.ReportBuilder;
|
||||
import com.google.common.base.Joiner;
|
||||
|
||||
/**
|
||||
* Represents an error reporter that rethrows every exception instead.
|
||||
* @author Kristian
|
||||
*/
|
||||
public class RethrowErrorReporter implements ErrorReporter {
|
||||
@Override
|
||||
public void reportMinimal(Plugin sender, String methodName, Throwable error) {
|
||||
throw new RuntimeException("Minimal error by " + sender + " in " + methodName, error);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportMinimal(Plugin sender, String methodName, Throwable error, Object... parameters) {
|
||||
throw new RuntimeException(
|
||||
"Minimal error by " + sender + " in " + methodName + " with " + Joiner.on(",").join(parameters), error);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportDebug(Object sender, Report report) {
|
||||
// Do nothing - this is just a debug
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportDebug(Object sender, ReportBuilder builder) {
|
||||
// As above
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportWarning(Object sender, ReportBuilder reportBuilder) {
|
||||
reportWarning(sender, reportBuilder.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportWarning(Object sender, Report report) {
|
||||
throw new RuntimeException("Warning by " + sender + ": " + report);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportDetailed(Object sender, ReportBuilder reportBuilder) {
|
||||
reportDetailed(sender, reportBuilder.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportDetailed(Object sender, Report report) {
|
||||
throw new RuntimeException("Detailed error " + sender + ": " + report, report.getException());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol.events;
|
||||
|
||||
import com.comphenix.protocol.PacketType.Sender;
|
||||
|
||||
/**
|
||||
* Used to set a packet filter.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public enum ConnectionSide {
|
||||
/**
|
||||
* Listen for server side packets that will invoke onPacketSending().
|
||||
*/
|
||||
SERVER_SIDE,
|
||||
|
||||
/**
|
||||
* Listen for client side packets that will invoke onPacketReceiving().
|
||||
*/
|
||||
CLIENT_SIDE,
|
||||
|
||||
/**
|
||||
* Listen for both client and server side packets.
|
||||
*/
|
||||
BOTH;
|
||||
|
||||
public boolean isForClient() {
|
||||
return this == CLIENT_SIDE || this == BOTH;
|
||||
}
|
||||
|
||||
public boolean isForServer() {
|
||||
return this == SERVER_SIDE || this == BOTH;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the sender of this connection side.
|
||||
* <p>
|
||||
* This is NULL for {@link #BOTH}.
|
||||
* @return The sender.
|
||||
*/
|
||||
public Sender getSender() {
|
||||
if (this == SERVER_SIDE)
|
||||
return Sender.SERVER;
|
||||
else if (this == CLIENT_SIDE)
|
||||
return Sender.CLIENT;
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* If both connection sides are present, return {@link #BOTH} - otherwise, return the one valud connection side.
|
||||
* <p>
|
||||
* NULL is not a valid connection side.
|
||||
* @param a - the first connection side.
|
||||
* @param b - the second connection side.
|
||||
* @return BOTH or the one valid side, or NULL.
|
||||
*/
|
||||
public static ConnectionSide add(ConnectionSide a, ConnectionSide b) {
|
||||
if (a == null)
|
||||
return b;
|
||||
if (b == null)
|
||||
return a;
|
||||
|
||||
// Now merge them together
|
||||
boolean client = a.isForClient() || b.isForClient();
|
||||
boolean server = a.isForServer() || b.isForServer();
|
||||
|
||||
if (client && server)
|
||||
return BOTH;
|
||||
else if (client)
|
||||
return CLIENT_SIDE;
|
||||
else
|
||||
return SERVER_SIDE;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.comphenix.protocol.events;
|
||||
|
||||
import com.comphenix.protocol.injector.GamePhase;
|
||||
|
||||
/**
|
||||
* Represents additional options a listener may require.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public enum ListenerOptions {
|
||||
/**
|
||||
* Retrieve the serialized client packet as it appears on the network stream.
|
||||
*/
|
||||
INTERCEPT_INPUT_BUFFER,
|
||||
|
||||
/**
|
||||
* Disable the automatic game phase detection that will normally force {@link GamePhase#LOGIN} when
|
||||
* a packet ID is known to be transmitted during login.
|
||||
*/
|
||||
DISABLE_GAMEPHASE_DETECTION,
|
||||
|
||||
/**
|
||||
* Do not verify that the owning plugin has a vaid plugin.yml.
|
||||
*/
|
||||
SKIP_PLUGIN_VERIFIER,
|
||||
|
||||
/**
|
||||
* Notify ProtocolLib that {@link PacketListener#onPacketSending(PacketEvent)} is thread safe.
|
||||
*/
|
||||
ASYNC;
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Represents a packet event priority, similar to the Bukkit EventPriority.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public enum ListenerPriority {
|
||||
/**
|
||||
* Event call is of very low importance and should be ran first, to allow
|
||||
* other plugins to further customise the outcome.
|
||||
*/
|
||||
LOWEST(0),
|
||||
/**
|
||||
* Event call is of low importance.
|
||||
*/
|
||||
LOW(1),
|
||||
/**
|
||||
* Event call is neither important or unimportant, and may be ran normally.
|
||||
*/
|
||||
NORMAL(2),
|
||||
/**
|
||||
* Event call is of high importance.
|
||||
*/
|
||||
HIGH(3),
|
||||
/**
|
||||
* Event call is critical and must have the final say in what happens to the
|
||||
* event.
|
||||
*/
|
||||
HIGHEST(4),
|
||||
/**
|
||||
* Event is listened to purely for monitoring the outcome of an event.
|
||||
* <p>
|
||||
* No modifications to the event should be made under this priority.
|
||||
*/
|
||||
MONITOR(5);
|
||||
|
||||
private final int slot;
|
||||
|
||||
private ListenerPriority(int slot) {
|
||||
this.slot = slot;
|
||||
}
|
||||
|
||||
/**
|
||||
* A low slot represents a low priority.
|
||||
* @return Integer representation of this priorty.
|
||||
*/
|
||||
public int getSlot() {
|
||||
return slot;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,484 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol.events;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Set;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.injector.GamePhase;
|
||||
import com.comphenix.protocol.injector.packet.PacketRegistry;
|
||||
import com.google.common.base.Objects;
|
||||
import com.google.common.collect.Sets;
|
||||
|
||||
/**
|
||||
* Determines which packets will be observed by a listener, and with what priority.
|
||||
|
||||
* @author Kristian
|
||||
*/
|
||||
public class ListeningWhitelist {
|
||||
/**
|
||||
* A whitelist with no packets - indicates that the listener shouldn't observe any packets.
|
||||
*/
|
||||
public static final ListeningWhitelist EMPTY_WHITELIST = new ListeningWhitelist(ListenerPriority.LOW);
|
||||
|
||||
private final ListenerPriority priority;
|
||||
private final GamePhase gamePhase;
|
||||
private final Set<ListenerOptions> options;
|
||||
private final Set<PacketType> types;
|
||||
|
||||
// Cache whitelist
|
||||
private transient Set<Integer> intWhitelist;
|
||||
|
||||
private ListeningWhitelist(Builder builder) {
|
||||
this.priority = builder.priority;
|
||||
this.types = builder.types;
|
||||
this.gamePhase = builder.gamePhase;
|
||||
this.options = builder.options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a packet whitelist for a given priority with a set of packet IDs.
|
||||
* <p>
|
||||
* Deprecated: Use {@link #newBuilder()} instead.
|
||||
* @param priority - the listener priority.
|
||||
* @param whitelist - set of IDs to observe/enable.
|
||||
*/
|
||||
@Deprecated
|
||||
public ListeningWhitelist(ListenerPriority priority, Set<Integer> whitelist) {
|
||||
this(priority, whitelist, GamePhase.PLAYING);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a packet whitelist for a given priority with a set of packet IDs.
|
||||
* <p>
|
||||
* Deprecated: Use {@link #newBuilder()} instead.
|
||||
* @param priority - the listener priority.
|
||||
* @param whitelist - set of IDs to observe/enable.
|
||||
* @param gamePhase - which game phase to receieve notifications on.
|
||||
*/
|
||||
@Deprecated
|
||||
public ListeningWhitelist(ListenerPriority priority, Set<Integer> whitelist, GamePhase gamePhase) {
|
||||
this.priority = priority;
|
||||
this.types = PacketRegistry.toPacketTypes(safeSet(whitelist));
|
||||
this.gamePhase = gamePhase;
|
||||
this.options = EnumSet.noneOf(ListenerOptions.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a packet whitelist of a given priority for a list of packets.
|
||||
* <p>
|
||||
* Deprecated: Use {@link #newBuilder()} instead.
|
||||
* @param priority - the listener priority.
|
||||
* @param whitelist - list of packet IDs to observe/enable.
|
||||
*/
|
||||
@Deprecated
|
||||
public ListeningWhitelist(ListenerPriority priority, Integer... whitelist) {
|
||||
this.priority = priority;
|
||||
this.types = PacketRegistry.toPacketTypes(Sets.newHashSet(whitelist));
|
||||
this.gamePhase = GamePhase.PLAYING;
|
||||
this.options = EnumSet.noneOf(ListenerOptions.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a packet whitelist for a given priority with a set of packet IDs.
|
||||
* <p>
|
||||
* Deprecated: Use {@link #newBuilder()} instead.
|
||||
* @param priority - the listener priority.
|
||||
* @param whitelist - list of packet IDs to observe/enable.
|
||||
* @param gamePhase - which game phase to receieve notifications on.
|
||||
*/
|
||||
@Deprecated
|
||||
public ListeningWhitelist(ListenerPriority priority, Integer[] whitelist, GamePhase gamePhase) {
|
||||
this.priority = priority;
|
||||
this.types = PacketRegistry.toPacketTypes(Sets.newHashSet(whitelist));
|
||||
this.gamePhase = gamePhase;
|
||||
this.options = EnumSet.noneOf(ListenerOptions.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a packet whitelist for a given priority with a set of packet IDs and options.
|
||||
* <p>
|
||||
* Deprecated: Use {@link #newBuilder()} instead.
|
||||
* @param priority - the listener priority.
|
||||
* @param whitelist - list of packet IDs to observe/enable.
|
||||
* @param gamePhase - which game phase to receieve notifications on.
|
||||
* @param options - every special option associated with this whitelist.
|
||||
*/
|
||||
@Deprecated
|
||||
public ListeningWhitelist(ListenerPriority priority, Integer[] whitelist, GamePhase gamePhase, ListenerOptions... options) {
|
||||
this.priority = priority;
|
||||
this.types = PacketRegistry.toPacketTypes(Sets.newHashSet(whitelist));
|
||||
this.gamePhase = gamePhase;
|
||||
this.options = safeEnumSet(Arrays.asList(options), ListenerOptions.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not this whitelist has any enabled packets.
|
||||
* @return TRUE if there are any packets, FALSE otherwise.
|
||||
*/
|
||||
public boolean isEnabled() {
|
||||
return types != null && types.size() > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the priority in the execution order of the packet listener. Highest priority will be executed last.
|
||||
* @return Execution order in terms of priority.
|
||||
*/
|
||||
public ListenerPriority getPriority() {
|
||||
return priority;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the list of packets that will be observed by the listeners.
|
||||
* <p>
|
||||
* Deprecated: Use {@link #getTypes()} instead.
|
||||
* @return Packet whitelist.
|
||||
*/
|
||||
@Deprecated
|
||||
public Set<Integer> getWhitelist() {
|
||||
if (intWhitelist == null)
|
||||
intWhitelist = PacketRegistry.toLegacy(types);
|
||||
return intWhitelist;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a set of the packets that will be observed by the listeners.
|
||||
* @return Packet whitelist.
|
||||
*/
|
||||
public Set<PacketType> getTypes() {
|
||||
return types;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve which game phase this listener is active under.
|
||||
* @return The active game phase.
|
||||
*/
|
||||
public GamePhase getGamePhase() {
|
||||
return gamePhase;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve every special option associated with this whitelist.
|
||||
* @return Every special option.
|
||||
*/
|
||||
public Set<ListenerOptions> getOptions() {
|
||||
return Collections.unmodifiableSet(options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hashCode(priority, types, gamePhase, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if any of the given IDs can be found in the whitelist.
|
||||
* @param whitelist - whitelist to test.
|
||||
* @param idList - list of packet IDs to find.
|
||||
* @return TRUE if any of the packets in the list can be found in the whitelist, FALSE otherwise.
|
||||
*/
|
||||
public static boolean containsAny(ListeningWhitelist whitelist, int... idList) {
|
||||
if (whitelist != null) {
|
||||
for (int i = 0; i < idList.length; i++) {
|
||||
if (whitelist.getWhitelist().contains(idList[i]))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the given whitelist is empty or not.
|
||||
* @param whitelist - the whitelist to test.
|
||||
* @return TRUE if the whitelist is empty, FALSE otherwise.
|
||||
*/
|
||||
public static boolean isEmpty(ListeningWhitelist whitelist) {
|
||||
if (whitelist == EMPTY_WHITELIST)
|
||||
return true;
|
||||
else if (whitelist == null)
|
||||
return true;
|
||||
else
|
||||
return whitelist.getTypes().isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
if (obj instanceof ListeningWhitelist) {
|
||||
final ListeningWhitelist other = (ListeningWhitelist) obj;
|
||||
return Objects.equal(priority, other.priority)
|
||||
&& Objects.equal(types, other.types)
|
||||
&& Objects.equal(gamePhase, other.gamePhase)
|
||||
&& Objects.equal(options, other.options);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (this == EMPTY_WHITELIST)
|
||||
return "EMPTY_WHITELIST";
|
||||
else
|
||||
return Objects.toStringHelper(this).
|
||||
add("priority", priority).
|
||||
add("packets", types).
|
||||
add("gamephase", gamePhase).
|
||||
add("options", options).
|
||||
toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new builder of whitelists.
|
||||
* @return New whitelist builder.
|
||||
*/
|
||||
public static Builder newBuilder() {
|
||||
return new Builder(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new builder of whitelists initialized to the same values as the template.
|
||||
* @param template - the template object.
|
||||
* @return New whitelist builder.
|
||||
*/
|
||||
public static Builder newBuilder(ListeningWhitelist template) {
|
||||
return new Builder(template);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a copy of a given enum.
|
||||
* @param options - the options to copy, or NULL to indicate the empty set.
|
||||
* @return A copy of the enum set.
|
||||
*/
|
||||
private static <T extends Enum<T>> EnumSet<T> safeEnumSet(Collection<T> options, Class<T> enumClass) {
|
||||
if (options != null && !options.isEmpty()) {
|
||||
return EnumSet.copyOf(options);
|
||||
} else {
|
||||
return EnumSet.noneOf(enumClass);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a copy of a given set.
|
||||
* @param list - the set to copy.
|
||||
* @return The copied set.
|
||||
*/
|
||||
private static <T> Set<T> safeSet(Collection<T> set) {
|
||||
if (set != null)
|
||||
return Sets.newHashSet(set);
|
||||
else
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a builder of whitelists.
|
||||
* @author Kristian
|
||||
*/
|
||||
public static class Builder {
|
||||
// Default values
|
||||
private ListenerPriority priority = ListenerPriority.NORMAL;
|
||||
private Set<PacketType> types = Sets.newHashSet();
|
||||
private GamePhase gamePhase = GamePhase.PLAYING;
|
||||
private Set<ListenerOptions> options = Sets.newHashSet();
|
||||
|
||||
/**
|
||||
* Construct a new listening whitelist template.
|
||||
* @param template - the template.
|
||||
*/
|
||||
private Builder(ListeningWhitelist template) {
|
||||
if (template != null) {
|
||||
priority(template.getPriority());
|
||||
gamePhase(template.getGamePhase());
|
||||
types(template.getTypes());
|
||||
options(template.getOptions());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the priority to use when constructing new whitelists.
|
||||
* @param priority - the priority.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public Builder priority(ListenerPriority priority) {
|
||||
this.priority = priority;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the priority of the whitelist to monitor.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public Builder monitor() {
|
||||
return priority(ListenerPriority.MONITOR);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the priority of the whitelist to normal.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public Builder normal() {
|
||||
return priority(ListenerPriority.NORMAL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the priority of the whitelist to lowest.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public Builder lowest() {
|
||||
return priority(ListenerPriority.LOWEST);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the priority of the whitelist to low.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public Builder low() {
|
||||
return priority(ListenerPriority.LOW);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the priority of the whitelist to highest.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public Builder highest() {
|
||||
return priority(ListenerPriority.HIGHEST);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the priority of the whitelist to high.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public Builder high() {
|
||||
return priority(ListenerPriority.HIGH);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the whitelist of packet IDs to copy when constructing new whitelists.
|
||||
* <p>
|
||||
* Deprecated: Use {@link #types(Collection)} instead.
|
||||
* @param whitelist - the whitelist of packets.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
@Deprecated
|
||||
public Builder whitelist(Collection<Integer> whitelist) {
|
||||
this.types = PacketRegistry.toPacketTypes(safeSet(whitelist));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the whitelist of packet types to copy when constructing new whitelists.
|
||||
* @param types - the whitelist of packets.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public Builder types(PacketType... types) {
|
||||
this.types = safeSet(Sets.newHashSet(types));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the whitelist of packet types to copy when constructing new whitelists.
|
||||
* @param types - the whitelist of packets.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public Builder types(Collection<PacketType> types) {
|
||||
this.types = safeSet(types);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the gamephase to use when constructing new whitelists.
|
||||
* @param gamePhase - the gamephase.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public Builder gamePhase(GamePhase gamePhase) {
|
||||
this.gamePhase = gamePhase;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the gamephase to {@link GamePhase#BOTH}.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public Builder gamePhaseBoth() {
|
||||
return gamePhase(GamePhase.BOTH);
|
||||
}
|
||||
/**
|
||||
* Set the options to copy when constructing new whitelists.
|
||||
* @param options - the options.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public Builder options(Set<ListenerOptions> options) {
|
||||
this.options = safeSet(options);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the options to copy when constructing new whitelists.
|
||||
* @param options - the options.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public Builder options(Collection<ListenerOptions> options) {
|
||||
this.options = safeSet(options);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the options to copy when constructing new whitelists.
|
||||
* @param serverOptions - the options array.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public Builder options(ListenerOptions[] serverOptions) {
|
||||
this.options = safeSet(Sets.newHashSet(serverOptions));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Options to merge into the current set of options.
|
||||
* @param serverOptions - the options array.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public Builder mergeOptions(ListenerOptions... serverOptions) {
|
||||
return mergeOptions(Arrays.asList(serverOptions));
|
||||
}
|
||||
|
||||
/**
|
||||
* Options to merge into the current set of options.
|
||||
* @param serverOptions - the options array.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public Builder mergeOptions(Collection<ListenerOptions> serverOptions) {
|
||||
if (options == null)
|
||||
return options(serverOptions);
|
||||
|
||||
// Merge the options
|
||||
this.options.addAll(serverOptions);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new whitelist from the values in this builder.
|
||||
* @return The new whitelist.
|
||||
*/
|
||||
public ListeningWhitelist build() {
|
||||
return new ListeningWhitelist(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol.events;
|
||||
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import com.comphenix.protocol.Packets;
|
||||
import com.comphenix.protocol.injector.GamePhase;
|
||||
import com.comphenix.protocol.injector.packet.PacketRegistry;
|
||||
import com.comphenix.protocol.reflect.FieldAccessException;
|
||||
|
||||
/**
|
||||
* Represents a listener that is notified of every sent and received packet.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public abstract class MonitorAdapter implements PacketListener {
|
||||
|
||||
private Plugin plugin;
|
||||
private ListeningWhitelist sending = ListeningWhitelist.EMPTY_WHITELIST;
|
||||
private ListeningWhitelist receiving = ListeningWhitelist.EMPTY_WHITELIST;
|
||||
|
||||
public MonitorAdapter(Plugin plugin, ConnectionSide side) {
|
||||
initialize(plugin, side, getLogger(plugin));
|
||||
}
|
||||
|
||||
public MonitorAdapter(Plugin plugin, ConnectionSide side, Logger logger) {
|
||||
initialize(plugin, side, logger);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private void initialize(Plugin plugin, ConnectionSide side, Logger logger) {
|
||||
this.plugin = plugin;
|
||||
|
||||
// Recover in case something goes wrong
|
||||
try {
|
||||
if (side.isForServer())
|
||||
this.sending = ListeningWhitelist.newBuilder().monitor().types(PacketRegistry.getServerPacketTypes()).gamePhaseBoth().build();
|
||||
if (side.isForClient())
|
||||
this.receiving = ListeningWhitelist.newBuilder().monitor().types(PacketRegistry.getClientPacketTypes()).gamePhaseBoth().build();
|
||||
} catch (FieldAccessException e) {
|
||||
if (side.isForServer())
|
||||
this.sending = new ListeningWhitelist(ListenerPriority.MONITOR, Packets.Server.getRegistry().values(), GamePhase.BOTH);
|
||||
if (side.isForClient())
|
||||
this.receiving = new ListeningWhitelist(ListenerPriority.MONITOR, Packets.Client.getRegistry().values(), GamePhase.BOTH);
|
||||
logger.log(Level.WARNING, "Defaulting to 1.3 packets.", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a logger, even if we're running in a CraftBukkit version that doesn't support it.
|
||||
* @param plugin - the plugin to retrieve.
|
||||
* @return The logger.
|
||||
*/
|
||||
private Logger getLogger(Plugin plugin) {
|
||||
try {
|
||||
return plugin.getLogger();
|
||||
} catch (NoSuchMethodError e) {
|
||||
return Logger.getLogger("Minecraft");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListeningWhitelist getSendingWhitelist() {
|
||||
return sending;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListeningWhitelist getReceivingWhitelist() {
|
||||
return receiving;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Plugin getPlugin() {
|
||||
return plugin;
|
||||
}
|
||||
}
|
||||
|
||||
Loaded 100 of 369 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user