Repository: Irmine/GoMine
Branch: master
Commit: b9cfb1f4b4b1
Files: 107
Total size: 269.0 KB
Directory structure:
gitextract_5pdpetld/
├── .github/
│ └── ISSUE_TEMPLATE.md
├── .gitignore
├── LICENSE
├── README.md
├── cmd/
│ └── gomine/
│ ├── main.go
│ └── shared_server_test.go
├── commands/
│ ├── arguments/
│ │ ├── argument.go
│ │ └── basic.go
│ ├── command.go
│ ├── manager.go
│ ├── selectors/
│ │ ├── all_entities.go
│ │ ├── all_players.go
│ │ ├── nearest_player.go
│ │ ├── random_player.go
│ │ ├── selector.go
│ │ └── self.go
│ └── sender.go
├── default_commands.go
├── items/
│ ├── conversion.go
│ ├── enchantments/
│ │ ├── enchantable.go
│ │ ├── enchantment.go
│ │ ├── enchantment_ids.go
│ │ └── manager.go
│ ├── inventory/
│ │ ├── inventory.go
│ │ ├── inventory_test.go
│ │ └── io/
│ │ ├── inventory_action_io.go
│ │ └── inventory_action_io_list.go
│ ├── item_test.go
│ ├── manager.go
│ ├── nbt_tag_names.go
│ ├── stack.go
│ └── type.go
├── net/
│ ├── info/
│ │ ├── info.go
│ │ └── protocol_ids.go
│ ├── manager.go
│ ├── minecraft_packet_batch.go
│ ├── minecraft_session.go
│ ├── network_adapter.go
│ ├── packet_handler.go
│ ├── packets/
│ │ ├── bedrock/
│ │ │ ├── add_entity.go
│ │ │ ├── add_player.go
│ │ │ ├── animate.go
│ │ │ ├── chunk_radius_updated.go
│ │ │ ├── client_handshake.go
│ │ │ ├── command_request.go
│ │ │ ├── crafting_data.go
│ │ │ ├── disconnect.go
│ │ │ ├── full_chunk_data.go
│ │ │ ├── interact_packet.go
│ │ │ ├── inventory_transaction.go
│ │ │ ├── login.go
│ │ │ ├── move_entity.go
│ │ │ ├── move_player.go
│ │ │ ├── network_chunk_publisher_update.go
│ │ │ ├── play_status.go
│ │ │ ├── player_action.go
│ │ │ ├── player_list.go
│ │ │ ├── player_skin.go
│ │ │ ├── remove_entity.go
│ │ │ ├── request_chunk_radius.go
│ │ │ ├── resource_pack_chunk_data.go
│ │ │ ├── resource_pack_chunk_request.go
│ │ │ ├── resource_pack_client_response.go
│ │ │ ├── resource_pack_data_info.go
│ │ │ ├── resource_pack_info.go
│ │ │ ├── resource_pack_stack.go
│ │ │ ├── server_handshake.go
│ │ │ ├── set_entity_data.go
│ │ │ ├── start_game.go
│ │ │ ├── text.go
│ │ │ ├── transfer.go
│ │ │ ├── update_attributes.go
│ │ │ └── update_block.go
│ │ ├── data/
│ │ │ └── constants.go
│ │ ├── minecraft_stream.go
│ │ ├── packet.go
│ │ └── types/
│ │ ├── levels.go
│ │ ├── net.go
│ │ ├── players.go
│ │ └── resource_packs.go
│ ├── protocol/
│ │ ├── entries.go
│ │ ├── handler.go
│ │ └── protocol.go
│ └── protocol_adapter.go
├── packet_handler.go
├── packet_manager.go
├── packs/
│ ├── base.go
│ ├── behavior.go
│ ├── manager.go
│ ├── resource.go
│ └── stack.go
├── permissions/
│ ├── group.go
│ ├── level.go
│ ├── manager.go
│ ├── permissible.go
│ └── permission.go
├── players/
│ └── player.go
├── plugin.go
├── plugin_manager.go
├── resources/
│ └── gomine.yml.go
├── server.go
├── text/
│ ├── command_reader.go
│ ├── font.go
│ ├── logger.go
│ └── logger_test.go
└── utils/
├── encryption.go
└── utils.go
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/ISSUE_TEMPLATE.md
================================================
#### Description
#### Information
* GoMine Version:
* GoMine Commit/Release:
* Operating System:
* Game Variant:
#### Plugins
#### Crash/Error
```go
```
================================================
FILE: .gitignore
================================================
.idea/
extensions/
worlds/
gomine.log
gomine.yml
gomine.exe
================================================
FILE: LICENSE
================================================
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc.
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. 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
them 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 prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. 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.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey 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;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If 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 convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU 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 that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
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.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
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.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
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
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
Copyright (C)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
Copyright (C)
This program 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, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
.
The GNU 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. But first, please read
.
================================================
FILE: README.md
================================================
#### GoMine is a Minecraft Bedrock Edition server software written in Go.
### Information
GoMine is a fast multi-threaded Minecraft server software. It aims to provide a highly customizable API for plugin developers to use. GoMine aims to make the setup of a server very easy, (through an executable) with low compile times, and aims to make GoMine usable for other purposes than just a vanilla server.
### Current State
GoMine is currently under heavy development and is not usable for production servers yet. It lacks many features which are yet to be implemented, and has (yet unknown) bugs that should be resolved.
### Releases and Development Builds
Development builds of GoMine might be unstable and should be used with care. It is always recommended to run officially released versions of GoMine for production where possible, to ensure no nasty bugs appear. If you do decide to run a development version, be aware that bugs may occur. Don't hesitate to report those bugs.
### Setup
GoMine aims to make the setup of a server very easily. The setup of GoMine can be explained in a couple steps.
If you want to use an official release:
1. Download the executable for your operating system from `Releases` and move it to your setup directory.
2. Execute the executable to run the server.
If you would like to use a development version:
1. Install Go > 1.9 from the official release page.
2. To clone the repository, execute `go get github.com/irmine/gomine`.
3. Compile GoMine by navigating into the `irmine/gomine` folder and executing `go install`.
4. Navigate to the folder at `GOBIN`, and grab the executable.
5. Move it to your setup folder and execute the executable.
### Issues
Issues can be reported in the `Issues` tab. Please provide enough information for us to solve the problem. The more information you provide, the easier it makes it for us to fix your issue.
### License
GoMine is licensed under the GNU General Public License.
================================================
FILE: cmd/gomine/main.go
================================================
package main
import (
"github.com/irmine/gomine"
"github.com/irmine/gomine/resources"
"github.com/irmine/gomine/text"
"os"
"path/filepath"
"strings"
"time"
)
func main() {
startTime := time.Now()
path, err := GetServerPath()
must(err)
SetUpDirectories(path)
config := resources.NewGoMineConfig(path)
server := gomine.NewServer(path, config)
must(server.Start())
text.DefaultLogger.Info("Server startup done! Took:", time.Now().Sub(startTime))
var ticker = time.NewTicker(time.Millisecond * 50)
for {
select{
case <- ticker.C:
if !server.IsRunning() {
break
}
server.Tick()
}
}
}
func must(err error) {
if err != nil {
panic(err)
}
}
// GetServerPath returns the server path.
func GetServerPath() (string, error) {
executable, err := os.Executable()
return strings.Replace(filepath.Dir(executable)+"/", `\`, "/", -1), err
}
// SetUpDirectories sets up all directories needed for GoMine.
func SetUpDirectories(path string) {
os.Mkdir(path+"extensions", 0700)
os.Mkdir(path+"extensions/plugins", 0700)
os.Mkdir(path+"extensions/behavior_packs", 0700)
os.Mkdir(path+"extensions/resource_packs", 0700)
}
================================================
FILE: cmd/gomine/shared_server_test.go
================================================
package main
import (
"github.com/irmine/gomine"
"github.com/irmine/gomine/resources"
"github.com/irmine/gomine/text"
"testing"
"time"
)
func TestSharedServer(t *testing.T) {
ports := []uint16{19132, 19133, 19134, 19135, 19136}
for _, port := range ports {
go StartServer(port)
}
time.Sleep(time.Minute * 10)
}
func StartServer(port uint16) {
text.DefaultLogger.Info("Starting server with port:", port)
startTime := time.Now()
path, err := GetServerPath()
if err != nil {
panic(err)
}
SetUpDirectories(path)
config := resources.NewGoMineConfig(path)
config.ServerPort = port
server := gomine.NewServer(path, config)
if err := server.Start(); err != nil {
panic(err)
}
text.DefaultLogger.Info("Server startup done! Took:", time.Now().Sub(startTime))
for range time.NewTicker(time.Second / 20).C {
if !server.IsRunning() {
break
}
server.Tick()
}
}
================================================
FILE: commands/arguments/argument.go
================================================
package arguments
import "strconv"
type Argument struct {
name string
optional bool
inputArgs int
output interface{}
validationFunction func(argument string) bool
conversionFunction func(argument string) interface{}
shouldMerge bool
}
// GetName returns the name of the argument.
func (argument *Argument) GetName() string {
return argument.name
}
// SetName sets the name of the argument.
func (argument *Argument) SetName(name string) {
argument.name = name
}
// IsOptional checks if the argument is optional.
func (argument *Argument) IsOptional() bool {
return argument.optional
}
// SetOptional sets the argument optional or non-optional.
func (argument *Argument) SetOptional(value bool) {
argument.optional = value
}
// GetInputAmount returns the amount of arguments of input this argument requires.
func (argument *Argument) GetInputAmount() int {
return argument.inputArgs
}
// SetInputAmount sets the amount of arguments the input of this argument requires.
func (argument *Argument) SetInputAmount(amount int) {
argument.inputArgs = amount
}
// SetOutput sets the output value of this argument.
func (argument *Argument) SetOutput(value interface{}) {
argument.output = value
}
// GetOutput returns the output value of this argument.
func (argument *Argument) GetOutput() interface{} {
return argument.output
}
// ShouldMerge returns whether this argument should merge all its values or not.
func (argument *Argument) ShouldMerge() bool {
return argument.shouldMerge
}
// IsValidValue checks if the given value is valid for the argument.
func (argument *Argument) IsValidValue(value string) bool {
return argument.validationFunction(value)
}
// ConvertValues returns the converted value of the value.
func (argument *Argument) ConvertValue(value string) interface{} {
return argument.conversionFunction(value)
}
// IsInt checks if the input string is able to be parsed as an integer.
func IsInt(value string) bool {
var _, err = strconv.Atoi(value)
return err == nil
}
// IsFloat checks if the input string is able to be parsed as an integer.
func IsFloat(value string) bool {
var _, err = strconv.ParseFloat(value, 64)
return err == nil
}
================================================
FILE: commands/arguments/basic.go
================================================
package arguments
import (
"strconv"
"strings"
)
// NewFloat returns a new Float argument with the given name and optional value.
func NewFloat(name string, optional bool) *Argument {
return &Argument{name, optional, 1, float64(0), func(value string) bool {
return IsFloat(value)
}, func(value string) interface{} {
var float, _ = strconv.ParseFloat(value, 64)
return float
}, false}
}
// NewInt returns a new Int argument with the given name and optional value.
func NewInt(name string, optional bool) *Argument {
return &Argument{name, optional, 1, 0, func(value string) bool {
return IsInt(value)
}, func(value string) interface{} {
var i, _ = strconv.ParseInt(value, 10, 64)
return i
}, false}
}
// NewString returns a new String argument with the given name and optional value.
func NewString(name string, optional bool) *Argument {
var arg = &Argument{name, optional, 1, "", func(value string) bool {
return true
}, func(value string) interface{} {
return value
}, true}
return arg
}
// NewStringEnum returns a new String Enum argument with the given name and optional value.
func NewStringEnum(name string, optional bool, options []string) *Argument {
var arg = &Argument{name, optional, 1, "", func(value string) bool {
for _, option := range options {
if strings.ToLower(option) == strings.ToLower(value) {
return true
}
}
return false
}, func(value string) interface{} {
return strings.ToLower(value)
}, true}
return arg
}
================================================
FILE: commands/command.go
================================================
package commands
import (
"reflect"
"strconv"
"strings"
"github.com/irmine/gomine/commands/arguments"
"github.com/irmine/gomine/text"
)
type Command struct {
name string
description string
permission string
aliases []string
arguments []*arguments.Argument
argumentTypes []string
usage string
permissionExempt bool
executionFunction interface{}
}
// NewCommand returns a new command with the given command function.
// The permission used in the command should be registered in order to get correct output.
func NewCommand(name string, description string, permission string, aliases []string, function interface{}) *Command {
if reflect.TypeOf(function).Kind() != reflect.Func {
function = func() {}
}
return &Command{name: name, permission: permission, aliases: aliases, description: description, executionFunction: function}
}
// GetUsage returns the usage of this command.
// The usage will get parsed if it had not yet been.
func (command *Command) GetUsage() string {
command.parseUsage()
return command.usage
}
// ExemptFromPermissionCheck sets the command exempted from permission checking, allowing anybody to use it.
func (command *Command) ExemptFromPermissionCheck(value bool) {
command.permissionExempt = value
}
// IsPermissionChecked checks if the user of this command is checked for the adequate permission.
func (command *Command) IsPermissionChecked() bool {
return !command.permissionExempt
}
// GetName returns the command name.
func (command *Command) GetName() string {
return command.name
}
// GetDescription returns the command description.
func (command *Command) GetDescription() string {
return command.description
}
// SetDescription sets the description of the command.
func (command *Command) SetDescription(description string) {
command.description = description
}
// SetPermission sets the permission of the command.
func (command *Command) SetPermission(permission string) {
command.permission = permission
}
// GetPermission returns the command permission string.
func (command *Command) GetPermission() string {
return command.permission
}
// GetAliases returns the aliases of this command.
func (command *Command) GetAliases() []string {
return command.aliases
}
// GetArguments returns a slice with all arguments.
func (command *Command) GetArguments() []*arguments.Argument {
return command.arguments
}
// SetArguments sets the command arguments.
func (command *Command) SetArguments(arguments []*arguments.Argument) {
command.arguments = arguments
}
// AppendArgument adds one argument to the command.
func (command *Command) AppendArgument(argument *arguments.Argument) {
command.argumentTypes = append(command.argumentTypes, reflect.TypeOf(argument.GetOutput()).Name())
command.arguments = append(command.arguments, argument)
}
// parseUsage parses the usage into a readable and clear one.
func (command *Command) parseUsage() {
if command.usage == "" {
var usage = text.Yellow + "Usage: /" + command.GetName() + " "
for index, argument := range command.GetArguments() {
if argument.IsOptional() {
usage += "["
} else {
usage += "<"
}
usage += argument.GetName() + ": " + command.argumentTypes[index]
if argument.GetInputAmount() > 1 && command.argumentTypes[index] != "string" {
usage += "(" + strconv.Itoa(argument.GetInputAmount()) + ")"
}
if argument.IsOptional() {
usage += "]"
} else {
usage += ">"
}
usage += " "
}
command.usage = usage
}
}
// Execute executes the command with the given sender and command arguments.
func (command *Command) Execute(sender Sender, commandArgs []string) {
if _, ok := command.parse(sender, commandArgs); !ok {
return
}
command.parseArgsAndExecute(sender)
}
// Parse checks and parses the values of a command.
func (command *Command) parse(sender Sender, commandArgs []string) ([]*arguments.Argument, bool) {
if command.IsPermissionChecked() && !sender.HasPermission(command.GetPermission()) {
sender.SendMessage("You do not have permission to execute this command.")
return []*arguments.Argument{}, false
}
var stringIndex = 0
if len(commandArgs) == 0 {
if len(command.GetArguments()) == 0 {
return command.GetArguments(), true
}
sender.SendMessage(command.GetUsage())
return nil, false
}
for _, argument := range command.arguments {
var i = 0
var output []string
for i < argument.GetInputAmount() {
if len(commandArgs) < stringIndex+i+1 {
if !argument.IsOptional() {
sender.SendMessage(command.GetUsage())
return nil, false
}
} else {
commandArgs[stringIndex+i] = strings.TrimSpace(commandArgs[stringIndex+i])
if !argument.IsValidValue(commandArgs[stringIndex+i]) {
sender.SendMessage(command.GetUsage())
return nil, false
}
output = append(output, commandArgs[stringIndex+i])
}
i++
}
stringIndex += i
var processedOutput []interface{}
for _, value := range output {
processedOutput = append(processedOutput, argument.ConvertValue(value))
}
if argument.ShouldMerge() {
argument.SetOutput(strings.Join(output, " "))
} else {
if len(processedOutput) == 1 {
argument.SetOutput(processedOutput[0])
} else {
argument.SetOutput(processedOutput)
}
}
}
return command.GetArguments(), true
}
// ParseArgsAndExecute parses the arguments into an output able to be typed against.
// After parsing, the command gets called.
func (command *Command) parseArgsAndExecute(sender Sender) {
var method = reflect.ValueOf(command.executionFunction)
var input = make([]reflect.Value, method.Type().NumIn())
var argOffset = 0
for i := 0; i < method.Type().NumIn(); i++ {
if method.Type().In(i).String() == "commands.Sender" {
input[i] = reflect.ValueOf(sender)
continue
}
input[i] = reflect.ValueOf(command.arguments[argOffset].GetOutput())
argOffset++
}
method.Call(input)
}
================================================
FILE: commands/manager.go
================================================
package commands
import (
"errors"
)
type Manager struct {
commands map[string]*Command
aliases map[string]*Command
}
// NewManager returns a new Manager struct.
func NewManager() *Manager {
return &Manager{make(map[string]*Command), make(map[string]*Command)}
}
// IsCommandRegistered checks if the command has been registered.
// Also checks for aliases.
func (holder *Manager) IsCommandRegistered(commandName string) bool {
var _, exists = holder.GetCommand(commandName)
return exists == nil
}
// DeregisterCommand deregisters a command from the command holder.
// Also deregisters all command aliases.
func (holder *Manager) DeregisterCommand(commandName string) bool {
if !holder.IsCommandRegistered(commandName) {
return false
}
var command, _ = holder.GetCommand(commandName)
for _, alias := range command.GetAliases() {
holder.deregisterAlias(alias)
}
delete(holder.commands, commandName)
return true
}
// GetCommand returns a command regardless whether it's an alias or the command name, or an error if none was found.
func (holder *Manager) GetCommand(commandName string) (*Command, error) {
var command, err = holder.GetCommandByName(commandName)
if err != nil {
command, err = holder.GetCommandByAlias(commandName)
}
return command, err
}
// GetCommandByAlias returns a command by alias, and an error if none was found.
func (holder *Manager) GetCommandByAlias(aliasName string) (*Command, error) {
if !holder.AliasExists(aliasName) {
return nil, errors.New("command alias " + aliasName + " not found")
}
return holder.aliases[aliasName], nil
}
// GetCommandByName returns a command by name, and an error if none was found.
func (holder *Manager) GetCommandByName(commandName string) (*Command, error) {
var _, exists = holder.commands[commandName]
if !exists {
return nil, errors.New("command " + commandName + " not found")
}
return holder.commands[commandName], nil
}
// RegisterCommand registers a command in the command holder with the including aliases.
func (holder *Manager) RegisterCommand(command *Command) {
holder.commands[command.GetName()] = command
for _, alias := range command.GetAliases() {
holder.registerAlias(alias, command)
}
}
// AliasExists checks if the given alias exists or not.
func (holder *Manager) AliasExists(aliasName string) bool {
var _, exists = holder.aliases[aliasName]
return exists
}
// registerAlias registers a new alias for the given command.
func (holder *Manager) registerAlias(aliasName string, command *Command) {
holder.aliases[aliasName] = command
}
// DeregisterAlias deregisters an alias.
func (holder *Manager) deregisterAlias(aliasName string) {
delete(holder.aliases, aliasName)
}
================================================
FILE: commands/selectors/all_entities.go
================================================
package selectors
type AllEntitiesSelector struct {
*TargetSelector
}
func NewAllEntitiesSelector() *AllEntitiesSelector {
return &AllEntitiesSelector{NewTargetSelector(AllEntities)}
}
================================================
FILE: commands/selectors/all_players.go
================================================
package selectors
type AllPlayersSelector struct {
*TargetSelector
}
func NewAllPlayersSelector() *AllPlayersSelector {
return &AllPlayersSelector{NewTargetSelector(AllPlayers)}
}
================================================
FILE: commands/selectors/nearest_player.go
================================================
package selectors
type NearestPlayerSelector struct {
*TargetSelector
}
func NewNearestPlayerSelector() *NearestPlayerSelector {
return &NearestPlayerSelector{NewTargetSelector(NearestPlayer)}
}
================================================
FILE: commands/selectors/random_player.go
================================================
package selectors
type RandomPlayerSelector struct {
*TargetSelector
}
func NewRandomPlayerSelector() *RandomPlayerSelector {
return &RandomPlayerSelector{NewTargetSelector(RandomPlayer)}
}
================================================
FILE: commands/selectors/selector.go
================================================
package selectors
const (
NearestPlayer = "@p"
RandomPlayer = "@r"
AllPlayers = "@a"
AllEntities = "@e"
Self = "@s"
)
type TargetSelector struct {
variable string
arguments map[string]string
}
func NewTargetSelector(variable string) *TargetSelector {
return &TargetSelector{variable, make(map[string]string)}
}
================================================
FILE: commands/selectors/self.go
================================================
package selectors
type SelfSelector struct {
*TargetSelector
}
func NewSelfSelector() *SelfSelector {
return &SelfSelector{NewTargetSelector(Self)}
}
================================================
FILE: commands/sender.go
================================================
package commands
type Sender interface {
HasPermission(string) bool
SendMessage(...interface{})
}
================================================
FILE: default_commands.go
================================================
package gomine
import (
"github.com/irmine/gomine/commands"
"github.com/irmine/gomine/net"
"github.com/irmine/gomine/text"
"strconv"
)
func NewTest(_ *Server) *commands.Command {
cmd := commands.NewCommand("chunk", "Lists the current chunk", "none", []string{}, func(sender commands.Sender) {
if session, ok := sender.(*net.MinecraftSession); ok {
text.DefaultLogger.Debug(session.GetPlayer().GetChunk().X, session.GetPlayer().GetChunk().Z)
session.SendMessage(session.GetPlayer().GetChunk().X, session.GetPlayer().GetChunk().Z)
}
})
cmd.ExemptFromPermissionCheck(true)
return cmd
}
func NewList(server *Server) *commands.Command {
var list = commands.NewCommand("list", "Lists all players online", "gomine.list", []string{}, func(sender commands.Sender) {
var s = "s"
if len(server.SessionManager.GetSessions()) == 1 {
s = ""
}
var playerList = text.BrightGreen + "-----" + text.White + " Player List (" + strconv.Itoa(len(server.SessionManager.GetSessions())) + " Player" + s + ") " + text.BrightGreen + "-----\n"
for name, player := range server.SessionManager.GetSessions() {
playerList += text.BrightGreen + name + ": " + text.Yellow + text.Bold + strconv.Itoa(int(player.GetPing())) + "ms" + text.Reset + "\n"
}
sender.SendMessage(playerList)
})
list.ExemptFromPermissionCheck(true)
return list
}
func NewPing() *commands.Command {
var ping = commands.NewCommand("ping", "Returns your latency", "gomine.ping", []string{}, func(sender commands.Sender) {
if session, ok := sender.(*net.MinecraftSession); ok {
session.SendMessage(text.Yellow+"Your current latency/ping is:", session.GetPing())
} else {
sender.SendMessage(text.Red + "Please run this command as a player.")
}
})
ping.ExemptFromPermissionCheck(true)
return ping
}
func NewStop(server *Server) *commands.Command {
return commands.NewCommand("stop", "Stops the server", "gomine.stop", []string{"shutdown"}, func() {
for _, session := range server.SessionManager.GetSessions() {
session.Kick("Server Stopped", false, true)
}
server.Shutdown()
})
}
================================================
FILE: items/conversion.go
================================================
package items
import (
"fmt"
"github.com/irmine/gomine/text"
"strconv"
"strings"
)
// IdToState is a map used to convert
// an ID + item data combination to item type.
// The keys of these maps are created using the
// getKey method.
var IdToType = map[string]Type{
GetKey(0, 0): DefaultManager.stringIds["minecraft:air"],
GetKey(1, 0): DefaultManager.stringIds["minecraft:stone"],
}
// TypeToId is a map used to convert
// a block state to an ID + data combination.
var TypeToId = map[string]string{
fmt.Sprint(DefaultManager.stringIds["minecraft:air"]): GetKey(0, 0),
fmt.Sprint(DefaultManager.stringIds["minecraft:stone"]): GetKey(1, 0),
}
// getKey returns the key of an ID + data combination,
// which is used in both maps.
func GetKey(id int16, data int16) string {
return fmt.Sprint(id, ":", data)
}
// FromKey attempts to retrieve an ID + data combination,
// from a string created with getKey.
// Any errors that occur are logged to the default logger.
func FromKey(key string) (int16, int16) {
fragments := strings.Split(key, ":")
idFrag, dataFrag := fragments[0], fragments[1]
i, err := strconv.Atoi(idFrag)
text.DefaultLogger.LogError(err)
d, err := strconv.Atoi(dataFrag)
text.DefaultLogger.LogError(err)
return int16(i), int16(d)
}
================================================
FILE: items/enchantments/enchantable.go
================================================
package enchantments
type Enchantable struct {
}
================================================
FILE: items/enchantments/enchantment.go
================================================
package enchantments
// Type holds the data of the enchantment.
// It is an immutable type, which is used
// to identify an enchantment.
type Type struct {
stringId string
id int16
}
// GetStringId returns the string ID of a type.
// This string ID may be used to identify
// enchantments by user output.
func (t Type) GetStringId() string {
return t.stringId
}
// GetId returns the enchantment ID of a type.
// It is used mainly to identify an enchantment.
func (t Type) GetId() int16 {
return t.id
}
// Instance is an enchantment instance.
// It holds an enchantment type,
// and contains the leftover duration of an
// enchantment, and the value of it.
type Instance struct {
Type
// Level is the enchantment level.
// This value indicates the strength of the
// enchantment.
Level byte
}
================================================
FILE: items/enchantments/enchantment_ids.go
================================================
package enchantments
const (
Protection byte = iota
FireProtection
FeatherFalling
BlastProtection
ProjectileProtection
Thorns
Respiration
DepthStrider
AquaAffinity
Sharpness
Smite
BaneOfArthropods
Knockback
FireAspect
Looting
Efficiency
SilkTouch
Unbreaking
Fortune
Power
Punch
Flame
Infinity
LuckOfTheSea
Lure
FrostWalker
Mending
)
================================================
FILE: items/enchantments/manager.go
================================================
package enchantments
// Manager provides helper functions for managing enchantments,
// such as registering, deregistering and checks for those.
type Manager struct {
// stringIds is a map of enchantment types,
// indexed with the string ID.
// Example: "minecraft:absorption": Type
stringIds map[string]Type
// byteIds is a map of enchantment types,
// indexed with the byte ID.
// Example: 3: Type
byteIds map[byte]Type
}
// DefaultManager is the default enchantment manager.
// The init function registers the default enchantments.
var DefaultManager = NewManager()
// init registers default enchantments of the manager.
func init() {
DefaultManager.RegisterDefaults()
}
// NewManager returns a new enchantment manager.
// Maps are allocated, but no default enchantments
// are registered yet.
func NewManager() *Manager {
return &Manager{make(map[string]Type), make(map[byte]Type)}
}
// RegisterDefaults registers all default enchantments.
// This function should be called whenever a new manager
// is made, in order to have all default enchantments registered.
func (manager *Manager) RegisterDefaults() {
}
================================================
FILE: items/inventory/inventory.go
================================================
package inventory
import (
"errors"
"github.com/irmine/gomine/items"
"strings"
)
// Inventory is a container of item stacks.
// Every inventory has a fixed amount of
// max slots, and the item stack count will
// never exceed these slots.
type Inventory struct {
// items is a slice of item stacks.
// The length of this slice will remain
// fixed for the lifetime of an inventory.
items []*items.Stack
}
// ExceedingSlot gets returned when an slot
// gets given that exceeds the inventory size.
// This may be for GetItem, or SetItem as example.
var ExceedingSlot = errors.New("slot given exceeds the inventory")
// EmptySlot gets returned in GetItem when a slot
// gets given and no item is available in that slot.
var EmptySlot = errors.New("slot given contains no item")
// FullInventory gets returned in AddItem when the
// inventory does not have enough space for the item.
var FullInventory = errors.New("inventory has no space for item")
// NewInventory returns a new inventory with size.
// An item slice gets made with the size,
// which's length will never grow or shrink.
func NewInventory(size int) *Inventory {
return &Inventory{make([]*items.Stack, size)}
}
// IsEmpty checks if a slot in the inventory is empty.
// True gets returned if no item was in the slot.
// True is also returned when the slot exceeds the
// maximum size of the inventory.
func (inventory *Inventory) IsEmpty(slot int) bool {
if slot >= len(inventory.items) {
return true
}
item := inventory.items[slot]
return item == nil
}
// GetItem returns an item in a slot in an inventory.
// If the slot exceeds the max inventory size,
// a nil item gets returned with ExceedingSlot error.
// If there is no item available at that slot,
// a nil item gets returned with EmptySlot.
// If the item was retrieved successfully,
// the item gets returned with no error.
func (inventory *Inventory) GetItem(slot int) (*items.Stack, error) {
if slot >= len(inventory.items) {
return nil, ExceedingSlot
}
item := inventory.items[slot]
if item == nil {
return nil, EmptySlot
}
return item, nil
}
// SetItem sets an item in a slot in an inventory.
// If the slot exceeds the max inventory size,
// a nil item gets returned with ExceedingSlot error,
// otherwise returns nil.
func (inventory *Inventory) SetItem(stack *items.Stack, slot int) error {
if slot >= len(inventory.items) {
return ExceedingSlot
}
inventory.items[slot] = stack
return nil
}
// AddItem adds an item to the inventory.
// FullInventory gets returned if there was
// not sufficient space to fit the item.
// Items are first attempted to be stacked onto
// previously existed stacks, and once all
// pre-existing stacks are filled new stacks
// are created.
func (inventory *Inventory) AddItem(item *items.Stack) error {
for slot, invItem := range inventory.items {
if item.Count == 0 {
return nil
}
if invItem == nil {
continue
}
item.StackOn(invItem)
inventory.SetItem(invItem, slot)
}
for slot, empty := range inventory.items {
if item.Count == 0 {
return nil
}
if empty != nil {
continue
}
n := *item
n.Count = 0
item.StackOn(&n)
inventory.SetItem(&n, slot)
}
if item.Count == 0 {
return nil
}
return FullInventory
}
// RemoveItem removes an item from an inventory.
// A given item gets searched in the inventory,
// removing every equal stack until the count
// of the given stack has been exhausted.
// Items may be removed from multiple stacks.
// A bool gets returned to indicate if the
// complete stack got removed from the inventory.
func (inventory *Inventory) RemoveItem(searched *items.Stack) bool {
count := searched.Count
for slot, item := range inventory.items {
if item == nil {
continue
}
canStack, _ := item.CanStackOn(searched)
if canStack {
if item.Count > count {
item.Count -= count
inventory.SetItem(item, slot)
count = 0
} else {
inventory.ClearSlot(slot)
}
count -= item.Count
if count <= 0 {
return true
}
}
}
return false
}
// ClearSlot clears a given slot in the inventory.
// ClearSlot returns ExceedingSlot if the slot exceeds
// the inventory size, and EmptySlot if the slot was
// already empty before clearing.
func (inventory *Inventory) ClearSlot(slot int) error {
if slot >= len(inventory.items) {
return ExceedingSlot
}
item := inventory.items[slot]
if item == nil {
return EmptySlot
}
inventory.SetItem(nil, slot)
return nil
}
// GetAll returns a copied slice of all item stacks,
// that are currently contained within the inventory.
// Operating on this slice will not operate directly
// on the content of this inventory.
func (inventory *Inventory) GetAll() []*items.Stack {
slice := make([]*items.Stack, len(inventory.items))
copy(slice, inventory.items)
return slice
}
// SetAll sets all items in the inventory.
// This function merely copies the items from
// slice to slice, and does not implement any
// other behaviour. Use SetItem where possible.
func (inventory *Inventory) SetAll(items []*items.Stack) {
copy(inventory.items, items)
}
// Contains checks if the inventory contains an item.
// This function checks through the whole inventory,
// to try and find out the total count of items with
// the same type of the item stack.
// The checked item stack may therefore be split out
// over multiple stacks in the inventory.
func (inventory *Inventory) Contains(searched *items.Stack) bool {
count := searched.Count
for _, item := range inventory.items {
if item == nil {
continue
}
canStack, _ := searched.CanStackOn(item)
if canStack {
count -= item.Count
if count <= 0 {
return true
}
}
}
return false
}
// String returns a string representation of an inventory.
// String implements the fmt.Stringer interface.
func (inventory *Inventory) String() string {
m := make(map[string]string)
for _, item := range inventory.items {
if item == nil {
continue
}
if _, ok := m[item.GetName()]; !ok {
m[item.GetName()] = "- " + item.String()
} else {
m[item.GetName()] += ", " + item.String()
}
}
str := ""
for _, instances := range m {
str += instances + "\n"
}
return "Inventory contents:\n" + strings.TrimRight(str, "\n")
}
================================================
FILE: items/inventory/inventory_test.go
================================================
package inventory
import (
"fmt"
"github.com/irmine/gomine/items"
"testing"
)
func Test(t *testing.T) {
manager := items.NewManager()
manager.Register(items.NewType("minecraft:emerald"), true)
manager.Register(items.NewType("minecraft:glass_bottle"), true)
inv := NewInventory(9)
item, _ := manager.Get("minecraft:emerald", 8)
inv.SetItem(item, 6)
inv.SetItem(item, 4)
item, _ = manager.Get("minecraft:glass_bottle", 34)
inv.SetItem(item, 8)
if err := inv.SetItem(item, 9); err != nil {
fmt.Println("Inventory size check works:", err)
}
fmt.Println(inv)
item, _ = manager.Get("minecraft:emerald", 16)
if inv.Contains(item) {
fmt.Println("Inventory contains 16 emeralds.")
} else {
fmt.Println("Inventory does not contain 16 emeralds.")
}
item, _ = manager.Get("minecraft:glass_bottle", 35)
if inv.Contains(item) {
fmt.Println("Inventory contains 35 glass bottles.")
} else {
fmt.Println("Inventory does not contain 35 glass bottles.")
}
item, _ = manager.Get("minecraft:emerald", 10)
inv.RemoveItem(item)
fmt.Println(inv)
item, _ = manager.Get("minecraft:emerald", 90)
inv.AddItem(item)
fmt.Println(inv)
}
================================================
FILE: items/inventory/io/inventory_action_io.go
================================================
package io
import (
"github.com/irmine/gomine/items"
"github.com/irmine/gomine/net/packets"
)
const (
ContainerSource = iota + 0
WorldSource = 2
//CreativeSource = 3
)
type InventoryActionIO struct {
Source uint32
WindowId int32
SourceFlags uint32
InventorySlot uint32
OldItem *items.Stack
NewItem *items.Stack
}
func NewInventoryActionIO() InventoryActionIO{
return InventoryActionIO{}
}
func (IO *InventoryActionIO) WriteToBuffer(bs *packets.MinecraftStream) {
bs.PutUnsignedVarInt(IO.Source)
switch IO.Source {
case ContainerSource:
bs.PutVarInt(IO.WindowId)
break
case WorldSource:
bs.PutUnsignedVarInt(IO.SourceFlags)
break
}
bs.PutUnsignedVarInt(IO.InventorySlot)
bs.PutItem(IO.OldItem)
bs.PutItem(IO.NewItem)
}
func (IO *InventoryActionIO) ReadFromBuffer(bs *packets.MinecraftStream) InventoryActionIO {
IO.Source = bs.GetUnsignedVarInt()
switch IO.Source {
case ContainerSource:
IO.WindowId = bs.GetVarInt()
break
case WorldSource:
IO.SourceFlags = bs.GetUnsignedVarInt()
break
}
IO.InventorySlot = bs.GetUnsignedVarInt()
IO.OldItem = bs.GetItem()
IO.NewItem = bs.GetItem()
return *IO
}
================================================
FILE: items/inventory/io/inventory_action_io_list.go
================================================
package io
import (
"github.com/irmine/gomine/net/packets"
)
type InventoryActionIOList struct {
List []InventoryActionIO
}
func NewInventoryActionIOList() *InventoryActionIOList{
return &InventoryActionIOList{}
}
func (IOList *InventoryActionIOList) GetCount() int {
return len(IOList.List)
}
func (IOList *InventoryActionIOList) PutAction(io InventoryActionIO) {
IOList.List = append(IOList.List, io)
}
func (IOList *InventoryActionIOList) WriteToBuffer(bs *packets.MinecraftStream) {
c := len(IOList.List)
bs.PutUnsignedVarInt(uint32(c))
for i := 0; i < c; i++ {
IOList.List[i].WriteToBuffer(bs)
}
}
func (IOList *InventoryActionIOList) ReadFromBuffer(bs *packets.MinecraftStream) *InventoryActionIOList{
c := bs.GetUnsignedVarInt()
for i := uint32(0); i < c; i ++{
a := NewInventoryActionIO()
a.ReadFromBuffer(bs)
IOList.PutAction(a)
}
return IOList
}
================================================
FILE: items/item_test.go
================================================
package items
import (
"fmt"
"testing"
)
func Test(t *testing.T) {
manager := NewManager()
manager.RegisterDefaults()
manager.Register(NewType("minecraft:emerald"), true)
emerald, ok := manager.Get("minecraft:emerald", 5)
if !ok {
panic("item not registered")
}
fmt.Println(emerald.name, emerald.Count)
}
================================================
FILE: items/manager.go
================================================
package items
import "github.com/irmine/gonbt"
// Manager supplies helper functions for item type registering.
// Item types get registered by their string ID,
// and can be retrieved using these.
type Manager struct {
// stringIds is a map containing item types,
// indexed by string IDs.
// Example: "minecraft:golden_apple": Type
stringIds map[string]Type
// creativeItems is a map containing item types,
// indexed by string IDs, similarly to stringIds.
// This map contains all items,
// that should be displayed in the creative inventory.
creativeItems map[string]Type
}
// DefaultManager is the default item manager.
// The default items are registered upon the init function.
var DefaultManager = NewManager()
// init initializes all default item types,
// of the default item manager.
func init() {
DefaultManager.RegisterDefaults()
}
// NewManager returns a new item registry.
// New registries will not have default items registered.
// Default registries should be registered using RegisterDefaults.
func NewManager() *Manager {
return &Manager{make(map[string]Type), make(map[string]Type)}
}
// Register registers a new item type.
// The item type will be registered to the stringIds map.
// Registered item types can be deregistered,
// using the Deregister functions.
// If registerCreative is set to true,
// the item will also be registered as creative item.
func (registry *Manager) Register(t Type, registerCreative bool) {
registry.stringIds[t.GetId()] = t
if registerCreative {
registry.RegisterCreativeType(t)
}
}
// RegisterMultiple registers multiple types at once.
// Item types will be registered to the stringIds map,
// and can be deregistered separately from each other.
// If registerCreative is set to true,
// the items will also be registered as creative item.
func (registry *Manager) RegisterMultiple(types []Type, registerCreative bool) {
for _, t := range types {
registry.stringIds[t.GetId()] = t
if registerCreative {
registry.RegisterCreativeType(t)
}
}
}
// RegisterCreativeType registers an item type,
// to the creative items map.
// All creative items will be displayed,
// in the creative inventory.
func (registry *Manager) RegisterCreativeType(t Type) {
registry.creativeItems[t.GetId()] = t
}
// IsCreativeTypeRegistered checks if an item type
// is registered to the creative inventory map.
func (registry *Manager) IsCreativeTypeRegistered(stringId string) bool {
_, ok := registry.creativeItems[stringId]
return ok
}
// DeregisterCreativeType deregisters a creative item.
// Creative items can be deregistered using the string ID
// of that particular item.
// A bool gets returned to indicate success of the action.
func (registry *Manager) DeregisterCreativeType(stringId string) bool {
_, ok := registry.creativeItems[stringId]
delete(registry.creativeItems, stringId)
return ok
}
// GetCreativeTypes returns all creative items.
// A map gets returned in the form of stringId => Type.
func (registry *Manager) GetCreativeTypes() map[string]Type {
return registry.creativeItems
}
// IsRegistered checks if an item type is registered,
// by its string ID in the stringIds map.
// Returns true if the string ID is registered.
func (registry *Manager) IsRegistered(stringId string) bool {
_, ok := registry.stringIds[stringId]
return ok
}
// Deregister deregisters an item type,
// by its string ID in the stringIds map.
// Returns true if the item type was deregistered successfully.
func (registry *Manager) Deregister(stringId string) bool {
_, ok := registry.stringIds[stringId]
if !ok {
return false
}
delete(registry.stringIds, stringId)
return true
}
// Get attempts to return a new item stack by a string ID,
// and sets the stack's count to the count given.
// A bool gets returned to indicate whether any item was found.
// If no item type could be found with the given string ID,
// a default air item and a bool false gets returned.
func (registry *Manager) Get(stringId string, count int) (*Stack, bool) {
t, ok := registry.stringIds[stringId]
if !ok {
t = registry.stringIds["minecraft:air"]
}
return &Stack{Type: t, Count: count, DisplayName: t.name, cachedNBT: gonbt.NewCompound("", make(map[string]gonbt.INamedTag))}, ok
}
// GetTypes returns all registered item types.
// Item types are returned in a map of the form stringId => Type.
func (registry *Manager) GetTypes() map[string]Type {
return registry.stringIds
}
// RegisterDefaults registers all default items.
// This function should be called immediately after NewManager,
// in order to register the proper default items.
func (registry *Manager) RegisterDefaults() {
registry.Register(NewType("minecraft:air"), false)
registry.Register(NewType("minecraft:stone"), true)
}
================================================
FILE: items/nbt_tag_names.go
================================================
package items
const (
Display = "display"
DisplayName = "Name"
DisplayLore = "Lore"
Ench = "ench"
EnchId = "id"
EnchLevel = "lvl"
)
================================================
FILE: items/stack.go
================================================
package items
import (
"fmt"
"github.com/irmine/gomine/items/enchantments"
"github.com/irmine/gonbt"
)
// Stack is an instance of a given amount of items.
// A stack may also be referred to as an item instance.
// A stack holds additional information about an item,
// that could differ on an every item base.
type Stack struct {
// Stack embeds Type. Therefore functions
// in the Type struct may also be used in Stack.
Type
// Count is the current count of an item.
// The count of an item is usually 16/64.
Count int
// Durability is the current left durability of the stack.
// Durability on non-breakable item types has no effect.
Durability int16
// DisplayName is the display name of an item.
// If a non-empty display name has been set,
// this name will be displayed,
// rather than the original Type name.
DisplayName string
// Lore is the displayed lore of an item.
// The lore is displayed under the item,
// when hovering over it in the inventory.
Lore []string
// enchantments is a map of enchantment instances,
// that are applied on this item.
// The map is indexed by the enchantment IDs.
enchantments map[string]enchantments.Instance
// additionalData is raw additional data of an item stack.
// The additionalData may not be directly used by plugins,
// but should rather be modified by encapsulating items.
additionalData interface{}
// cachedNBT is an NBT compound which gets set when parsing NBT.
// This cached NBT is used to ensure no NBT gets lost while parsing,
// and forms the base for NBT that gets emitted by the type.
cachedNBT *gonbt.Compound
}
// GetDisplayName returns the displayed name of an item.
// The custom name of the item always gets returned,
// unless the custom name is empty; Then the actual
// item type name gets returned.
func (stack Stack) GetDisplayName() string {
if stack.DisplayName == "" {
return stack.name
}
return stack.DisplayName
}
// String returns a string representation of a stack.
// It implements fmt.Stringer, and returns a string as such:
// x29 Emerald (minecraft:emerald)
func (stack Stack) String() string {
return fmt.Sprint("x", stack.Count, stack.Type)
}
// CanStackWith checks if two stacks can stack with each other.
// A bool is returned which indicates if the two can stack,
// and an integer is returned which specifies the count of
// of the item that can still be stacked on this stack.
// The returned integer may be 0, if the stack is already
// at the max stack size.
func (stack Stack) CanStackOn(stack2 *Stack) (bool, int) {
if !stack.Type.Equals(stack2.Type) || stack.DisplayName != stack2.DisplayName || !stack.EqualsEnchantments(stack2) || !stack.EqualsLore(stack2) {
return false, 0
}
count := stack2.maxStackSize - stack2.Count
countLeft := stack.Count
if countLeft < count {
count = countLeft
}
return true, count
}
// StackOn attempts to stack a stack on another stack.
// A first bool is returned which indicates if the two stacked
// successfully. A second bool is returned which is true as long as
// the item stack is not at count 0.
// An integer is returned to specify the count of items that got stacked
// on the other stack. The integer returned may be 0, which happens if the
// other stack is already at max stack size.
func (stack *Stack) StackOn(stack2 *Stack) (success bool, notZero bool, stackCount int) {
canStack, count := stack.CanStackOn(stack2)
countLeft := stack.Count != 0
if !canStack {
return false, countLeft, count
}
stack2.Count += count
stack.Count -= count
return true, countLeft, count
}
// Equals checks if two item stacks are considered equal.
// Equals checks if the item type is equal and if the count is equal.
// For more deep checks, EqualsExact should be used.
func (stack Stack) Equals(stack2 *Stack) bool {
return stack.Type.Equals(stack2.Type) && stack2.Count == stack.Count && stack.Durability == stack2.Durability && stack.DisplayName == stack2.DisplayName
}
// EqualsExact checks if two item stacks are considered exact equal.
// EqualsExact does all the checks Equals does,
// and checks if the lore and enchantments are equal.
func (stack Stack) EqualsExact(stack2 *Stack) bool {
return stack.Equals(stack2) && stack.EqualsLore(stack2) && stack.EqualsEnchantments(stack2)
}
// EqualsLore checks if the lore of two item
// stacks are equal to each other.
func (stack Stack) EqualsLore(stack2 *Stack) bool {
if len(stack.Lore) != len(stack2.Lore) {
return false
}
for key, val := range stack.Lore {
if stack2.Lore[key] != val {
return false
}
}
return true
}
// EqualsEnchantments checks if enchantments of two
// item stacks are equal to each other.
func (stack Stack) EqualsEnchantments(stack2 *Stack) bool {
if len(stack.enchantments) != len(stack2.enchantments) {
return false
}
for key, val := range stack.enchantments {
if stack2.enchantments[key] != val {
return false
}
}
return true
}
================================================
FILE: items/type.go
================================================
package items
import (
"fmt"
"github.com/irmine/gonbt"
"strings"
)
// Type is the type that identifies an item.
// Types contain a string ID,
// which can be used to construct a new item stack.
type Type struct {
// NBTParseFunction gets called once NBT is attempted
// to be decoded for an item. The compound passed is the
// compound the NBT data should be coming out of, and the stack
// passed is the stack that encapsulates this type.
NBTParseFunction func(compound *gonbt.Compound, stack *Stack)
// NBTEmitFunction gets called once NBT is attempted
// to be obtained from an item. The compound passed is the
// compound the NBT data should be going into, and the stack
// passed is the stack that encapsulates this type.
NBTEmitFunction func(compound *gonbt.Compound, stack *Stack)
// name is the name of the item type.
// This name is merely a modification of the string ID.
name string
// stringId is the identifier of the item type.
// This string ID is always used, rather than numeric IDs.
stringId string
// breakable defines if the item is breakable.
// Breakable items will have decrementing durability.
breakable bool
// maxStackSize is the maximum size of a stack of this item.
// Item stacks itself are not limited, but the stack size
// of occurrences in an inventory of the item are.
maxStackSize int
}
// NewType returns a new non-breakable type.
// The given string ID is used as identifier,
// and all properties are immune in the type.
// Type names prefixed with `minecraft:` get their
// name set to it without the prefix.
// Types get the default NBT parsing and emitting functions.
func NewType(stringId string) Type {
fragments := strings.Split(stringId[10:], "_")
name := ""
for _, frag := range fragments {
name += strings.Title(frag) + " "
}
return Type{ParseNBT, EmitNBT, strings.TrimRight(name, " "), stringId, false, 64}
}
// NewType returns a new breakable type.
// The given string ID is used as identifier,
// and all properties are immune in the type.
// Type names prefixed with `minecraft:` get their
// name set to it without the prefix.
// Types get the default NBT parsing and emitting functions.
func NewBreakable(stringId string) Type {
t := NewType(stringId)
t.breakable = true
return t
}
// GetName returns the readable name of an item type.
// This name may contains spaces.
func (t Type) GetName() string {
return t.name
}
// GetId returns the string ID of an item type.
// StringIds are a string used as an identifier,
// in order to lookup items by it.
func (t Type) GetId() string {
return t.stringId
}
// IsBreakable checks if an item is breakable.
// Breakable items use data fields for durability,
// but we separate them for forward compatibility sake.
func (t Type) IsBreakable() bool {
return t.breakable
}
// GetMaximumStackSize returns the maximum stack size of an item.
// Item stacks of the type are not limited to this size themselves,
// but are when set into an inventory.
func (t Type) GetMaximumStackSize() int {
return t.maxStackSize
}
// String returns a string representation of a type.
// It implements fmt.Stringer, and returns a string as such:
// Emerald(minecraft:emerald)
func (t Type) String() string {
return fmt.Sprint(t.name, "(", t.stringId, ")")
}
// GetAuxValue returns the aux value for the item stack with item data.
// This aux value is used for writing stacks over network.
func (t Type) GetAuxValue(stack *Stack, data int16) int32 {
if t.IsBreakable() {
data = stack.Durability
}
return int32(((data & 0x7fff) << 8) | int16(stack.Count))
}
// Equals checks if two item types are considered equal.
// Item types are merely checked against each other's
// string IDs, but should not require more comparisons.
func (t Type) Equals(t2 Type) bool {
return t.stringId == t2.stringId
}
// ParseNBT implements default behaviour for parsing NBT.
// This is the default function passed in for `NBTParseFunction`.
// The cached NBT gets set when parsing NBT.
func ParseNBT(compound *gonbt.Compound, stack *Stack) {
if compound.HasTagWithType(Display, gonbt.TAG_Compound) {
stack.DisplayName = compound.GetCompound(Display).GetString(DisplayName, stack.name)
for _, tag := range compound.GetCompound(Display).GetList(DisplayLore, gonbt.TAG_String).GetTags() {
stack.Lore = append(stack.Lore, tag.Interface().(string))
}
}
stack.cachedNBT = compound
}
// EmitNBT implements default behaviour for emitting NBT.
// This is the default function passed in for `NBTEmitFunction`.
// The compound first gets set to the cached compound of the item type.
func EmitNBT(compound *gonbt.Compound, stack *Stack) {
compound = stack.cachedNBT
compound.SetCompound(Display, make(map[string]gonbt.INamedTag))
if stack.DisplayName != "" {
compound.GetCompound(Display).SetString(DisplayName, stack.DisplayName)
var list []gonbt.INamedTag
for _, lore := range stack.Lore {
list = append(list, gonbt.NewString("", lore))
}
compound.GetCompound(Display).SetList(DisplayLore, gonbt.TAG_String, list)
}
}
================================================
FILE: net/info/info.go
================================================
package info
const (
LatestProtocol = 332
LatestGameVersion = "v1.9.0"
LatestGameVersionNetwork = "1.9.0"
)
type PacketIdList map[PacketName]int
type PacketName string
const (
LoginPacket PacketName = "LoginPacket"
PlayStatusPacket PacketName = "PlayStatusPacket"
ServerHandshakePacket PacketName = "ServerHandshakePacket"
ClientHandshakePacket PacketName = "ClientHandshakePacket"
DisconnectPacket PacketName = "DisconnectPacket"
ResourcePackInfoPacket PacketName = "ResourcePackInfoPacket"
ResourcePackStackPacket PacketName = "ResourcePackStackPacket"
ResourcePackClientResponsePacket PacketName = "ResourcePackClientResponsePacket"
TextPacket PacketName = "TextPacket"
SetTimePacket PacketName = "SetTimePacket"
StartGamePacket PacketName = "StartGamePacket"
AddPlayerPacket PacketName = "AddPlayerPacket"
AddEntityPacket PacketName = "AddEntityPacket"
RemoveEntityPacket PacketName = "RemoveEntityPacket"
AddItemEntityPacket PacketName = "AddItemEntityPacket"
AddHangingEntityPacket PacketName = "AddHangingEntityPacket"
TakeItemEntityPacket PacketName = "TakeItemEntityPacket"
MoveEntityPacket PacketName = "MoveEntityPacket"
MovePlayerPacket PacketName = "MovePlayerPacket"
RiderJumpPacket PacketName = "RiderJumpPacket"
UpdateBlockPacket PacketName = "UpdateBlockPacket"
AddPaintingPacket PacketName = "AddPaintingPacket"
ExplodePacket PacketName = "ExplodePacket"
LevelSoundEventPacket PacketName = "LevelSoundEventPacket"
LevelEventPacket PacketName = "LevelEventPacket"
BlockEventPacket PacketName = "BlockEventPacket"
EntityEventPacket PacketName = "EntityEventPacket"
MobEffectPacket PacketName = "MobEffectPacket"
UpdateAttributesPacket PacketName = "UpdateAttributesPacket"
InventoryTransactionPacket PacketName = "InventoryTransactionPacket"
MobEquipmentPacket PacketName = "MobEquipmentPacket"
MobArmorEquipmentPacket PacketName = "MobArmorEquipmentPacket"
InteractPacket PacketName = "InteractPacket"
BlockPickRequestPacket PacketName = "BlockPickRequestPacket"
EntityPickRequestPacket PacketName = "EntityPickRequestPacket"
PlayerActionPacket PacketName = "PlayerActionPacket"
EntityFallPacket PacketName = "EntityFallPacket"
HurtArmorPacket PacketName = "HurtArmorPacket"
SetEntityDataPacket PacketName = "SetEntityDataPacket"
SetEntityMotionPacket PacketName = "SetEntityMotionPacket"
SetEntityLinkPacket PacketName = "SetEntityLinkPacket"
SetHealthPacket PacketName = "SetHealthPacket"
SetSpawnPositionPacket PacketName = "SetSpawnPositionPacket"
AnimatePacket PacketName = "AnimatePacket"
RespawnPacket PacketName = "RespawnPacket"
ContainerOpenPacket PacketName = "ContainerOpenPacket"
ContainerClosePacket PacketName = "ContainerClosePacket"
PlayerHotbarPacket PacketName = "PlayerHotbarPacket"
InventoryContentPacket PacketName = "InventoryContentPacket"
InventorySlotPacket PacketName = "InventorySlotPacket"
ContainerSetDataPacket PacketName = "ContainerSetDataPacket"
CraftingDataPacket PacketName = "CraftingDataPacket"
CraftingEventPacket PacketName = "CraftingEventPacket"
GuiDataPickItemPacket PacketName = "GuiDataPickItemPacket"
AdventureSettingsPacket PacketName = "AdventureSettingsPacket"
BlockEntityDataPacket PacketName = "BlockEntityDataPacket"
PlayerInputPacket PacketName = "PlayerInputPacket"
FullChunkDataPacket PacketName = "FullChunkDataPacket"
SetCommandsEnabledPacket PacketName = "SetCommandsEnabledPacket"
SetDifficultyPacket PacketName = "SetDifficultyPacket"
ChangeDimensionPacket PacketName = "ChangeDimensionPacket"
SetPlayerGameTypePacket PacketName = "SetPlayerGameTypePacket"
PlayerListPacket PacketName = "PlayerListPacket"
SimpleEventPacket PacketName = "SimpleEventPacket"
EventPacket PacketName = "EventPacket"
SpawnExperienceOrbPacket PacketName = "SpawnExperienceOrbPacket"
ClientboundMapItemDataPacket PacketName = "ClientboundMapItemDataPacket"
MapInfoRequestPacket PacketName = "MapInfoRequestPacket"
RequestChunkRadiusPacket PacketName = "RequestChunkRadiusPacket"
ChunkRadiusUpdatedPacket PacketName = "ChunkRadiusUpdatedPacket"
ItemFrameDropItemPacket PacketName = "ItemFrameDropItemPacket"
GameRulesChangedPacket PacketName = "GameRulesChangedPacket"
CameraPacket PacketName = "CameraPacket"
BossEventPacket PacketName = "BossEventPacket"
ShowCreditsPacket PacketName = "ShowCreditsPacket"
AvailableCommandsPacket PacketName = "AvailableCommandsPacket"
CommandRequestPacket PacketName = "CommandRequestPacket"
CommandBlockUpdatePacket PacketName = "CommandBlockUpdatePacket"
CommandOutputPacket PacketName = "CommandOutputPacket"
UpdateTradePacket PacketName = "UpdateTradePacket"
UpdateEquipPacket PacketName = "UpdateEquipPacket"
ResourcePackDataInfoPacket PacketName = "ResourcePackDataInfoPacket"
ResourcePackChunkDataPacket PacketName = "ResourcePackChunkDataPacket"
ResourcePackChunkRequestPacket PacketName = "ResourcePackChunkRequestPacket"
TransferPacket PacketName = "TransferPacket"
PlaySoundPacket PacketName = "PlaySoundPacket"
StopSoundPacket PacketName = "StopSoundPacket"
SetTitlePacket PacketName = "SetTitlePacket"
AddBehaviorTreePacket PacketName = "AddBehaviorTreePacket"
StructureBlockUpdatePacket PacketName = "StructureBlockUpdatePacket"
ShowStoreOfferPacket PacketName = "ShowStoreOfferPacket"
PurchaseReceiptPacket PacketName = "PurchaseReceiptPacket"
PlayerSkinPacket PacketName = "PlayerSkinPacket"
SubClientLoginPacket PacketName = "SubClientLoginPacket"
WSConnectPacket PacketName = "WSConnectPacket"
SetLastHurtByPacket PacketName = "SetLastHurtByPacket"
BookEditPacket PacketName = "BookEditPacket"
NpcRequestPacket PacketName = "NpcRequestPacket"
PhotoTransferPacket PacketName = "PhotoTransferPacket"
ModalFormRequestPacket PacketName = "ModalFormRequestPacket"
ModalFormResponsePacket PacketName = "ModalFormResponsePacket"
ServerSettingsRequestPacket PacketName = "ServerSettingsRequestPacket"
ServerSettingsResponsePacket PacketName = "ServerSettingsResponsePacket"
ShowProfilePacket PacketName = "ShowProfilePacket"
SetDefaultGameTypePacket PacketName = "SetDefaultGameTypePacket"
NetworkChunkPublisherUpdatePacket PacketName = "NetworkChunkPublisherUpdatePacket"
)
================================================
FILE: net/info/protocol_ids.go
================================================
package info
var PacketIds = PacketIdList{
LoginPacket: 0x01,
PlayStatusPacket: 0x02,
ServerHandshakePacket: 0x03,
ClientHandshakePacket: 0x04,
DisconnectPacket: 0x05,
ResourcePackInfoPacket: 0x06,
ResourcePackStackPacket: 0x07,
ResourcePackClientResponsePacket: 0x08,
TextPacket: 0x09,
SetTimePacket: 0x0a,
StartGamePacket: 0x0b,
AddPlayerPacket: 0x0c,
AddEntityPacket: 0x0d,
RemoveEntityPacket: 0x0e,
AddItemEntityPacket: 0x0f,
AddHangingEntityPacket: 0x10,
TakeItemEntityPacket: 0x11,
MoveEntityPacket: 0x12,
MovePlayerPacket: 0x13,
RiderJumpPacket: 0x14,
UpdateBlockPacket: 0x15,
AddPaintingPacket: 0x16,
ExplodePacket: 0x17,
LevelSoundEventPacket: 0x18,
LevelEventPacket: 0x19,
BlockEventPacket: 0x1a,
EntityEventPacket: 0x1b,
MobEffectPacket: 0x1c,
UpdateAttributesPacket: 0x1d,
InventoryTransactionPacket: 0x1e,
MobEquipmentPacket: 0x1f,
MobArmorEquipmentPacket: 0x20,
InteractPacket: 0x21,
BlockPickRequestPacket: 0x22,
EntityPickRequestPacket: 0x23,
PlayerActionPacket: 0x24,
EntityFallPacket: 0x25,
HurtArmorPacket: 0x26,
SetEntityDataPacket: 0x27,
SetEntityMotionPacket: 0x28,
SetEntityLinkPacket: 0x29,
SetHealthPacket: 0x2a,
SetSpawnPositionPacket: 0x2b,
AnimatePacket: 0x2c,
RespawnPacket: 0x2d,
ContainerOpenPacket: 0x2e,
ContainerClosePacket: 0x2f,
PlayerHotbarPacket: 0x30,
InventoryContentPacket: 0x31,
InventorySlotPacket: 0x32,
ContainerSetDataPacket: 0x33,
CraftingDataPacket: 0x34,
CraftingEventPacket: 0x35,
GuiDataPickItemPacket: 0x36,
AdventureSettingsPacket: 0x37,
BlockEntityDataPacket: 0x38,
PlayerInputPacket: 0x39,
FullChunkDataPacket: 0x3a,
SetCommandsEnabledPacket: 0x3b,
SetDifficultyPacket: 0x3c,
ChangeDimensionPacket: 0x3d,
SetPlayerGameTypePacket: 0x3e,
PlayerListPacket: 0x3f,
SimpleEventPacket: 0x40,
EventPacket: 0x41,
SpawnExperienceOrbPacket: 0x42,
ClientboundMapItemDataPacket: 0x43,
MapInfoRequestPacket: 0x44,
RequestChunkRadiusPacket: 0x45,
ChunkRadiusUpdatedPacket: 0x46,
ItemFrameDropItemPacket: 0x47,
GameRulesChangedPacket: 0x48,
CameraPacket: 0x49,
BossEventPacket: 0x4a,
ShowCreditsPacket: 0x4b,
AvailableCommandsPacket: 0x4c,
CommandRequestPacket: 0x4d,
CommandBlockUpdatePacket: 0x4e,
CommandOutputPacket: 0x4f,
UpdateTradePacket: 0x50,
UpdateEquipPacket: 0x51,
ResourcePackDataInfoPacket: 0x52,
ResourcePackChunkDataPacket: 0x53,
ResourcePackChunkRequestPacket: 0x54,
TransferPacket: 0x55,
PlaySoundPacket: 0x56,
StopSoundPacket: 0x57,
SetTitlePacket: 0x58,
AddBehaviorTreePacket: 0x59,
StructureBlockUpdatePacket: 0x5a,
ShowStoreOfferPacket: 0x5b,
PurchaseReceiptPacket: 0x5c,
PlayerSkinPacket: 0x5d,
SubClientLoginPacket: 0x5e,
WSConnectPacket: 0x5f,
SetLastHurtByPacket: 0x60,
BookEditPacket: 0x61,
NpcRequestPacket: 0x62,
PhotoTransferPacket: 0x63,
ModalFormRequestPacket: 0x64,
ModalFormResponsePacket: 0x65,
ServerSettingsRequestPacket: 0x66,
ServerSettingsResponsePacket: 0x67,
ShowProfilePacket: 0x68,
SetDefaultGameTypePacket: 0x69,
NetworkChunkPublisherUpdatePacket: 0x79,
}
================================================
FILE: net/manager.go
================================================
package net
import (
"fmt"
"github.com/google/uuid"
"github.com/irmine/goraklib/server"
"sync"
)
// SessionManager is a struct managing Minecraft sessions.
// A session manager holds multiple maps used to find sessions by given keys.
type SessionManager struct {
mutex sync.RWMutex
nameMap map[string]*MinecraftSession
uuidMap map[uuid.UUID]*MinecraftSession
xuidMap map[string]*MinecraftSession
sessionMap map[string]*MinecraftSession
}
// NewSessionManager returns a new session manager.
func NewSessionManager() *SessionManager {
return &SessionManager{sync.RWMutex{}, make(map[string]*MinecraftSession), make(map[uuid.UUID]*MinecraftSession), make(map[string]*MinecraftSession), make(map[string]*MinecraftSession)}
}
// GetSessions returns the name => session map of the manager.
func (manager *SessionManager) GetSessions() map[string]*MinecraftSession {
return manager.nameMap
}
// AddMinecraftSession adds the given Minecraft session to the manager.
func (manager *SessionManager) AddMinecraftSession(session *MinecraftSession) {
manager.mutex.Lock()
manager.nameMap[session.GetName()] = session
manager.uuidMap[session.GetUUID()] = session
manager.xuidMap[session.GetXUID()] = session
manager.sessionMap[fmt.Sprint(session.GetSession())] = session
manager.mutex.Unlock()
}
// RemoveMinecraftSession removes a Minecraft session from the manager.
func (manager *SessionManager) RemoveMinecraftSession(session *MinecraftSession) {
if session != nil {
manager.mutex.Lock()
delete(manager.nameMap, session.GetPlayer().GetName())
delete(manager.uuidMap, session.GetUUID())
delete(manager.xuidMap, session.GetXUID())
delete(manager.sessionMap, fmt.Sprint(session.GetSession()))
manager.mutex.Unlock()
}
}
// GetSessionCount returns the session count of the manager.
func (manager *SessionManager) GetSessionCount() int {
return len(manager.nameMap)
}
// HasSession checks if the session manager has a session with the given name.
func (manager *SessionManager) HasSession(name string) bool {
manager.mutex.RLock()
var _, ok = manager.nameMap[name]
manager.mutex.RUnlock()
return ok
}
// GetSession attempts to retrieve a session by its name.
// A bool is returned indicating success.
func (manager *SessionManager) GetSession(name string) (*MinecraftSession, bool) {
manager.mutex.RLock()
var session, ok = manager.nameMap[name]
manager.mutex.RUnlock()
return session, ok
}
// HasSessionWithRakNetSession checks if the session manager has a session with the given RakNet session.
func (manager *SessionManager) HasSessionWithRakNetSession(rakNetSession *server.Session) bool {
manager.mutex.RLock()
var _, ok = manager.sessionMap[fmt.Sprint(rakNetSession)]
manager.mutex.RUnlock()
return ok
}
// GetSessionByRakNetSession attempts to retrieve a session by its RakNet session.
// A bool is returned indicating success.
func (manager *SessionManager) GetSessionByRakNetSession(rakNetSession *server.Session) (*MinecraftSession, bool) {
manager.mutex.RLock()
var session, ok = manager.sessionMap[fmt.Sprint(rakNetSession)]
manager.mutex.RUnlock()
return session, ok
}
// HasSessionWithXUID checks if the session manager has a session with the given XUID.
func (manager *SessionManager) HasSessionWithXUID(xuid string) bool {
manager.mutex.RLock()
var _, ok = manager.xuidMap[xuid]
manager.mutex.RUnlock()
return ok
}
// GetSessionByXUID attempts to retrieve a session by its XUID.
// A bool is returned indicating success.
func (manager *SessionManager) GetSessionByXUID(xuid string) (*MinecraftSession, bool) {
manager.mutex.RLock()
var session, ok = manager.xuidMap[xuid]
manager.mutex.RUnlock()
return session, ok
}
// HasSessionWithUUID checks if the session manager has a session with the given UUID.
func (manager *SessionManager) HasSessionWithUUID(uuid uuid.UUID) bool {
manager.mutex.RLock()
var _, ok = manager.uuidMap[uuid]
manager.mutex.RUnlock()
return ok
}
// GetSessionByUUID attempts to retrieve a session by its UUID.
// A bool is returned indicating success.
func (manager *SessionManager) GetSessionByUUID(uuid uuid.UUID) (*MinecraftSession, bool) {
manager.mutex.RLock()
var session, ok = manager.uuidMap[uuid]
manager.mutex.RUnlock()
return session, ok
}
================================================
FILE: net/minecraft_packet_batch.go
================================================
package net
import (
"bytes"
"compress/zlib"
"crypto/cipher"
"encoding/hex"
"errors"
"io/ioutil"
"github.com/irmine/binutils"
"github.com/irmine/gomine/net/packets"
"github.com/irmine/gomine/text"
)
const McpeFlag = 0xFE
type MinecraftPacketBatch struct {
*binutils.Stream
raw []byte
packets []packets.IPacket
session *MinecraftSession
needsEncryption bool
}
// NewMinecraftPacketBatch returns a new Minecraft Packet Batch used to decode/encode batches from Encapsulated Packets.
func NewMinecraftPacketBatch(session *MinecraftSession) *MinecraftPacketBatch {
var batch = &MinecraftPacketBatch{}
batch.Stream = binutils.NewStream()
batch.session = session
if session == nil {
batch.needsEncryption = false
} else {
batch.needsEncryption = session.UsesEncryption()
}
return batch
}
// Decode decodes the batch and separates packets. This does not decode the packets.
func (batch *MinecraftPacketBatch) Decode() {
defer func() {
if err := recover(); err != nil {
text.DefaultLogger.Debug(err)
}
}()
var mcpeFlag = batch.GetByte()
if mcpeFlag != McpeFlag {
return
}
batch.raw = batch.Buffer[batch.Offset:]
if batch.needsEncryption {
batch.decrypt()
}
var err = batch.decompress()
if err != nil {
text.DefaultLogger.LogError(err)
return
}
batch.ResetStream()
batch.SetBuffer(batch.raw)
var packetData [][]byte
for !batch.Feof() {
packetData = append(packetData, batch.GetLengthPrefixedBytes())
}
batch.fetchPackets(packetData)
}
// Encode encodes all packets in the batch and zlib encodes them.
func (batch *MinecraftPacketBatch) Encode() {
batch.ResetStream()
batch.PutByte(McpeFlag)
var stream = binutils.NewStream()
batch.putPackets(stream)
var zlibData = batch.compress(stream)
var data = zlibData
if batch.needsEncryption {
data = batch.encrypt(data)
}
batch.PutBytes(data)
}
// fetchPackets fetches all packets from the raw packet buffers.
func (batch *MinecraftPacketBatch) fetchPackets(packetData [][]byte) {
for _, data := range packetData {
if len(data) == 0 {
continue
}
packetId := int(data[0])
if !batch.session.adapter.packetManager.IsPacketRegistered(packetId) {
text.DefaultLogger.Debug("Unknown Minecraft packet with ID:", packetId)
continue
}
packet := batch.session.adapter.packetManager.GetPacket(packetId)
packet.SetBuffer(data)
batch.packets = append(batch.packets, packet)
}
}
// peekProtocol peeks in the packet's payload, looking for the bedrock.
func (batch *MinecraftPacketBatch) peekProtocol(packetData []byte) int32 {
if packetData[0] != 0x01 {
return 0
}
var protocolBytes = packetData[1:5]
var offset = 0
var protocol = binutils.ReadInt(&protocolBytes, &offset)
if protocol == 0 {
offset = 0
protocolBytes = packetData[3:7]
protocol = binutils.ReadInt(&protocolBytes, &offset)
}
return protocol
}
// encrypt encrypts the data passed to the function.
func (batch *MinecraftPacketBatch) encrypt(d []byte) []byte {
var data = batch.session.GetEncryptionHandler().Data
d = append(d, batch.session.GetEncryptionHandler().ComputeSendChecksum(d)...)
for i := range d {
var cfb = cipher.NewCFBEncrypter(data.EncryptCipher, data.EncryptIV)
cfb.XORKeyStream(d[i:i+1], d[i:i+1])
data.EncryptIV = append(data.EncryptIV[1:], d[i])
}
return d
}
// decrypt decrypts the buffer of the packet.
func (batch *MinecraftPacketBatch) decrypt() {
var data = batch.session.GetEncryptionHandler().Data
for i, b := range batch.raw {
var cfb = cipher.NewCFBDecrypter(data.DecryptCipher, data.DecryptIV)
cfb.XORKeyStream(batch.raw[i:i+1], batch.raw[i:i+1])
data.DecryptIV = append(data.DecryptIV[1:], b)
}
}
// putPackets puts all packets of the batch inside of the stream.
func (batch *MinecraftPacketBatch) putPackets(stream *binutils.Stream) {
for _, packet := range batch.GetPackets() {
packet.EncodeHeader()
packet.Encode()
stream.PutLengthPrefixedBytes(packet.GetBuffer())
}
}
// compress zlib compresses the data in the stream and returns it.
func (batch *MinecraftPacketBatch) compress(stream *binutils.Stream) []byte {
var buff = bytes.Buffer{}
var writer = zlib.NewWriter(&buff)
writer.Write(stream.Buffer)
writer.Close()
return buff.Bytes()
}
// decompress decompresses the zlib compressed buffer.
func (batch *MinecraftPacketBatch) decompress() error {
var reader = bytes.NewReader(batch.raw)
zlibReader, err := zlib.NewReader(reader)
text.DefaultLogger.LogError(err)
if err != nil {
text.DefaultLogger.Debug(hex.EncodeToString(batch.raw))
return err
}
if zlibReader == nil {
return errors.New("an error occurred when decompressing zlib")
}
zlibReader.Close()
batch.raw, err = ioutil.ReadAll(zlibReader)
return err
}
// AddPacket adds a packet to the batch when encoding.
func (batch *MinecraftPacketBatch) AddPacket(packet packets.IPacket) {
batch.packets = append(batch.packets, packet)
}
// GetPackets returns all packets inside of the batch.
// This only returns correctly when done after decoding, or before encoding.
func (batch *MinecraftPacketBatch) GetPackets() []packets.IPacket {
return batch.packets
}
================================================
FILE: net/minecraft_session.go
================================================
package net
import (
"fmt"
"github.com/google/uuid"
"github.com/irmine/gomine/net/packets"
"github.com/irmine/gomine/net/packets/types"
"github.com/irmine/gomine/permissions"
"github.com/irmine/gomine/players"
"github.com/irmine/gomine/text"
"github.com/irmine/gomine/utils"
"github.com/irmine/goraklib/protocol"
"github.com/irmine/goraklib/server"
"github.com/irmine/worlds"
"github.com/irmine/worlds/blocks"
"github.com/irmine/worlds/chunks"
"math"
"strings"
)
type MinecraftSession struct {
adapter *NetworkAdapter
session *server.Session
player *players.Player
uuid uuid.UUID
xuid string
clientId int
protocolNumber int32
minecraftVersion string
language string
clientPlatform int32
encryptionHandler *utils.EncryptionHandler
usesEncryption bool
xboxLiveAuthenticated bool
viewDistance int32
chunkLoader *worlds.Loader
permissions map[string]*permissions.Permission
permissionGroup *permissions.Group
Connected bool
}
// NewMinecraftSession returns a new Minecraft session with the given RakNet session.
func NewMinecraftSession(adapter *NetworkAdapter, session *server.Session) *MinecraftSession {
return &MinecraftSession{adapter, session, nil, uuid.New(), "", 0, 0, "", "", 0, utils.NewEncryptionHandler(), false, false, 0, nil, nil, nil, false}
}
// SetData sets the basic session data of the Minecraft Session
func (session *MinecraftSession) SetData(permissionManager *permissions.Manager, data types.SessionData) {
session.permissions = make(map[string]*permissions.Permission)
session.permissionGroup = permissionManager.GetDefaultGroup()
session.uuid = data.ClientUUID
session.xuid = data.ClientXUID
session.clientId = data.ClientId
session.protocolNumber = data.ProtocolNumber
session.minecraftVersion = data.GameVersion
session.language = data.Language
session.clientPlatform = int32(data.DeviceOS)
session.chunkLoader = worlds.NewLoader(nil, 0, 0)
session.chunkLoader.PublisherUpdateFunction = func() {
var vector = session.player.Position
var position = blocks.NewPosition(int32(vector.X), uint32(vector.Y), int32(vector.Z))
session.SendNetworkChunkPublisherUpdate(position, uint32(session.GetViewDistance() * 16))
}
session.chunkLoader.LoadFunction = func(chunk *chunks.Chunk) {
session.SendFullChunkData(chunk)
chunk.AddViewer(session)
chunk.AddEntity(session.player)
}
session.chunkLoader.UnloadFunction = func(chunk *chunks.Chunk) {
chunk.RemoveViewer(session)
chunk.RemoveEntity(session.player.GetRuntimeId())
}
}
// GetPlayer returns the player associated with the Minecraft session.
// This player may not yet exist during the login sequence, and this function may return nil.
func (session *MinecraftSession) GetPlayer() *players.Player {
return session.player
}
// SetPlayer sets the player associated with the Minecraft session.
// Network actions will be executed on this player.
func (session *MinecraftSession) SetPlayer(player *players.Player) {
session.player = player
}
// GetName returns the name of the player under the session.
func (session *MinecraftSession) GetName() string {
if session.player == nil {
return ""
}
return session.player.GetName()
}
// GetDisplayName returns the display name of the player under the session.
func (session *MinecraftSession) GetDisplayName() string {
if session.player == nil {
return ""
}
return session.player.GetDisplayName()
}
// HasSpawned checks if the player of the session has spawned.
func (session *MinecraftSession) HasSpawned() bool {
return session.GetPlayer().GetDimension() != nil
}
// SetViewDistance sets the view distance of this player.
func (session *MinecraftSession) SetViewDistance(distance int32) {
session.viewDistance = distance
}
// GetViewDistance returns the view distance of this player.
func (session *MinecraftSession) GetViewDistance() int32 {
return session.viewDistance
}
// GetChunkLoader returns the chunk loader of the session.
func (session *MinecraftSession) GetChunkLoader() *worlds.Loader {
return session.chunkLoader
}
// GetPlatform returns the platform the client uses to player the game.
func (session *MinecraftSession) GetPlatform() int32 {
return session.clientPlatform
}
// GetProtocolNumber returns the bedrock number the client used to join the server.
func (session *MinecraftSession) GetProtocolNumber() int32 {
return session.protocolNumber
}
// GetGameVersion returns the Minecraft version the player used to join the server.
func (session *MinecraftSession) GetGameVersion() string {
return session.minecraftVersion
}
// GetSession returns the GoRakLib session of this session.
func (session *MinecraftSession) GetSession() *server.Session {
return session.session
}
// GetPing returns the ping of the session in milliseconds.
func (session *MinecraftSession) GetPing() int64 {
return session.session.CurrentPing
}
// GetUUID returns the UUID of this session.
func (session *MinecraftSession) GetUUID() uuid.UUID {
return session.uuid
}
// GetXUID returns the XUID of this session.
func (session *MinecraftSession) GetXUID() string {
return session.xuid
}
// SetLanguage sets the language (locale) of this session.
func (session *MinecraftSession) SetLanguage(language string) {
session.language = language
}
// GetLanguage returns the language (locale) of this session.
func (session *MinecraftSession) GetLanguage() string {
return session.language
}
// GetClientId returns the client ID of this session.
func (session *MinecraftSession) GetClientId() int {
return session.clientId
}
// GetEncryptionHandler returns the handler used for encryption.
func (session *MinecraftSession) GetEncryptionHandler() *utils.EncryptionHandler {
return session.encryptionHandler
}
// UsesEncryption checks if the session uses encryption or not.
func (session *MinecraftSession) UsesEncryption() bool {
return session.usesEncryption
}
// EnableEncryption enables encryption for this session and computes secret key bytes.
func (session *MinecraftSession) EnableEncryption() {
session.usesEncryption = true
session.encryptionHandler.Data.ComputeSharedSecret()
session.encryptionHandler.Data.ComputeSecretKeyBytes()
}
// IsXBOXLiveAuthenticated checks if the session logged in while being logged into XBOX Live.
func (session *MinecraftSession) IsXBOXLiveAuthenticated() bool {
return session.xboxLiveAuthenticated
}
// SetXBOXLiveAuthenticated sets the session XBOX Live authenticated.
func (session *MinecraftSession) SetXBOXLiveAuthenticated(value bool) {
session.xboxLiveAuthenticated = value
}
// SendMessage sends a text message to the Minecraft session.
func (session *MinecraftSession) SendMessage(message ...interface{}) {
session.SendText(types.Text{Message: strings.Trim(fmt.Sprint(message), "[]")})
}
// GetPermissionGroup returns the permission group this session is in.
func (session *MinecraftSession) GetPermissionGroup() *permissions.Group {
return session.permissionGroup
}
// SetPermissionGroup sets the permission group of this session.
func (session *MinecraftSession) SetPermissionGroup(group *permissions.Group) {
session.permissionGroup = group
}
// HasPermission checks if this session has a permission.
func (session *MinecraftSession) HasPermission(permission string) bool {
if session.GetPermissionGroup().HasPermission(permission) {
return true
}
var _, exists = session.permissions[permission]
return exists
}
// AddPermission adds a permission to the session.
// Returns true if a permission with the same name was overwritten.
func (session *MinecraftSession) AddPermission(permission *permissions.Permission) bool {
var hasPermission = session.HasPermission(permission.GetName())
session.permissions[permission.GetName()] = permission
return hasPermission
}
// RemovePermission deletes a permission from the session.
// This does not delete the permission from the group the session is in.
func (session *MinecraftSession) RemovePermission(permission string) bool {
if !session.HasPermission(permission) {
return false
}
delete(session.permissions, permission)
return true
}
func (session *MinecraftSession) SendSkin(target *MinecraftSession) {
var player = session.GetPlayer()
target.SendPlayerSkin(player.GetUUID(), player.GetSkinId(), player.GetGeometryName(), player.GetGeometryData(), player.GetSkinData(), player.GetCapeData())
}
// SendPacket sends a packet to this session.
func (session *MinecraftSession) SendPacket(packet packets.IPacket) {
if session.session == nil {
return
}
var b = NewMinecraftPacketBatch(session)
b.AddPacket(packet)
session.SendBatch(b)
}
// SendBatch sends a batch to this session.
func (session *MinecraftSession) SendBatch(batch *MinecraftPacketBatch) {
if session.session == nil {
return
}
session.session.SendPacket(batch, protocol.ReliabilityReliable, server.PriorityMedium)
}
// HandlePacket handles packets of this session.
func (session *MinecraftSession) HandlePacket(packet packets.IPacket) {
priorityHandlers := session.adapter.packetManager.GetHandlersById(packet.GetId())
var handled = false
handling:
for _, h := range priorityHandlers {
for _, iHandler := range h {
if handler, ok := iHandler.(*PacketHandler); ok {
if packet.IsDiscarded() {
break handling
}
ret := handler.function(packet, session)
if !handled {
handled = ret
}
}
}
}
if !handled {
text.DefaultLogger.Debug("Unhandled Minecraft packet with ID:", packet.GetId())
}
}
func (session *MinecraftSession) Close(reason string, hideDisconnectionScreen bool) {
if session.Connected {
loadedChunks := session.GetChunkLoader().GetLoadedChunks()
for _, online := range session.adapter.sessionManager.GetSessions() {
online.SendRemoveEntity(session.player.Entity.GetUniqueId())
online.player.RemoveViewer(session)
}
for _, chunk := range loadedChunks {
chunk.RemoveViewer(session)
chunk.RemoveEntity(session.player.Entity.GetRuntimeId())
}
session.player.Close()
}
session.SendDisconnect(reason, hideDisconnectionScreen)
}
func (session *MinecraftSession) Kick(reason string, hideDisconnectionScreen bool, isAdmin bool) {
if isAdmin {
reason = "Kicked By Admin. Reason: " + reason
session.Close(reason, hideDisconnectionScreen)
text.DefaultLogger.Info(session.GetDisplayName() + " Disconnected.", reason)
}else{
session.Close(reason, hideDisconnectionScreen)
text.DefaultLogger.Info(session.GetDisplayName() + " Disconnected.", reason)
}
}
// SyncMove synchronizes the server's player movement with the client movement.
func (session *MinecraftSession) SyncMove(x, y, z float64, pitch, yaw, headYaw float64, onGround bool) {
session.player.SyncMove(x, y, z, pitch, yaw, headYaw, onGround)
}
func (session *MinecraftSession) Tick() {
if session.Connected {
session.GetChunkLoader().Warp(session.GetPlayer().GetDimension(), int32(math.Floor(session.player.Position.X))>>4, int32(math.Floor(session.player.Position.Z))>>4)
session.GetChunkLoader().Request(session.GetViewDistance(), 40)
}
}
================================================
FILE: net/network_adapter.go
================================================
package net
import (
"github.com/irmine/gomine/net/packets"
protocol2 "github.com/irmine/gomine/net/protocol"
"github.com/irmine/gomine/text"
"github.com/irmine/goraklib/protocol"
"github.com/irmine/goraklib/server"
"net"
)
type NetworkAdapter struct {
rakLibManager *server.Manager
packetManager protocol2.IPacketManager
sessionManager *SessionManager
}
// NewNetworkAdapter returns a new Network adapter to adapt to the RakNet server.
func NewNetworkAdapter(packetManager protocol2.IPacketManager, sessionManager *SessionManager) *NetworkAdapter {
var manager = server.NewManager()
var adapter = &NetworkAdapter{manager, packetManager, sessionManager}
manager.PacketFunction = func(packet []byte, session *server.Session) {
var minecraftSession *MinecraftSession
var ok bool
if minecraftSession, ok = adapter.sessionManager.GetSessionByRakNetSession(session); !ok {
minecraftSession = NewMinecraftSession(adapter, session)
}
adapter.HandlePacket(minecraftSession, packet)
}
manager.DisconnectFunction = func(session *server.Session) {
text.DefaultLogger.Debug(session, "disconnected!")
}
manager.ConnectFunction = func(session *server.Session) {
text.DefaultLogger.Debug(session, "connected!")
}
return adapter
}
// GetRakLibManager returns the GoRakLib manager of the network adapter.
func (adapter *NetworkAdapter) GetRakLibManager() *server.Manager {
return adapter.rakLibManager
}
// HandlePackets handles all packets of the given session + player.
func (adapter *NetworkAdapter) HandlePacket(session *MinecraftSession, buffer []byte) {
batch := NewMinecraftPacketBatch(session)
batch.Buffer = buffer
batch.Decode()
for _, packet := range batch.GetPackets() {
if session.GetProtocolNumber() < 120 {
packet.DecodeId()
} else {
packet.DecodeHeader()
}
packet.Decode()
session.HandlePacket(packet)
}
}
// GetSession returns a GoRakLib session by an address and port.
func (adapter *NetworkAdapter) GetSession(address string, port uint16) *server.Session {
var session, _ = adapter.rakLibManager.Sessions.GetSession(&net.UDPAddr{IP: net.ParseIP(address), Port: int(port)})
return session
}
// SendPacket sends a packet to the given Minecraft session with the given priority.
func (adapter *NetworkAdapter) SendPacket(pk packets.IPacket, session *MinecraftSession, priority server.Priority) {
var b = NewMinecraftPacketBatch(session)
b.AddPacket(pk)
adapter.SendBatch(b, session.GetSession(), priority)
}
// SendBatch sends a Minecraft packet batch to the given GoRakLib session with the given priority.
func (adapter *NetworkAdapter) SendBatch(batch *MinecraftPacketBatch, session *server.Session, priority server.Priority) {
session.SendPacket(batch, protocol.ReliabilityReliableOrdered, priority)
}
================================================
FILE: net/packet_handler.go
================================================
package net
import (
"github.com/irmine/gomine/net/packets"
)
// Packet handlers can be registered to listen on certain packet IDs.
// Handlers can be registered on unhandled packets in order to handle them from a plugin.
// Every packet handler has a handling function that handles the incoming packet.
type PacketHandler struct {
function func(packet packets.IPacket, session *MinecraftSession) bool
priority int
}
// NewPacketHandler returns a new packet handler with the given ID.
// NewPacketHandler will by default use a priority of 5.
func NewPacketHandler(function func(packet packets.IPacket, session *MinecraftSession) bool) *PacketHandler {
return &PacketHandler{function, 5}
}
// SetPriority sets the priority of this handler in an integer 0 - 10.
// 0 is executed first, 10 is executed last.
func (handler *PacketHandler) SetPriority(priority int) bool {
if priority > 10 || priority < 0 {
return false
}
handler.priority = priority
return true
}
// GetPriority returns the priority of this handler in an integer 0 - 10.
func (handler *PacketHandler) GetPriority() int {
return handler.priority
}
================================================
FILE: net/packets/bedrock/add_entity.go
================================================
package bedrock
import (
"github.com/golang/geo/r3"
"github.com/irmine/worlds/entities/data"
"github.com/irmine/gomine/net/info"
"github.com/irmine/gomine/net/packets"
)
type AddEntityPacket struct {
*packets.Packet
UniqueId int64
RuntimeId uint64
EntityType uint32
Position r3.Vector
Motion r3.Vector
Rotation data.Rotation
Attributes data.AttributeMap
EntityData map[uint32][]interface{}
}
func NewAddEntityPacket() *AddEntityPacket {
return &AddEntityPacket{packets.NewPacket(info.PacketIds[info.AddEntityPacket]), 0, 0, 0, r3.Vector{}, r3.Vector{}, data.Rotation{}, data.NewAttributeMap(), nil}
}
func (pk *AddEntityPacket) Encode() {
pk.PutEntityUniqueId(pk.UniqueId)
pk.PutEntityRuntimeId(pk.RuntimeId)
pk.PutUnsignedVarInt(pk.EntityType)
pk.PutVector(pk.Position)
pk.PutVector(pk.Motion)
pk.PutEntityRotation(pk.Rotation)
pk.PutAttributeMap(pk.Attributes)
pk.PutEntityData(pk.EntityData)
pk.PutUnsignedVarInt(0)
}
func (pk *AddEntityPacket) Decode() {
pk.UniqueId = pk.GetEntityUniqueId()
pk.RuntimeId = pk.GetEntityRuntimeId()
pk.EntityType = pk.GetUnsignedVarInt()
pk.Position = pk.GetVector()
pk.Motion = pk.GetVector()
pk.Rotation = pk.GetEntityRotation()
pk.Attributes = pk.GetAttributeMap()
pk.EntityData = pk.GetEntityData()
}
================================================
FILE: net/packets/bedrock/add_player.go
================================================
package bedrock
import (
"github.com/golang/geo/r3"
"github.com/google/uuid"
"github.com/irmine/gomine/net/info"
"github.com/irmine/gomine/net/packets"
"github.com/irmine/worlds/entities/data"
)
type AddPlayerPacket struct {
*packets.Packet
UUID uuid.UUID
Username string
PlatformChatId string
EntityUniqueId int64
EntityRuntimeId uint64
Position r3.Vector
Motion r3.Vector
Rotation data.Rotation
// HandItem TODO: Items.
Metadata map[uint32][]interface{}
Flags uint32
CommandPermission uint32
Flags2 uint32
PlayerPermission uint32
CustomFlags uint32
Long1 int64
// EntityLinks TODO
DeviceID string
}
func NewAddPlayerPacket() *AddPlayerPacket {
return &AddPlayerPacket{Packet: packets.NewPacket(info.PacketIds[info.AddPlayerPacket]), Metadata: make(map[uint32][]interface{}), Motion: r3.Vector{}}
}
func (pk *AddPlayerPacket) Encode() {
pk.PutUUID(pk.UUID)
pk.PutString(pk.Username)
pk.PutEntityUniqueId(pk.EntityUniqueId)
pk.PutEntityRuntimeId(pk.EntityRuntimeId)
pk.PutString(pk.PlatformChatId)
pk.PutVector(pk.Position)
pk.PutVector(pk.Motion)
pk.PutPlayerRotation(pk.Rotation)
pk.PutVarInt(0) // TODO
pk.PutEntityData(pk.Metadata)
pk.PutUnsignedVarInt(pk.Flags)
pk.PutUnsignedVarInt(pk.CommandPermission)
pk.PutUnsignedVarInt(pk.Flags2)
pk.PutUnsignedVarInt(pk.PlayerPermission)
pk.PutUnsignedVarInt(pk.CustomFlags)
pk.PutVarLong(pk.Long1)
pk.PutUnsignedVarInt(0) // TODO
pk.PutString(pk.DeviceID)
}
func (pk *AddPlayerPacket) Decode() {
}
================================================
FILE: net/packets/bedrock/animate.go
================================================
package bedrock
import (
"github.com/irmine/gomine/net/info"
"github.com/irmine/gomine/net/packets"
)
const (
SwingArm = 1
StopSleeping = 3
CriticalHit = 4
)
type AnimatePacket struct {
*packets.Packet
Action int32
RuntimeId uint64
Float float32
}
func NewAnimatePacket() *AnimatePacket {
return &AnimatePacket{ Packet: packets.NewPacket(info.PacketIds[info.AnimatePacket])}
}
func (pk *AnimatePacket) Encode() {
pk.PutVarInt(pk.Action)
pk.PutUnsignedVarLong(pk.RuntimeId)
if uint(pk.Action) & 0x80 == 1 {
pk.PutLittleFloat(pk.Float)
}
}
func (pk *AnimatePacket) Decode() {
pk.Action = pk.GetVarInt()
pk.RuntimeId = pk.GetUnsignedVarLong()
if uint(pk.Action) & 0x80 == 1 {
pk.Float = pk.GetLittleFloat()
}
}
================================================
FILE: net/packets/bedrock/chunk_radius_updated.go
================================================
package bedrock
import (
"github.com/irmine/gomine/net/info"
"github.com/irmine/gomine/net/packets"
)
type ChunkRadiusUpdatedPacket struct {
*packets.Packet
Radius int32
}
func NewChunkRadiusUpdatedPacket() *ChunkRadiusUpdatedPacket {
return &ChunkRadiusUpdatedPacket{packets.NewPacket(info.PacketIds[info.ChunkRadiusUpdatedPacket]), 0}
}
func (pk *ChunkRadiusUpdatedPacket) Encode() {
pk.PutVarInt(pk.Radius)
}
func (pk *ChunkRadiusUpdatedPacket) Decode() {
}
================================================
FILE: net/packets/bedrock/client_handshake.go
================================================
package bedrock
import (
"github.com/irmine/gomine/net/info"
"github.com/irmine/gomine/net/packets"
)
type ClientHandshakePacket struct {
*packets.Packet
}
func NewClientHandshakePacket() *ClientHandshakePacket {
return &ClientHandshakePacket{packets.NewPacket(info.PacketIds[info.ClientHandshakePacket])}
}
func (pk *ClientHandshakePacket) Encode() {
}
func (pk *ClientHandshakePacket) Decode() {
}
================================================
FILE: net/packets/bedrock/command_request.go
================================================
package bedrock
import (
"github.com/google/uuid"
"github.com/irmine/gomine/net/info"
"github.com/irmine/gomine/net/packets"
)
type CommandRequestPacket struct {
*packets.Packet
CommandText string
Type uint32
UUID uuid.UUID
RequestId string
Internal bool
}
func NewCommandRequestPacket() *CommandRequestPacket {
return &CommandRequestPacket{packets.NewPacket(info.PacketIds[info.CommandRequestPacket]), "", 0, uuid.New(), "", false}
}
func (pk *CommandRequestPacket) Encode() {
}
func (pk *CommandRequestPacket) Decode() {
pk.CommandText = pk.GetString()
pk.Type = pk.GetUnsignedVarInt()
pk.UUID = pk.GetUUID()
pk.RequestId = pk.GetString()
pk.Internal = pk.GetBool()
}
================================================
FILE: net/packets/bedrock/crafting_data.go
================================================
package bedrock
import (
"github.com/irmine/gomine/net/info"
"github.com/irmine/gomine/net/packets"
)
type CraftingDataPacket struct {
*packets.Packet
}
func NewCraftingDataPacket() *CraftingDataPacket {
return &CraftingDataPacket{packets.NewPacket(info.PacketIds[info.CraftingDataPacket])}
}
func (pk *CraftingDataPacket) Encode() {
pk.PutUnsignedVarInt(0)
pk.PutBool(true)
}
func (pk *CraftingDataPacket) Decode() {
}
================================================
FILE: net/packets/bedrock/disconnect.go
================================================
package bedrock
import (
"github.com/irmine/gomine/net/info"
"github.com/irmine/gomine/net/packets"
)
type DisconnectPacket struct {
*packets.Packet
HideDisconnectionScreen bool
Message string
}
func NewDisconnectPacket() *DisconnectPacket {
return &DisconnectPacket{packets.NewPacket(info.PacketIds[info.DisconnectPacket]), true, ""}
}
func (pk *DisconnectPacket) Encode() {
pk.PutBool(pk.HideDisconnectionScreen)
pk.PutString(pk.Message)
}
func (pk *DisconnectPacket) Decode() {
pk.HideDisconnectionScreen = pk.GetBool()
pk.Message = pk.GetString()
}
================================================
FILE: net/packets/bedrock/full_chunk_data.go
================================================
package bedrock
import (
"github.com/irmine/gomine/net/info"
"github.com/irmine/gomine/net/packets"
)
type FullChunkDataPacket struct {
*packets.Packet
ChunkX int32
ChunkZ int32
ChunkData []byte
}
func NewFullChunkDataPacket() *FullChunkDataPacket {
return &FullChunkDataPacket{Packet: packets.NewPacket(info.PacketIds[info.FullChunkDataPacket])}
}
func (pk *FullChunkDataPacket) Encode() {
pk.PutVarInt(pk.ChunkX)
pk.PutVarInt(pk.ChunkZ)
pk.PutLengthPrefixedBytes(pk.ChunkData)
}
func (pk *FullChunkDataPacket) Decode() {
}
================================================
FILE: net/packets/bedrock/interact_packet.go
================================================
package bedrock
import (
"github.com/irmine/gomine/net/info"
"github.com/irmine/gomine/net/packets"
)
const (
RightClick = 1
LeftClick = 2
LeaveCehicle = 3
MouseOver = 4
)
type InteractPacket struct {
*packets.Packet
Action byte
RuntimeId uint64
}
func NewInteractPacket() *InteractPacket {
return &InteractPacket{ packets.NewPacket(info.PacketIds[info.InteractPacket]), 0, 0}
}
func (pk *InteractPacket) Encode() {
pk.PutByte(pk.Action)
pk.PutUnsignedVarLong(pk.RuntimeId)
}
func (pk *InteractPacket) Decode() {
pk.Action = pk.GetByte()
pk.RuntimeId = pk.GetUnsignedVarLong()
}
================================================
FILE: net/packets/bedrock/inventory_transaction.go
================================================
package bedrock
import (
"github.com/golang/geo/r3"
"github.com/irmine/gomine/items"
"github.com/irmine/gomine/items/inventory/io"
"github.com/irmine/gomine/net/info"
"github.com/irmine/gomine/net/packets"
"github.com/irmine/worlds/blocks"
)
// Transaction Types
const (
Normal = iota + 0
Mismatch
UseItem
UseItemOnEntity
ReleaseItem
)
// Action Types
const (
ItemClickBlock = iota + 0
ItemClickAir
ItemBreakBlock
//CONSUMABLE ITEMS
ItemRelease = iota + 0
ItemConsume
)
// Entity Action types
const (
ItemOnEntityInteract = iota + 0
ItemOnEntityAttack
)
type InventoryTransactionPacket struct {
*packets.Packet
ActionList *io.InventoryActionIOList
TransactionType, ActionType uint32
Face, HotbarSlot int32
ItemSlot *items.Stack
BlockPosition blocks.Position
PlayerPosition, ClickPosition, HeadPosition r3.Vector
RuntimeId uint64
}
func NewInventoryTransactionPacket() *InventoryTransactionPacket {
pk := &InventoryTransactionPacket{Packet: packets.NewPacket(info.PacketIds[info.InventoryTransactionPacket]),
ActionList: io.NewInventoryActionIOList(),
TransactionType: 0,
ActionType: 0,
Face: 0,
HotbarSlot: 0,
ItemSlot: &items.Stack{},
PlayerPosition: r3.Vector{},
ClickPosition: r3.Vector{},
HeadPosition: r3.Vector{},
RuntimeId: 0,
}
return pk
}
func (pk *InventoryTransactionPacket) Encode() {
pk.PutUnsignedVarInt(pk.TransactionType)
pk.ActionList.WriteToBuffer(pk.MinecraftStream)
switch pk.TransactionType {
case Normal, Mismatch:
break
case UseItem:
pk.PutUnsignedVarInt(pk.ActionType)
pk.PutBlockPosition(pk.BlockPosition)
pk.PutVarInt(pk.Face)
pk.PutVarInt(pk.HotbarSlot)
pk.PutItem(pk.ItemSlot)
pk.PutVector(pk.PlayerPosition)
pk.PutVector(pk.ClickPosition)
break
case UseItemOnEntity:
pk.PutUnsignedVarLong(pk.RuntimeId)
pk.PutUnsignedVarInt(pk.ActionType)
pk.PutVarInt(pk.HotbarSlot)
pk.PutItem(pk.ItemSlot)
pk.PutVector(pk.PlayerPosition)
pk.PutVector(pk.ClickPosition)
break
case ReleaseItem:
pk.PutUnsignedVarInt(pk.ActionType)
pk.PutVarInt(pk.HotbarSlot)
pk.PutItem(pk.ItemSlot)
pk.PutVector(pk.HeadPosition)
break
default:
panic("Unknown transaction type passed: " + string(pk.TransactionType))
}
}
func (pk *InventoryTransactionPacket) Decode() {
pk.TransactionType = pk.GetUnsignedVarInt()
pk.ActionList.ReadFromBuffer(pk.MinecraftStream)
switch pk.TransactionType{
case Normal, Mismatch:
break
case UseItem:
pk.ActionType = pk.GetUnsignedVarInt()
pk.BlockPosition = pk.GetBlockPosition()
pk.Face = pk.GetVarInt()
pk.HotbarSlot = pk.GetVarInt()
pk.ItemSlot = pk.GetItem()
pk.PlayerPosition = pk.GetVector()
pk.ClickPosition = pk.GetVector()
break
case UseItemOnEntity:
pk.RuntimeId = pk.GetUnsignedVarLong()
pk.ActionType = pk.GetUnsignedVarInt()
pk.HotbarSlot = pk.GetVarInt()
pk.ItemSlot = pk.GetItem()
pk.PlayerPosition = pk.GetVector()
pk.ClickPosition = pk.GetVector()
break
case ReleaseItem:
pk.ActionType = pk.GetUnsignedVarInt()
pk.HotbarSlot = pk.GetVarInt()
pk.ItemSlot = pk.GetItem()
pk.HeadPosition = pk.GetVector()
break
default:
panic("Error: Unknown transaction type received: " + string(pk.TransactionType))
}
}
================================================
FILE: net/packets/bedrock/login.go
================================================
package bedrock
import (
"encoding/base64"
"encoding/json"
"strings"
"github.com/google/uuid"
"github.com/irmine/binutils"
"github.com/irmine/gomine/net/info"
"github.com/irmine/gomine/net/packets"
"github.com/irmine/gomine/net/packets/types"
"github.com/irmine/gomine/utils"
)
type LoginPacket struct {
*packets.Packet
Username string
Protocol int32
ClientUUID uuid.UUID
ClientId int
ClientXUID string
IdentityPublicKey string
ServerAddress string
Language string
SkinId string
SkinData []byte
CapeData []byte
GeometryName string
GeometryData string
ClientData types.ClientDataKeys
Chains []types.Chain
}
func NewLoginPacket() *LoginPacket {
pk := &LoginPacket{packets.NewPacket(info.PacketIds[info.LoginPacket]), "", 0, uuid.New(), 0, "", "", "", "", "", []byte{}, []byte{}, "", "", types.ClientDataKeys{}, []types.Chain{}}
return pk
}
func (pk *LoginPacket) Encode() {
}
func (pk *LoginPacket) Decode() {
pk.Protocol = pk.GetInt()
var stream = binutils.NewStream()
stream.Buffer = []byte(pk.GetString())
var length = int(stream.GetLittleInt())
var chainData = &types.ChainDataKeys{}
json.Unmarshal(stream.Get(length), &chainData)
for _, v := range chainData.RawChains {
WebToken := &types.WebTokenKeys{}
pk.Chains = append(pk.Chains, pk.BuildChain(v))
utils.DecodeJwtPayload(v, WebToken)
if v, ok := WebToken.ExtraData["displayName"]; ok {
pk.Username = v.(string)
}
if v, ok := WebToken.ExtraData["identity"]; ok {
pk.ClientUUID = uuid.Must(uuid.Parse(v.(string)))
}
if v, ok := WebToken.ExtraData["XUID"]; ok {
pk.ClientXUID = v.(string)
}
if len(WebToken.IdentityPublicKey) > 0 {
pk.IdentityPublicKey = WebToken.IdentityPublicKey
}
}
var clientDataJwt = stream.Get(int(stream.GetLittleInt()))
var clientData = &types.ClientDataKeys{}
utils.DecodeJwtPayload(string(clientDataJwt), clientData)
pk.ClientId = clientData.ClientRandomId
pk.ServerAddress = clientData.ServerAddress
pk.Language = clientData.LanguageCode
if pk.Language == "" {
pk.Language = "en_US"
}
pk.SkinId = clientData.SkinId
pk.GeometryName = clientData.GeometryId
pk.SkinData, _ = base64.RawStdEncoding.DecodeString(clientData.SkinData)
pk.CapeData, _ = base64.RawStdEncoding.DecodeString(clientData.CapeData)
var geometry, _ = base64.RawStdEncoding.DecodeString(clientData.GeometryData)
pk.GeometryData = string(geometry)
for len(pk.SkinData) < 16384 {
pk.SkinData = append(pk.SkinData, 0x00)
}
pk.ClientData = *clientData
}
func (pk *LoginPacket) BuildChain(raw string) types.Chain {
jwt := utils.DecodeJwt(raw)
var base64s = strings.Split(raw, ".")
chain := types.Chain{}
for i, str := range jwt {
switch i {
case 0:
header := types.ChainHeader{}
json.Unmarshal([]byte(str), &header)
header.Raw = base64s[i]
chain.Header = header
case 1:
payload := types.ChainPayload{}
json.Unmarshal([]byte(str), &payload)
payload.Raw = base64s[i]
chain.Payload = payload
case 2:
chain.Signature = str
}
}
return chain
}
================================================
FILE: net/packets/bedrock/move_entity.go
================================================
package bedrock
import (
"github.com/golang/geo/r3"
"github.com/irmine/gomine/net/info"
"github.com/irmine/gomine/net/packets"
data2 "github.com/irmine/worlds/entities/data"
)
type MoveEntityPacket struct {
*packets.Packet
RuntimeId uint64
Position r3.Vector
Rotation data2.Rotation
Flags byte
}
func NewMoveEntityPacket() *MoveEntityPacket {
return &MoveEntityPacket{Packet: packets.NewPacket(info.PacketIds[info.MoveEntityPacket]), Position: r3.Vector{}, Rotation: data2.Rotation{}, Flags: 0}
}
func (pk *MoveEntityPacket) Encode() {
pk.PutEntityRuntimeId(pk.RuntimeId)
pk.PutByte(pk.Flags)
pk.PutVector(pk.Position)
pk.PutEntityRotationBytes(pk.Rotation)
}
func (pk *MoveEntityPacket) Decode() {
pk.RuntimeId = pk.GetEntityRuntimeId()
pk.Flags = pk.GetByte()
pk.Position = pk.GetVector()
pk.Rotation = pk.GetEntityRotationBytes()
}
================================================
FILE: net/packets/bedrock/move_player.go
================================================
package bedrock
import (
"github.com/golang/geo/r3"
"github.com/irmine/gomine/net/info"
"github.com/irmine/gomine/net/packets"
"github.com/irmine/gomine/net/packets/data"
data2 "github.com/irmine/worlds/entities/data"
)
type MovePlayerPacket struct {
*packets.Packet
RuntimeId uint64
Position r3.Vector
Rotation data2.Rotation
Mode byte
OnGround bool
RidingRuntimeId uint64
TeleportCause, TeleportItem int32
}
func NewMovePlayerPacket() *MovePlayerPacket {
return &MovePlayerPacket{Packet: packets.NewPacket(info.PacketIds[info.MovePlayerPacket]), Position: r3.Vector{}, Rotation: data2.Rotation{}}
}
func (pk *MovePlayerPacket) Encode() {
pk.PutEntityRuntimeId(pk.RuntimeId)
pk.PutVector(pk.Position)
pk.PutPlayerRotation(pk.Rotation)
pk.PutByte(pk.Mode)
pk.PutBool(pk.OnGround)
pk.PutEntityRuntimeId(pk.RidingRuntimeId)
if pk.Mode == data.MoveTeleport {
pk.PutLittleInt(pk.TeleportCause)
pk.PutLittleInt(pk.TeleportItem)
}
}
func (pk *MovePlayerPacket) Decode() {
pk.RuntimeId = pk.GetEntityRuntimeId()
pk.Position = pk.GetVector()
pk.Rotation = pk.GetPlayerRotation()
pk.Mode = pk.GetByte()
pk.OnGround = pk.GetBool()
pk.RidingRuntimeId = pk.GetEntityRuntimeId()
if pk.Mode == data.MoveTeleport {
pk.TeleportCause = pk.GetLittleInt()
pk.TeleportItem = pk.GetLittleInt()
}
}
================================================
FILE: net/packets/bedrock/network_chunk_publisher_update.go
================================================
package bedrock
import (
"github.com/irmine/gomine/net/info"
"github.com/irmine/gomine/net/packets"
"github.com/irmine/worlds/blocks"
)
type NetworkChunkPublisherUpdatePacket struct {
*packets.Packet
Position blocks.Position
Radius uint32
}
func NewNetworkChunkPublisherUpdatePacket() *NetworkChunkPublisherUpdatePacket {
return &NetworkChunkPublisherUpdatePacket{packets.NewPacket(info.PacketIds[info.NetworkChunkPublisherUpdatePacket]), blocks.NewPosition(0, 0, 0), 0}
}
func (pk *NetworkChunkPublisherUpdatePacket) Encode() {
pk.PutBlockPosition(pk.Position)
pk.PutUnsignedVarInt(pk.Radius)
}
func (pk *NetworkChunkPublisherUpdatePacket) Decode() {
}
================================================
FILE: net/packets/bedrock/play_status.go
================================================
package bedrock
import (
"github.com/irmine/gomine/net/info"
"github.com/irmine/gomine/net/packets"
)
type PlayStatusPacket struct {
*packets.Packet
Status int32
}
func NewPlayStatusPacket() *PlayStatusPacket {
return &PlayStatusPacket{packets.NewPacket(info.PacketIds[info.PlayStatusPacket]), 0}
}
func (pk *PlayStatusPacket) Encode() {
pk.PutInt(pk.Status)
}
func (pk *PlayStatusPacket) Decode() {
pk.Status = pk.GetInt()
}
================================================
FILE: net/packets/bedrock/player_action.go
================================================
package bedrock
import (
"github.com/irmine/gomine/net/info"
"github.com/irmine/gomine/net/packets"
"github.com/irmine/worlds/blocks"
)
const (
PlayerStartBreak = iota
PlayerAbortBreak
PlayerStopBreak
PlayerGetUpdatedBlock
PlayerDropItem
playerStartSleeping
PlayerStopSleeping
PlayerRespawn
PlayerJump
PlayerStartSprint
PlayerStopSprint
PlayerStartSneak
PlayerStopSneak
PlayerDimensionChangeRequest
PlayerDimensionChangeAck
PlayerStartGlide
PlayerStopGlide
PlayerBuildDenied
PlayerContinueBreak
//TODO: add rest
)
type PlayerActionPacket struct {
*packets.Packet
RuntimeId uint64
Action int32
Position blocks.Position
Face int32
}
func NewPlayerActionPacket() *PlayerActionPacket {
return &PlayerActionPacket{ packets.NewPacket(info.PacketIds[info.PlayerActionPacket]), 0, 0, blocks.Position{}, 0}
}
func (pk *PlayerActionPacket) Encode() {
pk.PutEntityRuntimeId(pk.RuntimeId)
pk.PutVarInt(pk.Action)
pk.PutBlockPosition(pk.Position)
pk.PutVarInt(pk.Face)
}
func (pk *PlayerActionPacket) Decode() {
pk.RuntimeId = pk.GetEntityRuntimeId()
pk.Action = pk.GetVarInt()
pk.Position = pk.GetBlockPosition()
pk.Face = pk.GetVarInt()
}
================================================
FILE: net/packets/bedrock/player_list.go
================================================
package bedrock
import (
"github.com/irmine/gomine/net/info"
"github.com/irmine/gomine/net/packets"
"github.com/irmine/gomine/net/packets/data"
"github.com/irmine/gomine/net/packets/types"
)
type PlayerListPacket struct {
*packets.Packet
ListType byte
Entries map[string]types.PlayerListEntry
}
func NewPlayerListPacket() *PlayerListPacket {
return &PlayerListPacket{packets.NewPacket(info.PacketIds[info.PlayerListPacket]), 0, map[string]types.PlayerListEntry{}}
}
func (pk *PlayerListPacket) Encode() {
pk.PutByte(pk.ListType)
pk.PutUnsignedVarInt(uint32(len(pk.Entries)))
for _, entry := range pk.Entries {
if pk.ListType == byte(data.ListTypeAdd) {
pk.PutUUID(entry.UUID)
pk.PutEntityUniqueId(entry.EntityUniqueId)
pk.PutString(entry.Username)
pk.PutString(entry.SkinId)
pk.PutLengthPrefixedBytes(entry.SkinData)
pk.PutLengthPrefixedBytes(entry.CapeData)
pk.PutString(entry.GeometryName)
pk.PutString(entry.GeometryData)
pk.PutString(entry.XUID)
pk.PutString("")
} else {
pk.PutUUID(entry.UUID)
}
}
}
func (pk *PlayerListPacket) Decode() {
}
================================================
FILE: net/packets/bedrock/player_skin.go
================================================
package bedrock
import (
"github.com/google/uuid"
"github.com/irmine/gomine/net/info"
"github.com/irmine/gomine/net/packets"
)
type PlayerSkinPacket struct {
*packets.Packet
UUID uuid.UUID
SkinId string
NewSkinName string
OldSkinName string
SkinData []byte
CapeData []byte
GeometryName string
GeometryData string
PremiumSkin bool
}
func NewPlayerSkinPacket() *PlayerSkinPacket {
return &PlayerSkinPacket{packets.NewPacket(info.PacketIds[info.PlayerSkinPacket]), uuid.New(), "", "", "", []byte{}, []byte{}, "", "", false}
}
func (pk *PlayerSkinPacket) Encode() {
pk.PutUUID(pk.UUID)
pk.PutString(pk.SkinId)
pk.PutString(pk.NewSkinName)
pk.PutString(pk.OldSkinName)
pk.PutLengthPrefixedBytes(pk.SkinData)
pk.PutLengthPrefixedBytes(pk.CapeData)
pk.PutString(pk.GeometryName)
pk.PutString(pk.GeometryData)
pk.PutBool(pk.PremiumSkin)
}
func (pk *PlayerSkinPacket) Decode() {
pk.UUID = pk.GetUUID()
pk.SkinId = pk.GetString()
pk.NewSkinName = pk.GetString()
pk.OldSkinName = pk.GetString()
pk.SkinData = pk.GetLengthPrefixedBytes()
pk.CapeData = pk.GetLengthPrefixedBytes()
pk.GeometryName = pk.GetString()
pk.GeometryData = pk.GetString()
pk.PremiumSkin = pk.GetBool()
}
================================================
FILE: net/packets/bedrock/remove_entity.go
================================================
package bedrock
import (
"github.com/irmine/gomine/net/info"
"github.com/irmine/gomine/net/packets"
)
type RemoveEntityPacket struct {
*packets.Packet
EntityUniqueId int64
}
func NewRemoveEntityPacket() *RemoveEntityPacket {
return &RemoveEntityPacket{packets.NewPacket(info.PacketIds[info.RemoveEntityPacket]), 0}
}
func (pk *RemoveEntityPacket) Encode() {
pk.PutEntityUniqueId(pk.EntityUniqueId)
}
func (pk *RemoveEntityPacket) Decode() {
}
================================================
FILE: net/packets/bedrock/request_chunk_radius.go
================================================
package bedrock
import (
"github.com/irmine/gomine/net/info"
"github.com/irmine/gomine/net/packets"
)
type RequestChunkRadiusPacket struct {
*packets.Packet
Radius int32
}
func NewRequestChunkRadiusPacket() *RequestChunkRadiusPacket {
return &RequestChunkRadiusPacket{packets.NewPacket(info.PacketIds[info.RequestChunkRadiusPacket]), 0}
}
func (pk *RequestChunkRadiusPacket) Encode() {
}
func (pk *RequestChunkRadiusPacket) Decode() {
pk.Radius = pk.GetVarInt()
}
================================================
FILE: net/packets/bedrock/resource_pack_chunk_data.go
================================================
package bedrock
import (
"github.com/irmine/gomine/net/info"
"github.com/irmine/gomine/net/packets"
)
type ResourcePackChunkDataPacket struct {
*packets.Packet
PackUUID string
ChunkIndex int32
Progress int64
ChunkData []byte
}
func NewResourcePackChunkDataPacket() *ResourcePackChunkDataPacket {
return &ResourcePackChunkDataPacket{packets.NewPacket(info.PacketIds[info.ResourcePackChunkDataPacket]), "", 0, 0, []byte{}}
}
func (pk *ResourcePackChunkDataPacket) Encode() {
pk.PutString(pk.PackUUID)
pk.PutLittleInt(pk.ChunkIndex)
pk.PutLittleLong(pk.Progress)
pk.PutLittleInt(int32(len(pk.ChunkData)))
pk.PutBytes(pk.ChunkData)
}
func (pk *ResourcePackChunkDataPacket) Decode() {
}
================================================
FILE: net/packets/bedrock/resource_pack_chunk_request.go
================================================
package bedrock
import (
"github.com/irmine/gomine/net/info"
"github.com/irmine/gomine/net/packets"
)
type ResourcePackChunkRequestPacket struct {
*packets.Packet
PackUUID string
ChunkIndex int32
}
func NewResourcePackChunkRequestPacket() *ResourcePackChunkRequestPacket {
return &ResourcePackChunkRequestPacket{packets.NewPacket(info.PacketIds[info.ResourcePackChunkRequestPacket]), "", 0}
}
func (pk *ResourcePackChunkRequestPacket) Encode() {
}
func (pk *ResourcePackChunkRequestPacket) Decode() {
pk.PackUUID = pk.GetString()
pk.ChunkIndex = pk.GetLittleInt()
}
================================================
FILE: net/packets/bedrock/resource_pack_client_response.go
================================================
package bedrock
import (
"github.com/irmine/gomine/net/info"
"github.com/irmine/gomine/net/packets"
)
type ResourcePackClientResponsePacket struct {
*packets.Packet
Status byte
PackUUIDs []string
}
func NewResourcePackClientResponsePacket() *ResourcePackClientResponsePacket {
return &ResourcePackClientResponsePacket{packets.NewPacket(info.PacketIds[info.ResourcePackClientResponsePacket]), 0, []string{}}
}
func (pk *ResourcePackClientResponsePacket) Encode() {
}
func (pk *ResourcePackClientResponsePacket) Decode() {
pk.Status = pk.GetByte()
var idCount = pk.GetLittleShort()
for i := int16(0); i < idCount; i++ {
pk.PackUUIDs = append(pk.PackUUIDs, pk.GetString())
}
}
================================================
FILE: net/packets/bedrock/resource_pack_data_info.go
================================================
package bedrock
import (
"github.com/irmine/gomine/net/info"
"github.com/irmine/gomine/net/packets"
)
type ResourcePackDataInfoPacket struct {
*packets.Packet
PackUUID string
MaxChunkSize int32
ChunkCount int32
CompressedPackSize int64
Sha256 string
}
func NewResourcePackDataInfoPacket() *ResourcePackDataInfoPacket {
return &ResourcePackDataInfoPacket{packets.NewPacket(info.PacketIds[info.ResourcePackDataInfoPacket]), "", 0, 0, 0, ""}
}
func (pk *ResourcePackDataInfoPacket) Encode() {
pk.PutString(pk.PackUUID)
pk.PutLittleInt(pk.MaxChunkSize)
pk.PutLittleInt(pk.ChunkCount)
pk.PutLittleLong(pk.CompressedPackSize)
pk.PutString(pk.Sha256)
}
func (pk *ResourcePackDataInfoPacket) Decode() {
}
================================================
FILE: net/packets/bedrock/resource_pack_info.go
================================================
package bedrock
import (
"github.com/irmine/gomine/net/info"
"github.com/irmine/gomine/net/packets"
"github.com/irmine/gomine/net/packets/types"
)
type ResourcePackInfoPacket struct {
*packets.Packet
MustAccept bool
BehaviorPacks []types.ResourcePackInfoEntry
ResourcePacks []types.ResourcePackInfoEntry
Bool1 bool
}
func NewResourcePackInfoPacket() *ResourcePackInfoPacket {
return &ResourcePackInfoPacket{packets.NewPacket(info.PacketIds[info.ResourcePackInfoPacket]), false, []types.ResourcePackInfoEntry{}, []types.ResourcePackInfoEntry{}, false}
}
func (pk *ResourcePackInfoPacket) Encode() {
pk.PutBool(pk.MustAccept)
pk.PutBool(pk.Bool1)
pk.PutPackInfo(pk.BehaviorPacks)
pk.PutPackInfo(pk.ResourcePacks)
}
func (pk *ResourcePackInfoPacket) Decode() {
}
================================================
FILE: net/packets/bedrock/resource_pack_stack.go
================================================
package bedrock
import (
"github.com/irmine/gomine/net/info"
"github.com/irmine/gomine/net/packets"
"github.com/irmine/gomine/net/packets/types"
)
type ResourcePackStackPacket struct {
*packets.Packet
MustAccept bool
BehaviorPacks []types.ResourcePackStackEntry
ResourcePacks []types.ResourcePackStackEntry
Experimental bool
}
func NewResourcePackStackPacket() *ResourcePackStackPacket {
return &ResourcePackStackPacket{packets.NewPacket(info.PacketIds[info.ResourcePackStackPacket]), false, []types.ResourcePackStackEntry{}, []types.ResourcePackStackEntry{}, false}
}
func (pk *ResourcePackStackPacket) Encode() {
pk.PutBool(pk.MustAccept)
pk.PutPackStack(pk.BehaviorPacks)
pk.PutPackStack(pk.ResourcePacks)
pk.PutBool(pk.Experimental)
}
func (pk *ResourcePackStackPacket) Decode() {
}
================================================
FILE: net/packets/bedrock/server_handshake.go
================================================
package bedrock
import (
"github.com/irmine/gomine/net/info"
"github.com/irmine/gomine/net/packets"
)
type ServerHandshakePacket struct {
*packets.Packet
Jwt string
}
func NewServerHandshakePacket() *ServerHandshakePacket {
return &ServerHandshakePacket{packets.NewPacket(info.PacketIds[info.ServerHandshakePacket]), ""}
}
func (pk *ServerHandshakePacket) Encode() {
pk.PutString(pk.Jwt)
}
func (pk *ServerHandshakePacket) Decode() {
}
================================================
FILE: net/packets/bedrock/set_entity_data.go
================================================
package bedrock
import (
"github.com/irmine/gomine/net/info"
"github.com/irmine/gomine/net/packets"
)
type SetEntityDataPacket struct {
*packets.Packet
RuntimeId uint64
EntityData map[uint32][]interface{}
}
func NewSetEntityDataPacket() *SetEntityDataPacket {
return &SetEntityDataPacket{packets.NewPacket(info.PacketIds[info.SetEntityDataPacket]), 0, make(map[uint32][]interface{})}
}
func (pk *SetEntityDataPacket) Encode() {
pk.PutEntityRuntimeId(pk.RuntimeId)
pk.PutEntityData(pk.EntityData)
}
func (pk *SetEntityDataPacket) Decode() {
pk.RuntimeId = pk.GetEntityRuntimeId()
pk.EntityData = pk.GetEntityData()
}
================================================
FILE: net/packets/bedrock/start_game.go
================================================
package bedrock
import (
"encoding/base64"
"github.com/golang/geo/r3"
"github.com/irmine/gomine/net/info"
"github.com/irmine/gomine/net/packets"
"github.com/irmine/gomine/net/packets/types"
"github.com/irmine/worlds/blocks"
)
const (
GameBroadcastSettingNone = iota
GameBroadcastSettingInviteOnly
GameBroadcastSettingFriendsOnly
GameBroadcastSettingFriendsOfFriends
GameBroadcastSettingPublic
)
type StartGamePacket struct {
*packets.Packet
EntityUniqueId int64
EntityRuntimeId uint64
PlayerGameMode int32
PlayerPosition r3.Vector
Yaw float32
Pitch float32
LevelSeed int32
Dimension int32
Generator int32
LevelGameMode int32
Difficulty int32
LevelSpawnPosition blocks.Position
AchievementsDisabled bool
Time int32
EduMode bool
EduFeaturesEnabled bool
RainLevel float32
LightningLevel float32
Bool1 bool
MultiPlayerGame bool
BroadcastToLan bool
CommandsEnabled bool
ForcedResourcePacks bool
GameRules map[string]types.GameRuleEntry
BonusChest bool
StartMap bool
DefaultPermissionLevel int32
LevelName string
IsTrial bool
CurrentTick int64
EnchantmentSeed int32
ServerChunkTickRange int32
PlatformBroadcast bool
XBOXBroadcastIntent int32
PlatformBroadcastIntent int32
LockedBehaviorPack bool
LockedResourcePack bool
FromLockedWorldTemplate bool
UseMsaGamertagsOnly bool
FromWorldTemplate bool
WorldTemplateOptionLocked bool
RuntimeIdsTable []byte
MultiplayerCorrelationID string
}
func NewStartGamePacket() *StartGamePacket {
return &StartGamePacket{Packet: packets.NewPacket(info.PacketIds[info.StartGamePacket]), GameRules: make(map[string]types.GameRuleEntry)}
}
func (pk *StartGamePacket) Encode() {
pk.PutEntityUniqueId(pk.EntityUniqueId) // Entity Unique ID
pk.PutEntityRuntimeId(pk.EntityRuntimeId) // Entity runtime ID
pk.PutVarInt(pk.PlayerGameMode) // Player game mode.
pk.PutVector(pk.PlayerPosition) // Player pos.
pk.PutLittleFloat(pk.Pitch) // Pitch
pk.PutLittleFloat(pk.Yaw) // Yaw
pk.PutVarInt(pk.LevelSeed) // Seed
pk.PutVarInt(pk.Dimension) // Dimension
pk.PutVarInt(pk.Generator) // Generator
pk.PutVarInt(pk.LevelGameMode) // World gamemode
pk.PutVarInt(pk.Difficulty) // Difficulty
pk.PutBlockPosition(pk.LevelSpawnPosition) // Spawn pos.
pk.PutBool(pk.AchievementsDisabled) // Achievements disabled
pk.PutVarInt(pk.Time) // Time
pk.PutBool(pk.EduMode) // Education mode
pk.PutBool(pk.EduFeaturesEnabled) // Education mode features enabled
pk.PutLittleFloat(pk.RainLevel) // Rain level
pk.PutLittleFloat(pk.LightningLevel) // Lightning level
pk.PutBool(pk.Bool1)
pk.PutBool(pk.MultiPlayerGame) // Multi-player game
pk.PutBool(pk.BroadcastToLan) // LAN Broadcast
pk.PutVarInt(pk.XBOXBroadcastIntent)
pk.PutVarInt(pk.PlatformBroadcastIntent)
pk.PutBool(pk.CommandsEnabled) // Commands Enabled
pk.PutBool(pk.ForcedResourcePacks) // Texture packs required
pk.PutGameRules(pk.GameRules) // Game rules
pk.PutBool(pk.BonusChest) // Bonus chest
pk.PutBool(pk.StartMap) // Start map
pk.PutVarInt(pk.DefaultPermissionLevel) // Default permission level
pk.PutLittleInt(pk.ServerChunkTickRange) // Server chunk tick range
pk.PutBool(pk.LockedBehaviorPack) // Has Locked Behavior Pack
pk.PutBool(pk.LockedResourcePack) // Has Locked Resource Pack
pk.PutBool(pk.FromLockedWorldTemplate) // From World Locked Template
pk.PutBool(pk.UseMsaGamertagsOnly) // Use Msa Gamertags Only
pk.PutBool(pk.FromWorldTemplate) // From World Template
pk.PutBool(pk.WorldTemplateOptionLocked) // World template option locked
pk.PutString(base64.RawStdEncoding.EncodeToString([]byte(pk.LevelName))) // Level name base64 encoded
pk.PutString(pk.LevelName) // Level name
pk.PutString("") // Premium world template ID
pk.PutBool(pk.IsTrial) // Is Trial
pk.PutLittleLong(pk.CurrentTick) // Tick
pk.PutVarInt(pk.EnchantmentSeed) // Enchantment seed
pk.PutBytes(pk.RuntimeIdsTable)
pk.PutString(pk.MultiplayerCorrelationID)
}
func (pk *StartGamePacket) Decode() {
}
================================================
FILE: net/packets/bedrock/text.go
================================================
package bedrock
import (
"github.com/irmine/gomine/net/info"
"github.com/irmine/gomine/net/packets"
"github.com/irmine/gomine/net/packets/data"
)
type TextPacket struct {
*packets.Packet
TextType byte
Translation bool
SourceName string
Message string
XUID string
PlatformChatId string
Params []string
}
func NewTextPacket() *TextPacket {
return &TextPacket{
packets.NewPacket(info.PacketIds[info.TextPacket]),
data.TextRaw,
false,
"",
"",
"",
"",
[]string{},
}
}
func (pk *TextPacket) Encode() {
pk.PutByte(pk.TextType)
pk.PutBool(pk.Translation)
switch pk.TextType {
case data.TextRaw, data.TextTip, data.TextSystem, data.TextJson:
pk.PutString(pk.Message)
break
case data.TextChat, data.TextWhisper, data.TextAnnouncement:
pk.PutString(pk.SourceName)
pk.PutString(pk.Message)
break
case data.TextTranslation, data.TextPopup, data.TextJukeboxPopup:
pk.PutString(pk.Message)
pk.PutUnsignedVarInt(uint32(len(pk.Params)))
for _, v := range pk.Params {
pk.PutString(v)
}
break
}
pk.PutString(pk.XUID)
pk.PutString(pk.PlatformChatId)
}
func (pk *TextPacket) Decode() {
pk.TextType = pk.GetByte()
pk.Translation = pk.GetBool()
switch pk.TextType {
case data.TextRaw, data.TextTip, data.TextSystem:
pk.Message = pk.GetString()
break
case data.TextChat, data.TextWhisper, data.TextAnnouncement:
pk.SourceName = pk.GetString()
pk.Message = pk.GetString()
break
case data.TextTranslation, data.TextPopup, data.TextJukeboxPopup:
pk.Message = pk.GetString()
c := pk.GetUnsignedVarInt()
for i := uint32(0); i < c; i++ {
pk.Params = append(pk.Params, pk.GetString())
}
break
}
pk.XUID = pk.GetString()
pk.PlatformChatId = pk.GetString()
}
================================================
FILE: net/packets/bedrock/transfer.go
================================================
package bedrock
import (
"github.com/irmine/gomine/net/info"
"github.com/irmine/gomine/net/packets"
)
type TransferPacket struct {
*packets.Packet
Address string
Port uint16
}
func NewTransferPacket() *TransferPacket {
return &TransferPacket{packets.NewPacket(info.PacketIds[info.TransferPacket]), "", 0}
}
func (pk *TransferPacket) Encode() {
pk.PutString(pk.Address)
pk.PutLittleShort(int16(pk.Port))
}
func (pk *TransferPacket) Decode() {
}
================================================
FILE: net/packets/bedrock/update_attributes.go
================================================
package bedrock
import (
"github.com/irmine/gomine/net/info"
"github.com/irmine/gomine/net/packets"
"github.com/irmine/worlds/entities/data"
)
type UpdateAttributesPacket struct {
*packets.Packet
RuntimeId uint64
Attributes data.AttributeMap
}
func NewUpdateAttributesPacket() *UpdateAttributesPacket {
return &UpdateAttributesPacket{packets.NewPacket(info.PacketIds[info.UpdateAttributesPacket]), 0, data.NewAttributeMap()}
}
func (pk *UpdateAttributesPacket) Encode() {
pk.PutEntityRuntimeId(pk.RuntimeId)
pk.PutAttributeMap(pk.Attributes)
}
func (pk *UpdateAttributesPacket) Decode() {
pk.RuntimeId = pk.GetEntityRuntimeId()
pk.Attributes = pk.GetAttributeMap()
}
================================================
FILE: net/packets/bedrock/update_block.go
================================================
package bedrock
import (
"github.com/irmine/gomine/net/info"
"github.com/irmine/gomine/net/packets"
"github.com/irmine/worlds/blocks"
)
const (
DataLayerNormal = iota
DataLayerLiquid
)
type UpdateBlockPacket struct {
*packets.Packet
Position blocks.Position
BlockRuntimeId uint32
Flags uint32
DataLayerId uint32
}
func NewUpdateBlockPacket() *UpdateBlockPacket {
return &UpdateBlockPacket{Packet: packets.NewPacket(info.PacketIds[info.UpdateBlockPacket]), Flags: 0x02, DataLayerId: DataLayerNormal}
}
func (pk *UpdateBlockPacket) Encode() {
pk.PutBlockPosition(pk.Position)
pk.PutUnsignedVarInt(pk.BlockRuntimeId)
pk.PutUnsignedVarInt(pk.Flags)
pk.PutUnsignedVarInt(pk.DataLayerId)
}
func (pk *UpdateBlockPacket) Decode() {
pk.Position = pk.GetBlockPosition()
pk.BlockRuntimeId = pk.GetUnsignedVarInt()
pk.Flags = pk.GetUnsignedVarInt()
pk.DataLayerId = pk.GetUnsignedVarInt()
}
================================================
FILE: net/packets/data/constants.go
================================================
package data
const (
MojangPublicKey = "MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE8ELkixyLcwlZryUQcu1TvPOmI2B7vX83ndnWRUaXm74wFfa5f/lwQNTfrLVHa2PmenpGI6JhIMUJaWZrjmMj90NoKNFSNBuKdm8rYiXsfaz3K36x/1U26HpG0ZxK/V1V"
)
const (
StatusLoginSuccess = iota
StatusLoginFailedClient
StatusLoginFailedServer
StatusSpawn
StatusLoginFailedInvalidTenant
StatusLoginFailedVanillaEdu
StatusLoginFailedEduVanilla
)
const (
MoveNormal = iota
MoveReset
MoveTeleport
MovePitch
)
const (
MoveEntityGround = iota + 1
MoveEntityTeleport
)
const (
StatusRefused = iota + 1
StatusSendPacks
StatusHaveAllPacks
StatusCompleted
)
const (
TextRaw = iota
TextChat
TextTranslation
TextPopup
TextJukeboxPopup
TextTip
TextSystem
TextWhisper
TextAnnouncement
TextJson
)
const (
ResourcePackChunkSize = 1048576
)
const (
ListTypeAdd = iota
ListTypeRemove
)
================================================
FILE: net/packets/minecraft_stream.go
================================================
package packets
import (
"fmt"
"github.com/golang/geo/r3"
"github.com/google/uuid"
"github.com/irmine/binutils"
"github.com/irmine/gomine/items"
"github.com/irmine/gomine/net/packets/types"
"github.com/irmine/gonbt"
"github.com/irmine/worlds/blocks"
"github.com/irmine/worlds/entities/data"
)
// MinecraftStream extends the binutils stream,
// and implements methods for writing types specific
// to the Minecraft bedrock.
type MinecraftStream struct {
// MinecraftStream embeds binutils.Stream.
// Usual binary encoding/decoding functions can
// be called on a MinecraftStream.
*binutils.Stream
}
// NewMinecraftStream reads a new MinecraftStream.
// This stream is pre-initialized and ready for usage.
func NewMinecraftStream() *MinecraftStream {
return &MinecraftStream{binutils.NewStream()}
}
// PutEntityRuntimeId writes the runtime ID of an entity.
// Entity runtime IDs are an uint64.
func (stream *MinecraftStream) PutEntityRuntimeId(id uint64) {
stream.PutUnsignedVarLong(id)
}
// GetEntityRuntimeId reads the runtime ID of an entity.
// Entity runtime IDs are an uint64, and can be looked up
// in the level they belong to.
func (stream *MinecraftStream) GetEntityRuntimeId() uint64 {
return stream.GetUnsignedVarLong()
}
// PutEntityUniqueId writes the unique ID of an entity.
// Entity unique IDs are an int64, and remain the same through sessions.
func (stream *MinecraftStream) PutEntityUniqueId(id int64) {
stream.PutVarLong(id)
}
// GetEntityUniqueId reads the unique ID of an entity.
// Unique IDs will currently always be identical to runtime IDs,
// and will therefore have the same result.
func (stream *MinecraftStream) GetEntityUniqueId() int64 {
return stream.GetVarLong()
}
// PutVector writes a float64 r3.Vector.
// Vector values are first converted to a float32,
// after which they are written little endian.
func (stream *MinecraftStream) PutVector(vector r3.Vector) {
stream.PutLittleFloat(float32(vector.X))
stream.PutLittleFloat(float32(vector.Y))
stream.PutLittleFloat(float32(vector.Z))
}
// GetVector reads a float64 r3.Vector.
// Values read are actually float32, but converted to float64.
func (stream *MinecraftStream) GetVector() r3.Vector {
return r3.Vector{X: float64(stream.GetLittleFloat()), Y: float64(stream.GetLittleFloat()), Z: float64(stream.GetLittleFloat())}
}
// PutBlockPosition writes a position of a block.
// Block positions are always rounded numbers,
// and the Y value is always positive.
func (stream *MinecraftStream) PutBlockPosition(position blocks.Position) {
stream.PutVarInt(position.X)
stream.PutUnsignedVarInt(position.Y)
stream.PutVarInt(position.Z)
}
// GetBlockPosition reads a position of a block.
// Block positions are always rounded numbers,
// and the Y value is always positive.
func (stream *MinecraftStream) GetBlockPosition() blocks.Position {
return blocks.NewPosition(stream.GetVarInt(), stream.GetUnsignedVarInt(), stream.GetVarInt())
}
// PutEntityRotation writes the rotation of an entity in bytes.
// The rotation of an entity will only contain yaw and pitch.
func (stream *MinecraftStream) PutEntityRotationBytes(rotation data.Rotation) {
stream.PutRotationByte(byte(rotation.Pitch))
stream.PutRotationByte(byte(rotation.Yaw))
stream.PutRotationByte(byte(rotation.Yaw))
}
// GetEntityRotation reads the rotation of an entity in bytes.
// The rotation of an entity has no different head yaw,
// which will therefore always be the same as the yaw when returned.
func (stream *MinecraftStream) GetEntityRotationBytes() data.Rotation {
return data.Rotation{Pitch: float64(stream.getRotationByte()), Yaw: float64(stream.getRotationByte()), HeadYaw: float64(stream.getRotationByte())}
}
// PutEntityRotation writes the rotation of an entity.
// The rotation of an entity will only contain yaw and pitch.
func (stream *MinecraftStream) PutEntityRotation(rotation data.Rotation) {
stream.PutLittleFloat(float32(rotation.Pitch))
stream.PutLittleFloat(float32(rotation.Yaw))
stream.PutLittleFloat(float32(rotation.Yaw))
}
// GetEntityRotation reads the rotation of an entity.
// The rotation of an entity has no different head yaw,
// which will therefore always be the same as the yaw when returned.
func (stream *MinecraftStream) GetEntityRotation() data.Rotation {
return data.Rotation{Pitch: float64(stream.GetLittleFloat()), Yaw: float64(stream.GetLittleFloat()), HeadYaw: float64(stream.GetLittleFloat())}
}
// PutPlayerRotation writes the rotation of a player.
// Players have a head yaw too, which gets written.
func (stream *MinecraftStream) PutPlayerRotation(rot data.Rotation) {
stream.PutLittleFloat(float32(rot.Pitch))
stream.PutLittleFloat(float32(rot.Yaw))
stream.PutLittleFloat(float32(rot.Yaw))
}
// GetPlayerRotation reads the rotation of a player.
// Players are supposed to have a different head yaw than normal yaw,
// but since recent updates the head yaw and yaw are always the same.
func (stream *MinecraftStream) GetPlayerRotation() data.Rotation {
return data.Rotation{Pitch: float64(stream.GetLittleFloat()), Yaw: float64(stream.GetLittleFloat()), HeadYaw: float64(stream.GetLittleFloat())}
}
func (stream *MinecraftStream) PutRotationByte(rot byte){
stream.PutByte(rot / (360 / 256))
}
func (stream *MinecraftStream) getRotationByte() byte {
return stream.GetByte() * (360 / 256)
}
// PutAttributeMap writes the attribute map of an entity.
// The amount of attributes of the map is written,
// after which the attribute properties follow.
func (stream *MinecraftStream) PutAttributeMap(m data.AttributeMap) {
stream.PutUnsignedVarInt(uint32(len(m)))
for _, v := range m {
stream.PutLittleFloat(v.MinValue)
stream.PutLittleFloat(v.MaxValue)
stream.PutLittleFloat(v.Value)
stream.PutLittleFloat(v.DefaultValue)
stream.PutString(string(v.GetName()))
}
}
// GetAttributeMap reads an attribute map of an entity.
// There may be attributes in this attribute map that are
// not set in the default attribute map, or missing attributes.
func (stream *MinecraftStream) GetAttributeMap() data.AttributeMap {
m := data.NewAttributeMap()
c := stream.GetUnsignedVarInt()
for i := uint32(0); i < c; i++ {
min := stream.GetLittleFloat()
max := stream.GetLittleFloat()
value := stream.GetLittleFloat()
defaultValue := stream.GetLittleFloat()
name := data.AttributeName(stream.GetString())
att := data.NewAttribute(name, value, max)
att.DefaultValue = defaultValue
att.MinValue = min
m.SetAttribute(att)
}
return m
}
// PutItem writes an item stack.
// Item stacks also get their NBT written to network,
// through the call of Stack.EmitNBT().
func (stream *MinecraftStream) PutItem(item *items.Stack) {
id, v := items.FromKey(items.TypeToId[fmt.Sprint(item.Type)])
stream.PutVarInt(int32(id))
stream.PutVarInt(item.GetAuxValue(item, v))
writer := gonbt.NewWriter(true, binutils.LittleEndian)
compound := gonbt.NewCompound("", make(map[string]gonbt.INamedTag))
item.NBTEmitFunction(compound, item)
writer.WriteUncompressedCompound(compound)
d := writer.GetBuffer()
stream.PutLittleShort(int16(len(d)))
stream.PutBytes(d)
// Fields for canPlaceOn and canBreak are not implemented.
// TODO
stream.PutVarInt(0)
stream.PutVarInt(0)
}
// GetItem reads a new item stack.
// The item stack returned may have NBT properties.
// If the item ID was unknown, an air item gets returned.
func (stream *MinecraftStream) GetItem() *items.Stack {
id := stream.GetVarInt()
if id <= 0 {
i, _ := items.DefaultManager.Get("minecraft:air", 0)
return i
}
aux := stream.GetVarInt()
itemData := aux >> 8
t := items.IdToType[items.GetKey(int16(id), int16(itemData))]
count := aux & 0xff
var nbtLength int16
var nbtData *gonbt.Compound
item, _ := items.DefaultManager.Get(t.GetId(), int(count))
nbtLength = stream.GetLittleShort()
//text.DefaultLogger.Debug(nbtLength)
if nbtLength > 0 {
reader := gonbt.NewReader(stream.Get(int(nbtLength)), true, binutils.LittleEndian)
nbtData = reader.ReadUncompressedIntoCompound()
}else if nbtLength == -1 {
nbtCount := stream.GetUnsignedVarInt()
for i := uint32(0); i < nbtCount; i++ {
reader := gonbt.NewReader(stream.Buffer[stream.Offset:], true, binutils.LittleEndian)
nbtData = reader.ReadUncompressedIntoCompound()
stream.Offset += reader.GetOffset()
}
}
if nbtData != nil {
item.NBTParseFunction(nbtData, item)
}
// Fields for canPlaceOn and canBreak are not implemented.
// TODO
canPlace := stream.GetVarInt()
if canPlace > 0 {
for i := int32(0); i < canPlace; i++ {
stream.GetString()
}
}
canBreak := stream.GetVarInt()
if canBreak > 0 {
for i := int32(0); i < canBreak; i++ {
stream.GetString()
}
}
//text.DefaultLogger.Debug(canPlace, canBreak)
return item
}
// PutEntityData writes the data properties of an entity.
func (stream *MinecraftStream) PutEntityData(entityData map[uint32][]interface{}) {
var count= uint32(len(entityData))
stream.PutUnsignedVarInt(count)
for key, dataValues := range entityData {
stream.PutUnsignedVarInt(key)
var flagId, ok = dataValues[0].(uint32)
if !ok {
stream.PutUnsignedVarInt(999999) // invalid flag id
continue
}
stream.PutUnsignedVarInt(flagId)
switch flagId {
case data.EntityDataByte:
if value, ok := dataValues[1].(byte); ok {
stream.PutByte(value)
}
break
case data.EntityDataShort:
if value, ok := dataValues[1].(int16); ok {
stream.PutLittleShort(value)
}
break
case data.EntityDataInt:
if value, ok := dataValues[1].(int32); ok {
stream.PutVarInt(value)
}
break
case data.EntityDataFloat:
if value, ok := dataValues[1].(float32); ok {
stream.PutLittleFloat(value)
}
break
case data.EntityDataString:
if value, ok := dataValues[1].(string); ok {
stream.PutString(value)
}
break
case data.EntityDataItem:
if value, ok := dataValues[1].(*items.Stack); ok {
stream.PutItem(value)
}
break
case data.EntityDataPos:
if value, ok := dataValues[1].(blocks.Position); ok {
stream.PutBlockPosition(value)
}
break
case data.EntityDataLong:
if value, ok := dataValues[1].(int64); ok {
stream.PutVarLong(value)
}
break
case data.EntityDataVector:
if value, ok := dataValues[1].(r3.Vector); ok {
stream.PutVector(value)
}
break
}
}
}
// GetEntityData reads an entity data property map from an entity.
func (stream *MinecraftStream) GetEntityData() map[uint32][]interface{} {
entityData := make(map[uint32][]interface{})
count := stream.GetUnsignedVarInt()
if count > 0 {
for i := uint32(0); i < count; i++ {
var key = stream.GetUnsignedVarInt()
var flagId = stream.GetUnsignedVarInt()
switch flagId {
case data.EntityDataByte:
entityData[key] = []interface{}{flagId, stream.GetByte()}
break
case data.EntityDataShort:
entityData[key] = []interface{}{flagId, stream.GetLittleShort()}
break
case data.EntityDataInt:
entityData[key] = []interface{}{flagId, stream.GetVarInt()}
break
case data.EntityDataFloat:
entityData[key] = []interface{}{flagId, stream.GetLittleFloat()}
break
case data.EntityDataString:
entityData[key] = []interface{}{flagId, stream.GetString()}
break
case data.EntityDataItem:
entityData[key] = []interface{}{flagId, stream.GetItem()}
break
case data.EntityDataPos:
entityData[key] = []interface{}{flagId, stream.GetBlockPosition()}
break
case data.EntityDataLong:
entityData[key] = []interface{}{flagId, stream.GetVarLong()}
break
case data.EntityDataVector:
entityData[key] = []interface{}{flagId, stream.GetVector()}
break
}
}
}
return entityData
}
// PutGameRules writes a map of game rules.
// Game rules get prefixed by the type of the game rule,
// 1 being bool, 2 being uint32, 3 being float32.
func (stream *MinecraftStream) PutGameRules(gameRules map[string]types.GameRuleEntry) {
stream.PutUnsignedVarInt(uint32(len(gameRules)))
for _, gameRule := range gameRules {
stream.PutString(gameRule.Name)
switch value := gameRule.Value.(type) {
case bool:
stream.PutUnsignedVarInt(1)
stream.PutBool(value)
case uint32:
stream.PutUnsignedVarInt(2)
stream.PutUnsignedVarInt(value)
case float32:
stream.PutUnsignedVarInt(3)
stream.PutLittleFloat(value)
}
}
}
// PutPackInfo writes the info of an array of resource pack entries.
// The UUID, version and pack size gets written.
func (stream *MinecraftStream) PutPackInfo(packs []types.ResourcePackInfoEntry) {
stream.PutLittleShort(int16(len(packs)))
for _, pack := range packs {
stream.PutString(pack.UUID)
stream.PutString(pack.Version)
stream.PutLittleLong(pack.PackSize)
stream.PutString("")
stream.PutString("")
stream.PutString("")
stream.PutBool(false)
}
}
// PutPackStack writes an array of resource pack entries.
// The order of this array specifies the order the client should apply those,
// with index 0 meaning highest priority.
func (stream *MinecraftStream) PutPackStack(packs []types.ResourcePackStackEntry) {
stream.PutUnsignedVarInt(uint32(len(packs)))
for _, pack := range packs {
stream.PutString(pack.UUID)
stream.PutString(pack.Version)
stream.PutString("")
}
}
// PutUUID writes a UUID.
// UUIDs are first re-ordered for little endian byte order,
// after which they get written.
func (stream *MinecraftStream) PutUUID(uuid uuid.UUID) {
b, err := uuid.MarshalBinary()
if err != nil {
panic(err)
}
for i, j := 0, len(b)-1; i < j; i, j = i+1, j-1 {
b[i], b[j] = b[j], b[i]
}
stream.PutBytes(b[8:])
stream.PutBytes(b[:8])
}
// GetUUID reads a UUID.
// TODO: Re-order for little endian byte order. Order gets messed up.
func (stream *MinecraftStream) GetUUID() uuid.UUID {
return uuid.Must(uuid.FromBytes(stream.Get(16)))
}
================================================
FILE: net/packets/packet.go
================================================
package packets
import (
"github.com/irmine/gomine/text"
)
// IPacket gets implemented by every packet.
// Every packet can be encoded and decoded.
type IPacket interface {
SetBuffer([]byte)
GetBuffer() []byte
EncodeHeader()
Encode()
DecodeHeader()
Decode()
ResetStream()
GetOffset() int
SetOffset(int)
Discard()
IsDiscarded() bool
EncodeId()
DecodeId()
GetId() int
}
// Packet is a Minecraft bedrock packet.
// Packets have a given ID and contain two prefix
// bytes, which are used for split screen.
// Packets can be discarded during handling
// of the packets to stop other handlers from
// handling those packets.
type Packet struct {
*MinecraftStream
// PacketId is the ID of the packet.
// Packet IDs may differ for different protocols.
PacketId int
// SenderIdentifier is used for split screen.
// It specifies the sender sub ID.
SenderIdentifier byte
// ReceiverIdentifier is used for split screen.
// It specifies the receiver sub ID.
ReceiverIdentifier byte
discarded bool
}
// NewPacket returns a new packet with packet ID.
// The packet's stream gets pre-initialized.
func NewPacket(id int) *Packet {
return &Packet{NewMinecraftStream(), id, 0, 0, false}
}
// GetId returns the packet ID of the packet.
func (pk *Packet) GetId() int {
return pk.PacketId
}
// Discard discards the packet.
// Once discarded, handlers will no longer
// handle this packet.
func (pk *Packet) Discard() {
pk.discarded = true
}
// IsDiscard checks if a packet has been discarded.
// Discarded packets are no longer processed,
// and get disposed immediately.
func (pk *Packet) IsDiscarded() bool {
return pk.discarded
}
// EncodeId encodes the ID of the packet.
func (pk *Packet) EncodeId() {
pk.PutUnsignedVarInt(uint32(pk.PacketId))
}
// DecodeId decodes the packet ID of the packet.
// The function panics if the packet ID
// and read ID do not match.
func (pk *Packet) DecodeId() {
id := int(pk.GetUnsignedVarInt())
if id != pk.PacketId {
text.DefaultLogger.Debug("Packet IDs do not match. Expected:", pk.PacketId, "Got:", id)
}
}
// EncodeHeader encodes the header of a packet,
// with bedrock >= 200.
// First the packet ID gets encoded,
// after which the sender and receiver ID bytes get written.
func (pk *Packet) EncodeHeader() {
pk.EncodeId()
}
// DecodeHeader decodes a header of a packet,
// with bedrock >= 200.
// First the packet ID gets decoded,
// after which the sender and receiver ID bytes.
func (pk *Packet) DecodeHeader() {
pk.DecodeId()
}
func (pk *Packet) Encode() {}
func (pk *Packet) Decode() {}
================================================
FILE: net/packets/types/levels.go
================================================
package types
type GameRuleEntry struct {
Name string
Value interface{}
}
================================================
FILE: net/packets/types/net.go
================================================
package types
type ChainDataKeys struct {
RawChains []string `json:"chain"`
Chains []Chain
}
type Chain struct {
Header ChainHeader
Payload ChainPayload
Signature string
}
type ChainHeader struct {
X5u string `json:"x5u"`
Alg string `json:"alg"`
Raw string
}
type ChainPayload struct {
CertificateAuthority bool `json:"certificateAuthority"`
ExpirationTime int64 `json:"exp"`
IdentityPublicKey string `json:"identityPublicKey"`
NotBefore int64 `json:"nbf"`
RandomNonce int `json:"randomNonce"`
Issuer string `json:"iss"`
IssuedAt int64 `json:"iat"`
Raw string
}
type WebTokenKeys struct {
ExtraData map[string]interface{} `json:"extraData"`
IdentityPublicKey string `json:"identityPublicKey"`
}
type ClientDataKeys struct {
ClientRandomId int `json:"ClientRandomId"`
ServerAddress string `json:"ServerAddress"`
LanguageCode string `json:"LanguageCode"`
SkinId string `json:"SkinId"`
SkinData string `json:"SkinData"`
CapeData string `json:"CapeData"`
GeometryId string `json:"SkinGeometryName"`
GeometryData string `json:"SkinGeometry"`
CurrentInputMode string `json:"CurrentInputMode"`
DefaultInputMode string `json:"DefaultInputMode"`
DeviceModel string `json:"DeviceModel"`
DeviceOS int `json:"DeviceOS"`
GameVersion string `json:"GameVersion"`
GuiScale int `json:"GuiScale"`
UIProfile int `json:"UIProfile"`
ThirdPartyName string `json:"ThirdPartyName"`
}
================================================
FILE: net/packets/types/players.go
================================================
package types
import (
"github.com/google/uuid"
)
type PlayerListEntry struct {
UUID uuid.UUID
XUID string
EntityUniqueId int64
Username string
DisplayName string
Platform int32
SkinId string
SkinData []byte
CapeData []byte
GeometryName string
GeometryData string
}
type SessionData struct {
ClientUUID uuid.UUID
ClientXUID string
ClientId int
ProtocolNumber int32
GameVersion string
Language string
DeviceOS int
}
type Text struct {
Message string
SourceName string
SourceXUID string
PlatformChatId string
TextType byte
IsTranslation bool
TranslationParameters []string
}
================================================
FILE: net/packets/types/resource_packs.go
================================================
package types
type ResourcePackInfoEntry struct {
UUID string
Version string
PackSize int64
}
type ResourcePackStackEntry struct {
UUID string
Version string
}
================================================
FILE: net/protocol/entries.go
================================================
package protocol
import (
"github.com/golang/geo/r3"
"github.com/google/uuid"
"github.com/irmine/worlds"
"github.com/irmine/worlds/entities/data"
)
type AddEntityEntry interface {
GetUniqueId() int64
GetRuntimeId() uint64
GetEntityType() uint32
GetPosition() r3.Vector
GetMotion() r3.Vector
GetRotation() data.Rotation
GetAttributeMap() data.AttributeMap
GetEntityData() map[uint32][]interface{}
}
type AddPlayerEntry interface {
AddEntityEntry
GetDisplayName() string
GetName() string
}
type PlayerListEntry interface {
AddPlayerEntry
GetXUID() string
GetUUID() uuid.UUID
GetSkinId() string
GetSkinData() []byte
GetCapeData() []byte
GetGeometryName() string
GetGeometryData() string
GetPlatform() int32
}
type StartGameEntry interface {
GetRuntimeId() uint64
GetUniqueId() int64
GetPosition() r3.Vector
GetDimension() *worlds.Dimension
}
================================================
FILE: net/protocol/handler.go
================================================
package protocol
// Handler is an interface satisfied by every packet handler.
type Handler interface {
GetPriority() int
SetPriority(int) bool
}
================================================
FILE: net/protocol/protocol.go
================================================
package protocol
import (
"github.com/golang/geo/r3"
"github.com/google/uuid"
"github.com/irmine/gomine/net/info"
"github.com/irmine/gomine/net/packets"
"github.com/irmine/gomine/net/packets/types"
"github.com/irmine/gomine/packs"
"github.com/irmine/worlds/blocks"
"github.com/irmine/worlds/chunks"
"github.com/irmine/worlds/entities/data"
)
type IPacketManager interface {
GetIdList() info.PacketIdList
GetHandlers(packet info.PacketName) [][]Handler
GetHandlersById(id int) [][]Handler
RegisterHandler(packet info.PacketName, handler Handler) bool
DeregisterPacketHandlers(packet info.PacketName, priority int)
GetPackets() map[int]func() packets.IPacket
RegisterPacket(packetId int, packetFunc func() packets.IPacket)
GetPacket(packetId int) packets.IPacket
IsPacketRegistered(packetId int) bool
GetAddEntity(AddEntityEntry) packets.IPacket
GetAddPlayer(uuid.UUID, AddPlayerEntry) packets.IPacket
GetChunkRadiusUpdated(int32) packets.IPacket
GetCraftingData() packets.IPacket
GetDisconnect(string, bool) packets.IPacket
GetFullChunkData(*chunks.Chunk) packets.IPacket
GetMovePlayer(uint64, r3.Vector, data.Rotation, byte, bool, uint64) packets.IPacket
GetPlayerList(byte, map[string]PlayerListEntry) packets.IPacket
GetPlayStatus(int32) packets.IPacket
GetRemoveEntity(int64) packets.IPacket
GetResourcePackChunkData(string, int32, int64, []byte) packets.IPacket
GetResourcePackDataInfo(packs.Pack) packets.IPacket
GetResourcePackInfo(bool, *packs.Stack, *packs.Stack) packets.IPacket
GetResourcePackStack(bool, *packs.Stack, *packs.Stack) packets.IPacket
GetServerHandshake(string) packets.IPacket
GetSetEntityData(uint64, map[uint32][]interface{}) packets.IPacket
GetStartGame(StartGameEntry, []byte) packets.IPacket
GetText(types.Text) packets.IPacket
GetTransfer(string, uint16) packets.IPacket
GetUpdateAttributes(uint64, data.AttributeMap) packets.IPacket
GetNetworkChunkPublisherUpdatePacket(position blocks.Position, radius uint32) packets.IPacket
GetMoveEntity(uint64, r3.Vector, data.Rotation, byte, bool) packets.IPacket
GetPlayerSkin(uuid2 uuid.UUID, skinId, geometryName, geometryData string, skinData, capeData []byte) packets.IPacket
GetPlayerAction(runtimeId uint64, action int32, position blocks.Position, face int32) packets.IPacket
GetAnimate(action int32, runtimeId uint64, float float32) packets.IPacket
GetUpdateBlock(position blocks.Position, blockRuntimeId, dataLayerId uint32) packets.IPacket
}
// PacketManagerBase is a struct providing the base for a PacketManagerBase.
// It provides utility functions for a basic PacketManagerBase implementation.
type PacketManagerBase struct {
idList info.PacketIdList
packets map[int]func() packets.IPacket
handlers map[int][][]Handler
}
// NewBase returns a new PacketManagerBase with the given PacketManagerBase number and packets.
func NewPacketManagerBase(idList info.PacketIdList, packets map[int]func() packets.IPacket, handlers map[int][][]Handler) *PacketManagerBase {
return &PacketManagerBase{idList, packets, handlers}
}
// GetIdList returns the packet name => Id list of the bedrock.
func (Base *PacketManagerBase) GetIdList() info.PacketIdList {
return Base.idList
}
// GetHandlers returns all handlers registered for the given packet name.
func (Base *PacketManagerBase) GetHandlers(packet info.PacketName) [][]Handler {
var id = Base.idList[packet]
return Base.handlers[id]
}
// GetHandlersById returns all handlers registered on the given ID.
func (Base *PacketManagerBase) GetHandlersById(id int) [][]Handler {
return Base.handlers[id]
}
// RegisterHandler registers a new packet handler to listen for packets with the given ID.
// This function uses the priority of the handler.
// Returns a bool indicating success.
func (Base *PacketManagerBase) RegisterHandler(packet info.PacketName, handler Handler) bool {
var id = Base.idList[packet]
if Base.handlers[id] == nil {
Base.handlers[id] = make([][]Handler, 11)
}
Base.handlers[id][handler.GetPriority()] = append(Base.handlers[id][handler.GetPriority()], handler)
return true
}
// DeregisterPackHandlers deregisters all packet handlers listening for packets with the given ID, on the given priority.
func (Base *PacketManagerBase) DeregisterPacketHandlers(packet info.PacketName, priority int) {
var id = Base.idList[packet]
Base.handlers[id][priority] = []Handler{}
}
// GetPackets returns a packet ID => packet function map containing all registered packets.
func (Base *PacketManagerBase) GetPackets() map[int]func() packets.IPacket {
return Base.packets
}
// RegisterPacket registers a packet function with the given packet ID.
func (Base *PacketManagerBase) RegisterPacket(packetId int, packetFunc func() packets.IPacket) {
Base.packets[packetId] = packetFunc
}
// GetPacket returns a packet with the given packet ID.
func (Base *PacketManagerBase) GetPacket(packetId int) packets.IPacket {
return Base.packets[packetId]()
}
// IsPacketRegistered checks if the PacketManagerBase has a packet with the given packet ID.
func (Base *PacketManagerBase) IsPacketRegistered(packetId int) bool {
var _, ok = Base.packets[packetId]
return ok
}
================================================
FILE: net/protocol_adapter.go
================================================
package net
import (
"github.com/golang/geo/r3"
"github.com/google/uuid"
"github.com/irmine/gomine/net/packets/types"
"github.com/irmine/gomine/net/protocol"
"github.com/irmine/gomine/packs"
"github.com/irmine/worlds/blocks"
"github.com/irmine/worlds/chunks"
"github.com/irmine/worlds/entities/data"
)
func (session *MinecraftSession) SendAddEntity(entity protocol.AddEntityEntry) {
session.SendPacket(session.adapter.packetManager.GetAddEntity(entity))
}
func (session *MinecraftSession) SendAddPlayer(uuid uuid.UUID, player protocol.AddPlayerEntry) {
session.SendPacket(session.adapter.packetManager.GetAddPlayer(uuid, player))
}
func (session *MinecraftSession) SendChunkRadiusUpdated(radius int32) {
session.SendPacket(session.adapter.packetManager.GetChunkRadiusUpdated(radius))
}
func (session *MinecraftSession) SendCraftingData() {
session.SendPacket(session.adapter.packetManager.GetCraftingData())
}
func (session *MinecraftSession) SendDisconnect(message string, hideDisconnect bool) {
session.SendPacket(session.adapter.packetManager.GetDisconnect(message, hideDisconnect))
}
func (session *MinecraftSession) SendFullChunkData(chunk *chunks.Chunk) {
session.SendPacket(session.adapter.packetManager.GetFullChunkData(chunk))
}
func (session *MinecraftSession) SendMovePlayer(runtimeId uint64, position r3.Vector, rotation data.Rotation, mode byte, onGround bool, ridingRuntimeId uint64) {
session.SendPacket(session.adapter.packetManager.GetMovePlayer(runtimeId, position, rotation, mode, onGround, ridingRuntimeId))
}
func (session *MinecraftSession) SendPlayerList(listType byte, players map[string]protocol.PlayerListEntry) {
session.SendPacket(session.adapter.packetManager.GetPlayerList(listType, players))
}
func (session *MinecraftSession) SendPlayStatus(status int32) {
session.SendPacket(session.adapter.packetManager.GetPlayStatus(status))
}
func (session *MinecraftSession) SendRemoveEntity(uniqueId int64) {
session.SendPacket(session.adapter.packetManager.GetRemoveEntity(uniqueId))
}
func (session *MinecraftSession) SendResourcePackChunkData(packUUID string, chunkIndex int32, progress int64, data []byte) {
session.SendPacket(session.adapter.packetManager.GetResourcePackChunkData(packUUID, chunkIndex, progress, data))
}
func (session *MinecraftSession) SendResourcePackDataInfo(pack packs.Pack) {
session.SendPacket(session.adapter.packetManager.GetResourcePackDataInfo(pack))
}
func (session *MinecraftSession) SendResourcePackInfo(mustAccept bool, resourcePacks *packs.Stack, behaviorPacks *packs.Stack) {
session.SendPacket(session.adapter.packetManager.GetResourcePackInfo(mustAccept, resourcePacks, behaviorPacks))
}
func (session *MinecraftSession) SendResourcePackStack(mustAccept bool, resourcePacks *packs.Stack, behaviorPacks *packs.Stack) {
session.SendPacket(session.adapter.packetManager.GetResourcePackStack(mustAccept, resourcePacks, behaviorPacks))
}
func (session *MinecraftSession) SendServerHandshake(encryptionJwt string) {
session.SendPacket(session.adapter.packetManager.GetServerHandshake(encryptionJwt))
}
func (session *MinecraftSession) SendSetEntityData(runtimeId uint64, data map[uint32][]interface{}) {
session.SendPacket(session.adapter.packetManager.GetSetEntityData(runtimeId, data))
}
func (session *MinecraftSession) SendStartGame(player protocol.StartGameEntry, runtimeIdsTable []byte) {
session.SendPacket(session.adapter.packetManager.GetStartGame(player, runtimeIdsTable))
}
func (session *MinecraftSession) SendText(text types.Text) {
session.SendPacket(session.adapter.packetManager.GetText(text))
}
func (session *MinecraftSession) Transfer(address string, port uint16) {
session.SendPacket(session.adapter.packetManager.GetTransfer(address, port))
}
func (session *MinecraftSession) SendUpdateAttributes(runtimeId uint64, attributes data.AttributeMap) {
session.SendPacket(session.adapter.packetManager.GetUpdateAttributes(runtimeId, attributes))
}
func (session *MinecraftSession) SendNetworkChunkPublisherUpdate(position blocks.Position, radius uint32) {
session.SendPacket(session.adapter.packetManager.GetNetworkChunkPublisherUpdatePacket(position, radius))
}
func (session *MinecraftSession) SendMoveEntity(runtimeId uint64, position r3.Vector, rot data.Rotation, flags byte, teleport bool) {
session.SendPacket(session.adapter.packetManager.GetMoveEntity(runtimeId, position, rot, flags, teleport))
}
func (session *MinecraftSession) SendPlayerSkin(uuid2 uuid.UUID, skinId, geometryName, geometryData string, skinData, capeData []byte) {
session.SendPacket(session.adapter.packetManager.GetPlayerSkin(uuid2, skinId, geometryName, geometryData, skinData, capeData))
}
func (session *MinecraftSession) SendPlayerAction(runtimeId uint64, action int32, position blocks.Position, face int32) {
session.SendPacket(session.adapter.packetManager.GetPlayerAction(runtimeId, action, position, face))
}
func (session *MinecraftSession) SendAnimate(action int32, runtimeId uint64, float float32) {
session.SendPacket(session.adapter.packetManager.GetAnimate(action, runtimeId, float))
}
func (session *MinecraftSession) SendUpdateBlock(position blocks.Position, blockRuntimeId, dataLayerId uint32) {
session.SendPacket(session.adapter.packetManager.GetUpdateBlock(position, blockRuntimeId, dataLayerId))
}
================================================
FILE: packet_handler.go
================================================
package gomine
import (
"crypto/ecdsa"
"crypto/sha512"
"crypto/x509"
"encoding/base64"
"github.com/golang/geo/r3"
"github.com/irmine/gomine/net"
"github.com/irmine/gomine/net/info"
"github.com/irmine/gomine/net/packets"
"github.com/irmine/gomine/net/packets/bedrock"
"github.com/irmine/gomine/net/packets/data"
"github.com/irmine/gomine/net/packets/types"
"github.com/irmine/gomine/net/protocol"
"github.com/irmine/gomine/players"
"github.com/irmine/gomine/text"
"github.com/irmine/gomine/utils"
"github.com/irmine/worlds/blocks"
"github.com/irmine/worlds/chunks"
data2 "github.com/irmine/worlds/entities/data"
utils2 "github.com/irmine/worlds/utils"
"math/big"
"strings"
"time"
)
func NewClientHandshakeHandler(server *Server) *net.PacketHandler {
return net.NewPacketHandler(func(packet packets.IPacket, session *net.MinecraftSession) bool {
if _, ok := packet.(*bedrock.ClientHandshakePacket); ok {
session.SendPlayStatus(data.StatusLoginSuccess)
session.SendResourcePackInfo(server.Config.ForceResourcePacks, server.PackManager.GetResourceStack(), server.PackManager.GetBehaviorStack())
return true
}
return false
})
}
func NewCommandRequestHandler(server *Server) *net.PacketHandler {
return net.NewPacketHandler(func(packet packets.IPacket, session *net.MinecraftSession) bool {
if pk, ok := packet.(*bedrock.CommandRequestPacket); ok {
var args = strings.Split(pk.CommandText, " ")
var commandName = strings.TrimLeft(args[0], "/")
var i = 1
for !server.CommandManager.IsCommandRegistered(commandName) {
if i == len(args) {
break
}
commandName += " " + args[i]
i++
}
if !server.CommandManager.IsCommandRegistered(commandName) {
session.SendMessage("Command could not be found.")
return false
}
args = args[i:]
var command, _ = server.CommandManager.GetCommand(commandName)
command.Execute(session, args)
return true
}
return false
})
}
func NewLoginHandler(server *Server) *net.PacketHandler {
return net.NewPacketHandler(func(packet packets.IPacket, session *net.MinecraftSession) bool {
if loginPacket, ok := packet.(*bedrock.LoginPacket); ok {
var _, ok = server.SessionManager.GetSession(loginPacket.Username)
if ok {
return false
}
if loginPacket.Protocol > info.LatestProtocol {
session.Kick("Outdated server.", false, true)
return false
}
if loginPacket.Protocol < info.LatestProtocol {
session.Kick("Outdated client.", false, true)
return false
}
var successful, authenticated, pubKey = VerifyLoginRequest(loginPacket.Chains, server)
if !successful {
text.DefaultLogger.Debug(loginPacket.Username, "has joined with invalid login data.")
return true
}
if authenticated {
text.DefaultLogger.Debug(loginPacket.Username, "has joined while being logged into XBOX Live.")
} else {
if server.Config.XBOXLiveAuth {
text.DefaultLogger.Debug(loginPacket.Username, "has tried to join while not being logged into XBOX Live.")
session.Kick("XBOX Live account required.", false, false)
return true
}
text.DefaultLogger.Debug(loginPacket.Username, "has joined while not being logged into XBOX Live.")
}
session.SetData(server.PermissionManager, types.SessionData{ClientUUID: loginPacket.ClientUUID, ClientXUID: loginPacket.ClientXUID, ClientId: loginPacket.ClientId, ProtocolNumber: loginPacket.Protocol, GameVersion: loginPacket.ClientData.GameVersion, Language: loginPacket.Language, DeviceOS: loginPacket.ClientData.DeviceOS})
session.SetPlayer(players.NewPlayer(loginPacket.ClientUUID, loginPacket.ClientXUID, int32(loginPacket.ClientData.DeviceOS), loginPacket.Username))
session.GetEncryptionHandler().Data = &utils.EncryptionData{
ClientPublicKey: pubKey,
ServerPrivateKey: server.GetPrivateKey(),
ServerToken: server.GetServerToken(),
}
session.GetPlayer().SetName(loginPacket.Username)
session.GetPlayer().SetDisplayName(loginPacket.Username)
session.GetPlayer().SetSkinId(loginPacket.SkinId)
session.GetPlayer().SetSkinData(loginPacket.SkinData)
session.GetPlayer().SetCapeData(loginPacket.CapeData)
session.GetPlayer().SetGeometryName(loginPacket.GeometryName)
session.GetPlayer().SetGeometryData(loginPacket.GeometryData)
session.SetXBOXLiveAuthenticated(authenticated)
if server.Config.UseEncryption {
var jwt = utils.ConstructEncryptionJwt(server.GetPrivateKey(), server.GetServerToken())
session.SendServerHandshake(jwt)
session.EnableEncryption()
} else {
session.SendPlayStatus(data.StatusLoginSuccess)
session.SendResourcePackInfo(server.Config.ForceResourcePacks, server.PackManager.GetResourceStack(), server.PackManager.GetBehaviorStack())
}
server.SessionManager.AddMinecraftSession(session)
return true
}
return false
})
}
func NewMovePlayerHandler(_ *Server) *net.PacketHandler {
return net.NewPacketHandler(func(packet packets.IPacket, session *net.MinecraftSession) bool {
if pk, ok := packet.(*bedrock.MovePlayerPacket); ok {
if session.GetPlayer().GetDimension() == nil {
return false
}
session.SyncMove(pk.Position.X, pk.Position.Y, pk.Position.Z, pk.Rotation.Pitch, pk.Rotation.Yaw, pk.Rotation.HeadYaw, pk.OnGround)
return true
}
return false
})
}
func NewRequestChunkRadiusHandler(server *Server) *net.PacketHandler {
return net.NewPacketHandler(func(packet packets.IPacket, session *net.MinecraftSession) bool {
if chunkRadiusPacket, ok := packet.(*bedrock.RequestChunkRadiusPacket); ok {
var viewDistance = server.GetAllowedViewDistance(chunkRadiusPacket.Radius)
session.SetViewDistance(viewDistance)
session.SendChunkRadiusUpdated(viewDistance)
var sessions = server.SessionManager.GetSessions()
var viewers = make(map[string]protocol.PlayerListEntry)
for name, online := range sessions {
if online.HasSpawned() {
viewers[name] = online.GetPlayer()
online.SendPlayerList(data.ListTypeAdd, map[string]protocol.PlayerListEntry{session.GetName(): session.GetPlayer()})
}
}
session.SendPlayerList(data.ListTypeAdd, viewers)
for _, online := range server.SessionManager.GetSessions() {
if session.GetUUID() != online.GetUUID() {
online.GetPlayer().SpawnPlayerTo(session)
online.GetPlayer().AddViewer(session)
session.GetPlayer().SpawnPlayerTo(online)
session.GetPlayer().AddViewer(online)
online.SendSkin(session)
session.SendSkin(online)
}
}
session.SendSetEntityData(session.GetPlayer().GetRuntimeId(), session.GetPlayer().GetEntityData())
session.SendUpdateAttributes(session.GetPlayer().GetRuntimeId(), session.GetPlayer().GetAttributeMap())
server.BroadcastMessage(text.Yellow+session.GetDisplayName(), "has joined the server")
session.SendPlayStatus(data.StatusSpawn)
session.Connected = true
return true
}
return false
})
}
func NewResourcePackChunkRequestHandler(server *Server) *net.PacketHandler {
return net.NewPacketHandler(func(packet packets.IPacket, session *net.MinecraftSession) bool {
if request, ok := packet.(*bedrock.ResourcePackChunkRequestPacket); ok {
if !server.PackManager.IsPackLoaded(request.PackUUID) {
// TODO: Kick the player. We can't kick yet.
return false
}
var pack = server.PackManager.GetPack(request.PackUUID)
session.SendResourcePackChunkData(request.PackUUID, request.ChunkIndex, int64(data.ResourcePackChunkSize*request.ChunkIndex), pack.GetChunk(int(data.ResourcePackChunkSize*request.ChunkIndex), data.ResourcePackChunkSize))
return true
}
return false
})
}
func NewResourcePackClientResponseHandler(server *Server) *net.PacketHandler {
return net.NewPacketHandler(func(packet packets.IPacket, session *net.MinecraftSession) bool {
if response, ok := packet.(*bedrock.ResourcePackClientResponsePacket); ok {
switch response.Status {
case data.StatusRefused:
// TODO: Kick the player. We can't kick yet.
return false
case data.StatusSendPacks:
for _, packUUID := range response.PackUUIDs {
if !server.PackManager.IsPackLoaded(packUUID) {
// TODO: Kick the player. We can't kick yet.
return false
}
session.SendResourcePackDataInfo(server.PackManager.GetPack(packUUID))
}
case data.StatusHaveAllPacks:
session.SendResourcePackStack(server.Config.ForceResourcePacks, server.PackManager.GetResourceStack(), server.PackManager.GetBehaviorStack())
case data.StatusCompleted:
server.LevelManager.GetDefaultLevel().GetDefaultDimension().LoadChunk(0, 0, func(chunk *chunks.Chunk) {
server.LevelManager.GetDefaultLevel().GetDefaultDimension().AddEntity(session.GetPlayer(), r3.Vector{X: 0, Y: 7, Z: 0})
server.LevelManager.GetDefaultLevel().GetDefaultDimension().AddViewer(session, r3.Vector{X: 0, Y: 7, Z: 0})
session.SendStartGame(session.GetPlayer(), blocks.GetRuntimeIdsTable())
session.SendCraftingData()
})
}
return true
}
return false
})
}
func NewTextHandler(server *Server) *net.PacketHandler {
return net.NewPacketHandler(func(packet packets.IPacket, session *net.MinecraftSession) bool {
if textPacket, ok := packet.(*bedrock.TextPacket); ok {
if textPacket.TextType != data.TextChat {
return false
}
for _, receiver := range server.SessionManager.GetSessions() {
receiver.SendText(types.Text{
Message: "<" + session.GetDisplayName() + "> " + textPacket.Message,
PlatformChatId: textPacket.PlatformChatId,
SourceXUID: session.GetXUID(),
TextType: data.TextChat,
})
}
text.DefaultLogger.LogChat("<" + session.GetDisplayName() + "> " + textPacket.Message)
return true
}
return false
})
}
func NewInteractHandler(_ *Server) *net.PacketHandler {
return net.NewPacketHandler(func(packet packets.IPacket, session *net.MinecraftSession) bool {
if /*interactPacket*/ _, ok := packet.(*bedrock.InteractPacket); ok {
}
return true
})
}
func NewPlayerActionHandler(_ *Server) *net.PacketHandler {
return net.NewPacketHandler(func(packet packets.IPacket, session *net.MinecraftSession) bool {
//TODO: fix sending to others
if playerAction, ok := packet.(*bedrock.PlayerActionPacket); ok {
switch playerAction.Action {
case bedrock.PlayerStartSneak:
session.GetPlayer().SetEntityProperty(data2.EntityDataSneaking, true)
break
case bedrock.PlayerStopSneak:
session.GetPlayer().SetEntityProperty(data2.EntityDataSneaking, false)
break
case bedrock.PlayerStartSprint:
session.GetPlayer().SetEntityProperty(data2.EntityDataSprinting, true)
break
case bedrock.PlayerStopSprint:
session.GetPlayer().SetEntityProperty(data2.EntityDataSprinting, false)
break
}
}
return true
})
}
func NewAnimateHandler(_ *Server) *net.PacketHandler {
return net.NewPacketHandler(func(packet packets.IPacket, session *net.MinecraftSession) bool {
if animate, ok := packet.(*bedrock.AnimatePacket); ok {
for _, viewer := range session.GetPlayer().GetViewers() {
if viewer, ok := viewer.(*net.MinecraftSession); ok {
viewer.SendAnimate(animate.Action, animate.RuntimeId, animate.Float)
}
}
}
return true
})
}
func NewInventoryTransactionHandler(_ *Server) *net.PacketHandler {
return net.NewPacketHandler(func(packet packets.IPacket, session *net.MinecraftSession) bool {
if invTransaction, ok := packet.(*bedrock.InventoryTransactionPacket); ok {
var clickPos = invTransaction.BlockPosition
switch invTransaction.TransactionType {
case bedrock.UseItem:
switch invTransaction.ActionType {
case bedrock.ItemBreakBlock:
runtimeId, ok := blocks.GetRuntimeId(0, 0)
if ok {
var block= blocks.New(blocks.NewBlockState("air", int32(runtimeId), 0, 0))
session.GetPlayer().GetDimension().SetBlockAt(utils2.PositionToVector(clickPos), block)
}
break
case bedrock.ItemClickBlock:
// TODO: do block placing
break
}
break
}
}
return true
})
}
func VerifyLoginRequest(chains []types.Chain, _ *Server) (successful bool, authenticated bool, clientPublicKey *ecdsa.PublicKey) {
var publicKey *ecdsa.PublicKey
var publicKeyRaw string
for _, chain := range chains {
if publicKeyRaw == "" {
if chain.Header.X5u == "" {
return
}
publicKeyRaw = chain.Header.X5u
}
sig := []byte(chain.Signature)
d := []byte(chain.Header.Raw + "." + chain.Payload.Raw)
var b64, errB64 = base64.RawStdEncoding.DecodeString(publicKeyRaw)
text.DefaultLogger.LogError(errB64)
key, err := x509.ParsePKIXPublicKey(b64)
if err != nil {
text.DefaultLogger.LogError(err)
return
}
hash := sha512.New384()
hash.Write(d)
publicKey = key.(*ecdsa.PublicKey)
r := new(big.Int).SetBytes(sig[:len(sig)/2])
s := new(big.Int).SetBytes(sig[len(sig)/2:])
if !ecdsa.Verify(publicKey, hash.Sum(nil), r, s) {
return
}
if publicKeyRaw == data.MojangPublicKey {
authenticated = true
}
t := time.Now().Unix()
if chain.Payload.ExpirationTime <= t && chain.Payload.ExpirationTime != 0 || chain.Payload.NotBefore > t || chain.Payload.IssuedAt > chain.Payload.ExpirationTime {
return
}
publicKeyRaw = chain.Payload.IdentityPublicKey
}
var b64, errB64 = base64.RawStdEncoding.DecodeString(publicKeyRaw)
text.DefaultLogger.LogError(errB64)
key, err := x509.ParsePKIXPublicKey(b64)
if err != nil {
text.DefaultLogger.LogError(err)
return
}
clientPublicKey = key.(*ecdsa.PublicKey)
successful = true
return
}
================================================
FILE: packet_manager.go
================================================
package gomine
import (
"github.com/golang/geo/r3"
"github.com/google/uuid"
"github.com/irmine/gomine/net/info"
"github.com/irmine/gomine/net/packets"
"github.com/irmine/gomine/net/packets/bedrock"
"github.com/irmine/gomine/net/packets/data"
"github.com/irmine/gomine/net/packets/types"
"github.com/irmine/gomine/net/protocol"
"github.com/irmine/gomine/packs"
"github.com/irmine/gomine/permissions"
"github.com/irmine/worlds/blocks"
"github.com/irmine/worlds/chunks"
data2 "github.com/irmine/worlds/entities/data"
"math"
)
type PacketManager struct {
*protocol.PacketManagerBase
}
func NewPacketManager(server *Server) *PacketManager {
var ids = info.PacketIds
var proto = &PacketManager{protocol.NewPacketManagerBase(info.PacketIds, map[int]func() packets.IPacket{
ids[info.LoginPacket]: func() packets.IPacket { return bedrock.NewLoginPacket() },
ids[info.ClientHandshakePacket]: func() packets.IPacket { return bedrock.NewClientHandshakePacket() },
ids[info.ResourcePackClientResponsePacket]: func() packets.IPacket { return bedrock.NewResourcePackClientResponsePacket() },
ids[info.RequestChunkRadiusPacket]: func() packets.IPacket { return bedrock.NewRequestChunkRadiusPacket() },
ids[info.MovePlayerPacket]: func() packets.IPacket { return bedrock.NewMovePlayerPacket() },
ids[info.CommandRequestPacket]: func() packets.IPacket { return bedrock.NewCommandRequestPacket() },
ids[info.ResourcePackChunkRequestPacket]: func() packets.IPacket { return bedrock.NewResourcePackChunkRequestPacket() },
ids[info.TextPacket]: func() packets.IPacket { return bedrock.NewTextPacket() },
ids[info.PlayerListPacket]: func() packets.IPacket { return bedrock.NewPlayerListPacket() },
ids[info.InteractPacket]: func() packets.IPacket { return bedrock.NewInteractPacket() },
ids[info.SetEntityDataPacket]: func() packets.IPacket { return bedrock.NewSetEntityDataPacket() },
ids[info.PlayerActionPacket]: func() packets.IPacket { return bedrock.NewPlayerActionPacket() },
ids[info.AnimatePacket]: func() packets.IPacket { return bedrock.NewAnimatePacket() },
ids[info.InventoryTransactionPacket]: func() packets.IPacket { return bedrock.NewInventoryTransactionPacket() },
}, map[int][][]protocol.Handler{})}
proto.initHandlers(server)
return proto
}
func (protocol *PacketManager) initHandlers(server *Server) {
protocol.RegisterHandler(info.LoginPacket, NewLoginHandler(server))
protocol.RegisterHandler(info.ClientHandshakePacket, NewClientHandshakeHandler(server))
protocol.RegisterHandler(info.RequestChunkRadiusPacket, NewRequestChunkRadiusHandler(server))
protocol.RegisterHandler(info.ResourcePackClientResponsePacket, NewResourcePackClientResponseHandler(server))
protocol.RegisterHandler(info.MovePlayerPacket, NewMovePlayerHandler(server))
protocol.RegisterHandler(info.CommandRequestPacket, NewCommandRequestHandler(server))
protocol.RegisterHandler(info.ResourcePackChunkRequestPacket, NewResourcePackChunkRequestHandler(server))
protocol.RegisterHandler(info.TextPacket, NewTextHandler(server))
protocol.RegisterHandler(info.InteractPacket, NewInteractHandler(server))
protocol.RegisterHandler(info.PlayerActionPacket, NewPlayerActionHandler(server))
protocol.RegisterHandler(info.AnimatePacket, NewAnimateHandler(server))
protocol.RegisterHandler(info.InventoryTransactionPacket, NewInventoryTransactionHandler(server))
}
func (protocol *PacketManager) GetAddEntity(entity protocol.AddEntityEntry) packets.IPacket {
var pk = bedrock.NewAddEntityPacket()
pk.UniqueId = entity.GetUniqueId()
pk.RuntimeId = entity.GetRuntimeId()
pk.EntityType = entity.GetEntityType()
pk.Position = entity.GetPosition()
pk.Motion = entity.GetMotion()
pk.Rotation = entity.GetRotation()
pk.Attributes = entity.GetAttributeMap()
pk.EntityData = entity.GetEntityData()
return pk
}
func (protocol *PacketManager) GetAddPlayer(uuid uuid.UUID, player protocol.AddPlayerEntry) packets.IPacket {
var pk = bedrock.NewAddPlayerPacket()
pk.UUID = uuid
pk.Username = player.GetName()
pk.EntityRuntimeId = player.GetRuntimeId()
pk.EntityUniqueId = player.GetUniqueId()
pk.Position = player.GetPosition()
pk.Rotation = player.GetRotation()
pk.Motion = player.GetMotion()
return pk
}
func (protocol *PacketManager) GetChunkRadiusUpdated(radius int32) packets.IPacket {
var pk = bedrock.NewChunkRadiusUpdatedPacket()
pk.Radius = radius
return pk
}
func (protocol *PacketManager) GetCraftingData() packets.IPacket {
var pk = bedrock.NewCraftingDataPacket()
return pk
}
func (protocol *PacketManager) GetDisconnect(message string, hideDisconnectScreen bool) packets.IPacket {
var pk = bedrock.NewDisconnectPacket()
pk.HideDisconnectionScreen = hideDisconnectScreen
pk.Message = message
return pk
}
func (protocol *PacketManager) GetFullChunkData(chunk *chunks.Chunk) packets.IPacket {
var pk = bedrock.NewFullChunkDataPacket()
pk.ChunkX, pk.ChunkZ = chunk.X, chunk.Z
pk.ChunkData = chunk.ToBinary()
return pk
}
func (protocol *PacketManager) GetMovePlayer(runtimeId uint64, position r3.Vector, rotation data2.Rotation, mode byte, onGround bool, ridingRuntimeId uint64) packets.IPacket {
var pk = bedrock.NewMovePlayerPacket()
pk.RuntimeId = runtimeId
pk.Position = position
pk.Rotation = rotation
pk.Mode = mode
pk.OnGround = onGround
pk.RidingRuntimeId = ridingRuntimeId
return pk
}
func (protocol *PacketManager) GetPlayerList(listType byte, players map[string]protocol.PlayerListEntry) packets.IPacket {
var pk = bedrock.NewPlayerListPacket()
pk.ListType = listType
var entries = map[string]types.PlayerListEntry{}
for name, player := range players {
entries[name] = types.PlayerListEntry{
UUID: player.GetUUID(),
XUID: player.GetXUID(),
EntityUniqueId: player.GetUniqueId(),
Username: player.GetName(),
DisplayName: player.GetDisplayName(),
Platform: player.GetPlatform(),
SkinId: player.GetSkinId(),
SkinData: player.GetSkinData(),
CapeData: player.GetCapeData(),
GeometryName: player.GetGeometryName(),
GeometryData: player.GetGeometryData(),
}
}
pk.Entries = entries
return pk
}
func (protocol *PacketManager) GetPlayStatus(status int32) packets.IPacket {
var pk = bedrock.NewPlayStatusPacket()
pk.Status = status
return pk
}
func (protocol *PacketManager) GetRemoveEntity(uniqueId int64) packets.IPacket {
var pk = bedrock.NewRemoveEntityPacket()
pk.EntityUniqueId = uniqueId
return pk
}
func (protocol *PacketManager) GetResourcePackChunkData(packUUID string, chunkIndex int32, progress int64, data []byte) packets.IPacket {
var pk = bedrock.NewResourcePackChunkDataPacket()
pk.PackUUID = packUUID
pk.ChunkIndex = chunkIndex
pk.Progress = progress
pk.ChunkData = data
return pk
}
func (protocol *PacketManager) GetResourcePackDataInfo(pack packs.Pack) packets.IPacket {
var pk = bedrock.NewResourcePackDataInfoPacket()
pk.PackUUID = pack.GetUUID()
pk.MaxChunkSize = data.ResourcePackChunkSize
pk.ChunkCount = int32(math.Ceil(float64(pack.GetFileSize()) / float64(data.ResourcePackChunkSize)))
pk.CompressedPackSize = pack.GetFileSize()
pk.Sha256 = pack.GetSha256()
return pk
}
func (protocol *PacketManager) GetResourcePackInfo(mustAccept bool, resourcePacks *packs.Stack, behaviorPacks *packs.Stack) packets.IPacket {
var pk = bedrock.NewResourcePackInfoPacket()
pk.MustAccept = mustAccept
var resourceEntries []types.ResourcePackInfoEntry
var behaviorEntries []types.ResourcePackInfoEntry
for _, pack := range *resourcePacks {
resourceEntries = append(resourceEntries, types.ResourcePackInfoEntry{
UUID: pack.GetUUID(),
Version: pack.GetVersion(),
PackSize: pack.GetFileSize(),
})
}
for _, pack := range *behaviorPacks {
behaviorEntries = append(behaviorEntries, types.ResourcePackInfoEntry{
UUID: pack.GetUUID(),
Version: pack.GetVersion(),
PackSize: pack.GetFileSize(),
})
}
pk.ResourcePacks = resourceEntries
pk.BehaviorPacks = behaviorEntries
return pk
}
func (protocol *PacketManager) GetResourcePackStack(mustAccept bool, resourcePacks *packs.Stack, behaviorPacks *packs.Stack) packets.IPacket {
var pk = bedrock.NewResourcePackStackPacket()
pk.MustAccept = mustAccept
var resourceEntries []types.ResourcePackStackEntry
var behaviorEntries []types.ResourcePackStackEntry
for _, pack := range *resourcePacks {
resourceEntries = append(resourceEntries, types.ResourcePackStackEntry{
UUID: pack.GetUUID(),
Version: pack.GetVersion(),
})
}
for _, pack := range *behaviorPacks {
behaviorEntries = append(behaviorEntries, types.ResourcePackStackEntry{
UUID: pack.GetUUID(),
Version: pack.GetVersion(),
})
}
pk.ResourcePacks = resourceEntries
pk.BehaviorPacks = behaviorEntries
return pk
}
func (protocol *PacketManager) GetServerHandshake(encryptionJwt string) packets.IPacket {
var pk = bedrock.NewServerHandshakePacket()
pk.Jwt = encryptionJwt
return pk
}
func (protocol *PacketManager) GetSetEntityData(runtimeId uint64, data map[uint32][]interface{}) packets.IPacket {
var pk = bedrock.NewSetEntityDataPacket()
pk.RuntimeId = runtimeId
pk.EntityData = data
return pk
}
func (protocol *PacketManager) GetStartGame(player protocol.StartGameEntry, runtimeIdsTable []byte) packets.IPacket {
var pk = bedrock.NewStartGamePacket()
pk.Generator = 1
pk.LevelSeed = 312402
pk.DefaultPermissionLevel = permissions.LevelMember
pk.EntityRuntimeId = player.GetRuntimeId()
pk.EntityUniqueId = player.GetUniqueId()
pk.PlayerGameMode = 1
pk.PlayerPosition = player.GetPosition()
pk.LevelGameMode = 1
pk.LevelSpawnPosition = blocks.NewPosition(0, 7, 0)
pk.CommandsEnabled = true
var gameRules = player.GetDimension().GetLevel().GetGameRules()
var gameRuleEntries = map[string]types.GameRuleEntry{}
for name, gameRule := range gameRules {
gameRuleEntries[string(name)] = types.GameRuleEntry{Name: string(gameRule.GetName()), Value: gameRule.GetValue()}
}
pk.GameRules = gameRuleEntries
pk.LevelName = player.GetDimension().GetLevel().GetName()
pk.CurrentTick = player.GetDimension().GetLevel().GetCurrentTick()
pk.Time = 0
pk.AchievementsDisabled = true
pk.BroadcastToLan = true
pk.RuntimeIdsTable = runtimeIdsTable
pk.PlatformBroadcastIntent = bedrock.GameBroadcastSettingPublic
pk.XBOXBroadcastIntent = bedrock.GameBroadcastSettingPublic
return pk
}
func (protocol *PacketManager) GetText(text types.Text) packets.IPacket {
var pk = bedrock.NewTextPacket()
pk.TextType = text.TextType
pk.Translation = text.IsTranslation
pk.Params = text.TranslationParameters
pk.SourceName = text.SourceName
pk.XUID = text.SourceXUID
pk.Message = text.Message
return pk
}
func (protocol *PacketManager) GetTransfer(address string, port uint16) packets.IPacket {
var pk = bedrock.NewTransferPacket()
pk.Address = address
pk.Port = port
return pk
}
func (protocol *PacketManager) GetUpdateAttributes(runtimeId uint64, attributeMap data2.AttributeMap) packets.IPacket {
var pk = bedrock.NewUpdateAttributesPacket()
pk.RuntimeId = runtimeId
pk.Attributes = attributeMap
return pk
}
func (protocol *PacketManager) GetNetworkChunkPublisherUpdatePacket(position blocks.Position, radius uint32) packets.IPacket {
var pk = bedrock.NewNetworkChunkPublisherUpdatePacket()
pk.Position = position
pk.Radius = radius
return pk
}
func (protocol *PacketManager) GetMoveEntity(runtimeId uint64, position r3.Vector, rot data2.Rotation, flags byte, teleport bool) packets.IPacket {
var pk = bedrock.NewMoveEntityPacket()
pk.RuntimeId = runtimeId
pk.Position = position
pk.Rotation = rot
pk.Flags = flags
if teleport {
pk.Flags |= data.MoveEntityTeleport
}
return pk
}
func (protocol *PacketManager) GetPlayerSkin(uuid2 uuid.UUID, skinId, geometryName, geometryData string, skinData, capeData []byte) packets.IPacket {
var pk = bedrock.NewPlayerSkinPacket()
pk.UUID = uuid2
pk.SkinId = skinId
pk.SkinData = skinData
pk.CapeData = capeData
pk.GeometryName = geometryName
pk.GeometryData = geometryData
return pk
}
func (protocol *PacketManager) GetPlayerAction(runtimeId uint64, action int32, position blocks.Position, face int32) packets.IPacket {
var pk = bedrock.NewPlayerActionPacket()
pk.RuntimeId = runtimeId
pk.Action = action
pk.Position = position
pk.Face = face
return pk
}
func (protocol *PacketManager) GetAnimate(action int32, runtimeId uint64, float float32) packets.IPacket {
var pk = bedrock.NewAnimatePacket()
pk.RuntimeId = runtimeId
pk.Action = action
pk.Float = float
return pk
}
func (protocol *PacketManager) GetUpdateBlock(position blocks.Position, blockRuntimeId, dataLayerId uint32) packets.IPacket {
var pk = bedrock.NewUpdateBlockPacket()
pk.Position = position
pk.BlockRuntimeId = blockRuntimeId
pk.DataLayerId = dataLayerId
return pk
}
================================================
FILE: packs/base.go
================================================
package packs
import (
"archive/zip"
"crypto/sha256"
"encoding/json"
"errors"
"io/ioutil"
"os"
"strconv"
"strings"
)
const (
Behavior PackType = "data"
Resource PackType = "resources"
)
// PackType is a name of a pack type.
type PackType string
// Pack is the main interface which both Resource- and BehaviorPack satisfy.
type Pack interface {
GetUUID() string
GetVersion() string
GetFileSize() int64
GetSha256() string
GetChunk(offset int, length int) []byte
GetPath() string
}
// PacketManagerBase is a struct that forms the base of every pack.
// It has functions for loading, validating and pack data.
type Base struct {
packPath string
manifest *Manifest
content []byte
size int64
sha256 []byte
packType PackType
}
// Manifest is a struct that contains all information of a pack.
type Manifest struct {
Header struct {
Description string `json:"description"`
Name string `json:"name"`
UUID string `json:"uuid"`
Version []float64 `json:"version"`
VersionString string
} `json:"header"`
Modules []struct {
Description string `json:"description"`
Type string `json:"type"`
UUID string `json:"uuid"`
Version []float64 `json:"version"`
} `json:"modules"`
Dependencies []struct {
Description string `json:"description"`
Type string `json:"type"`
UUID string `json:"uuid"`
Version []float64 `json:"version"`
} `json:"dependencies"`
}
// newBase returns a new base at the given path and with the given pack type.
func newBase(path string, packType PackType) *Base {
var reader, _ = os.Open(path)
var content, _ = ioutil.ReadAll(reader)
var sha = sha256.Sum256(content)
var shaBytes []byte
for _, b := range sha {
shaBytes = append(shaBytes, b)
}
return &Base{path, &Manifest{}, content, int64(len(content)), shaBytes, packType}
}
// Load loads the pack, and returns an error if any.
func (pack *Base) Load() error {
var zipFile, err = zip.OpenReader(pack.packPath)
if err != nil {
panic(err)
}
for _, file := range zipFile.File {
if file.Name != "manifest.json" && file.Name != "pack_manifest.json" {
continue
}
reader, _ := file.Open()
bytes, _ := ioutil.ReadAll(reader)
manifest := &Manifest{}
err := json.Unmarshal(bytes, manifest)
pack.manifest = manifest
reader.Close()
return err
}
return errors.New("No manifest.json or pack_manifest.json could be found in zip: " + pack.packPath)
}
// GetPath returns the path of the pack.
func (pack *Base) GetPath() string {
return pack.packPath
}
// GetSha256 returns the Sha256 checksum of the pack.
func (pack *Base) GetSha256() string {
return string(pack.sha256)
}
// GetFileSize returns the file size of the pack.
func (pack *Base) GetFileSize() int64 {
return pack.size
}
// GetUUID returns the UUID of the pack.
func (pack *Base) GetUUID() string {
return pack.manifest.Header.UUID
}
// GetVersion returns the version string of the pack.
func (pack *Base) GetVersion() string {
return pack.manifest.Header.VersionString
}
// GetManifest returns the manifest of the pack.
func (pack *Base) GetManifest() *Manifest {
return pack.manifest
}
// GetContent returns the full byte array of the data of the pack.
func (pack *Base) GetContent() []byte {
return pack.content
}
// ValidateManifest validates the manifest, and returns an error if any.
func (pack *Base) ValidateManifest() error {
var manifest = pack.manifest
if manifest.Header.Description == "" {
return errors.New("Pack at " + pack.packPath + " is missing a description.")
}
if manifest.Header.Name == "" {
return errors.New("Pack at " + pack.packPath + " is missing a name.")
}
if len(manifest.Header.Version) < 2 {
return errors.New("Pack at " + pack.packPath + " is missing a valid version.")
}
var versionStrings []string
for _, versionNumber := range manifest.Header.Version {
versionStrings = append(versionStrings, strconv.Itoa(int(versionNumber)))
}
manifest.Header.VersionString = strings.Join(versionStrings, ".")
return pack.ValidateModules()
}
// ValidateModules validates the modules of the pack, and returns an error if any.
func (pack *Base) ValidateModules() error {
var modules = pack.manifest.Modules
if len(modules) == 0 {
return errors.New("Pack at " + pack.packPath + " doesn't have any modules.")
}
for index, module := range modules {
if module.Description == "" {
return errors.New("Module " + strconv.Itoa(index) + " in pack at " + pack.packPath + " is missing a description.")
}
if len(module.Version) < 2 {
return errors.New("Module " + strconv.Itoa(index) + " in pack at " + pack.packPath + " is missing a valid version.")
}
if module.Type == "" {
return errors.New("Module " + strconv.Itoa(index) + " in pack at " + pack.packPath + " is missing a valid type.")
}
}
return nil
}
// GetChunk returns a chunk of the pack at the given offset with the given length.
func (pack *Base) GetChunk(offset int, length int) []byte {
if offset > len(pack.content) || offset < 0 || length < 1 {
return []byte{}
}
if offset+length > len(pack.content) {
length = int(pack.size) - offset
}
return pack.content[offset : offset+length]
}
================================================
FILE: packs/behavior.go
================================================
package packs
import (
"errors"
"strconv"
)
// BehaviorPack is a pack used to modify the behavior of entities.
type BehaviorPack struct {
*Base
}
// NewBehaviorPack returns a new behavior pack at the given path.
func NewBehaviorPack(path string) *BehaviorPack {
return &BehaviorPack{newBase(path, Behavior)}
}
// ValidateDependencies validates all dependencies of the behavior pack, and returns an error if any.
func (pack *BehaviorPack) ValidateDependencies(manager *Manager) error {
var dependencies = pack.manifest.Dependencies
for index, dependency := range dependencies {
if dependency.Description == "" {
return errors.New("Dependency " + strconv.Itoa(index) + " in pack at " + pack.packPath + " is missing a description.")
}
if !manager.IsResourcePackLoaded(dependency.UUID) {
return errors.New("Dependency with UUID: " + dependency.UUID + " is not loaded.")
}
if len(dependency.Version) < 2 {
return errors.New("Dependency " + strconv.Itoa(index) + " in pack at " + pack.packPath + " is missing a valid version.")
}
if dependency.Type != string(Resource) {
return errors.New("Dependency " + strconv.Itoa(index) + " in pack at " + pack.packPath + " is missing the correct type. Expected: 'resources', got: '" + dependency.Type + "'")
}
}
return nil
}
================================================
FILE: packs/manager.go
================================================
package packs
import (
"github.com/irmine/gomine/text"
"io/ioutil"
"path/filepath"
)
// Manager manages the loading of packs.
// It provides helper functions for both types of packs.
type Manager struct {
serverPath string
resourcePacks map[string]*ResourcePack
resourceStack *Stack
behaviorPacks map[string]*BehaviorPack
behaviorStack *Stack
}
// NewManager returns a new pack manager with the given path.
func NewManager(serverPath string) *Manager {
return &Manager{serverPath, make(map[string]*ResourcePack), NewStack(), make(map[string]*BehaviorPack), NewStack()}
}
// GetResourcePacks returns all resource maps in a UUID => pack map.
func (manager *Manager) GetResourcePacks() map[string]*ResourcePack {
return manager.resourcePacks
}
// GetBehaviorPacks returns all behavior packs in a UUID => pack map.
func (manager *Manager) GetBehaviorPacks() map[string]*BehaviorPack {
return manager.behaviorPacks
}
// GetResourceStack returns the resource pack stack.
func (manager *Manager) GetResourceStack() *Stack {
return manager.resourceStack
}
// GetBehaviorStack returns the behavior pack stack.
func (manager *Manager) GetBehaviorStack() *Stack {
return manager.behaviorStack
}
// LoadResourcePacks loads all resource packs in the `serverPath/extensions/resource_packs/` folder.
// It returns an array of errors that occurred during the loading of all resource packs.
func (manager *Manager) LoadResourcePacks() []error {
var path = manager.serverPath + "extensions/resource_packs/"
var files, _ = ioutil.ReadDir(path)
var errors []error
for _, file := range files {
if file.IsDir() {
continue
}
extension := filepath.Ext(file.Name())
if extension != ".mcpack" && extension != ".zip" {
continue
}
filePath := path + file.Name()
resourcePack := NewResourcePack(filePath)
err := resourcePack.Load()
if err != nil {
errors = append(errors, err)
continue
}
err = resourcePack.ValidateManifest()
if err != nil {
errors = append(errors, err)
continue
}
manager.resourcePacks[resourcePack.manifest.Header.UUID] = resourcePack
text.DefaultLogger.Info("Loaded resource pack:", text.Yellow+resourcePack.manifest.Header.Name)
manager.GetResourceStack().Push(resourcePack)
}
return errors
}
// LoadBehaviorPacks loads all behavior packs in the `serverPath/extensions/behavior_packs/` folder.
// It returns an array of errors that occurred during the loading of all behavior packs.
func (manager *Manager) LoadBehaviorPacks() []error {
var path = manager.serverPath + "extensions/behavior_packs/"
var files, _ = ioutil.ReadDir(path)
var errors []error
for _, file := range files {
if file.IsDir() {
continue
}
extension := filepath.Ext(file.Name())
if extension != ".mcpack" && extension != ".zip" {
continue
}
filePath := path + file.Name()
behaviorPack := NewBehaviorPack(filePath)
err := behaviorPack.Load()
if err != nil {
errors = append(errors, err)
continue
}
err = behaviorPack.ValidateManifest()
if err != nil {
errors = append(errors, err)
continue
}
err = behaviorPack.ValidateDependencies(manager)
if err != nil {
errors = append(errors, err)
continue
}
manager.behaviorPacks[behaviorPack.manifest.Header.UUID] = behaviorPack
manager.GetBehaviorStack().Push(behaviorPack)
}
return errors
}
// IsResourcePackLoaded checks if a resource pack with the given UUID is loaded.
func (manager *Manager) IsResourcePackLoaded(uuid string) bool {
var _, exists = manager.resourcePacks[uuid]
return exists
}
// IsBehaviorPackLoaded checks if a behavior pack with the given UUID is loaded.
func (manager *Manager) IsBehaviorPackLoaded(uuid string) bool {
var _, exists = manager.behaviorPacks[uuid]
return exists
}
// IsPackLoaded checks if any pack with the given UUID is loaded.
func (manager *Manager) IsPackLoaded(uuid string) bool {
return manager.IsResourcePackLoaded(uuid) || manager.IsBehaviorPackLoaded(uuid)
}
// GetResourcePack returns a resource pack by its UUID, or nil of none was found.
func (manager *Manager) GetResourcePack(uuid string) *ResourcePack {
if !manager.IsResourcePackLoaded(uuid) {
return nil
}
return manager.resourcePacks[uuid]
}
// GetBehaviorPack returns a behavior pack by its UUID, or nil if none was found.
func (manager *Manager) GetBehaviorPack(uuid string) *BehaviorPack {
if !manager.IsBehaviorPackLoaded(uuid) {
return nil
}
return manager.behaviorPacks[uuid]
}
// GetPack returns any pack that has the given UUID, or nil if none was found.
func (manager *Manager) GetPack(uuid string) Pack {
if manager.GetResourcePack(uuid) != nil {
return manager.GetResourcePack(uuid)
}
return manager.GetBehaviorPack(uuid)
}
================================================
FILE: packs/resource.go
================================================
package packs
// ResourcePack is a pack that modifies the visual side of game play.
type ResourcePack struct {
*Base
}
// NewResourcePack returns a new resource pack with the given path.
func NewResourcePack(path string) *ResourcePack {
return &ResourcePack{newBase(path, Resource)}
}
================================================
FILE: packs/stack.go
================================================
package packs
// Stack is a struct that allows ordering the stack of packs.
type Stack []Pack
// NewStack returns a new pack stack.
func NewStack() *Stack {
return &Stack{}
}
// GetPackAtOffset returns the pack at the given offset on the stack.
func (stack *Stack) GetPackAtOffset(offset int) Pack {
return (*stack)[offset]
}
// Pop removes the pack on top of the stack.
func (stack *Stack) Pop() {
*stack = (*stack)[1:]
}
// Push adds the given pack on top of the stack.
func (stack *Stack) Push(pack Pack) {
*stack = append([]Pack{pack}, *stack...)
}
// Swap swaps the packs at the given offsets with each other.
func (stack *Stack) Swap(offset1, offset2 int) {
pack1 := (*stack)[offset1]
pack2 := (*stack)[offset2]
(*stack)[offset1] = pack2
(*stack)[offset2] = pack1
}
// Len returns the length of the stack.
func (stack *Stack) Len() int {
return len(*stack)
}
// Peek returns the top pack of the stack.
func (stack *Stack) Peek() Pack {
return (*stack)[0]
}
================================================
FILE: permissions/group.go
================================================
package permissions
// Group is a struct used for basic permission managing.
// Groups can be granted a set of permissions.
type Group struct {
name string
level int
permissions map[string]*Permission
}
// NewGroup returns a new group with the given name and permission level.
func NewGroup(name string, level int) *Group {
return &Group{name, level, make(map[string]*Permission)}
}
// GetName returns the name of the group.
func (group *Group) GetName() string {
return group.name
}
// GetPermissions returns a name => permission map of all permissions of the group.
func (group *Group) GetPermissions() map[string]*Permission {
return group.permissions
}
// HasPermission checks if the group has a permission with the name.
func (group *Group) HasPermission(permission string) bool {
var _, ok = group.permissions[permission]
return ok
}
// AddPermission adds a permission to the group.
func (group *Group) AddPermission(permission *Permission) {
group.permissions[permission.GetName()] = permission
}
// RemovePermission removes a permission with the given name from the group.
func (group *Group) RemovePermission(permission string) {
delete(group.permissions, permission)
}
// InheritGroup inherits all permissions from a group.
func (group *Group) InheritGroup(inheritedGroup *Group) {
for _, permission := range inheritedGroup.GetPermissions() {
group.AddPermission(permission)
}
}
================================================
FILE: permissions/level.go
================================================
package permissions
const (
LevelVisitor PermissionLevel = iota
LevelMember = 1
LevelOperator = 2
LevelCustom = 3
)
// A Permission level is used to connect groups with permissions.
type PermissionLevel byte
================================================
FILE: permissions/manager.go
================================================
package permissions
import (
"errors"
)
// Manager is a struct used to manage permissions and groups.
// It provides helper functions and functions to register groups and permissions.
type Manager struct {
defaultGroup *Group
permissions map[string]*Permission
groups map[string]*Group
}
var (
UnknownPermission = errors.New("unknown permission")
UnknownGroup = errors.New("unknown group")
)
// NewManager returns a new permission manager.
func NewManager() *Manager {
return &Manager{nil, make(map[string]*Permission), make(map[string]*Group)}
}
// GetDefaultGroup returns the default group of the manager.
func (manager *Manager) GetDefaultGroup() *Group {
return manager.defaultGroup
}
// SetDefaultGroup sets the default group of the manager.
func (manager *Manager) SetDefaultGroup(group *Group) {
manager.defaultGroup = group
}
// AddGroup adds a new group to the manager.
func (manager *Manager) AddGroup(group *Group) {
manager.groups[group.GetName()] = group
}
// GetGroup returns a group in the manager with the given name and an error if it could not be found.
func (manager *Manager) GetGroup(name string) (*Group, error) {
if !manager.GroupExists(name) {
return nil, UnknownGroup
}
return manager.groups[name], nil
}
// GroupExists checks if a group with the given name exists.
func (manager *Manager) GroupExists(name string) bool {
var _, ok = manager.groups[name]
return ok
}
// RemoveGroup removes a group with the given name from the manager.
func (manager *Manager) RemoveGroup(name string) {
delete(manager.groups, name)
}
// GetPermission returns a permission by its name, and an error if it could not be found.
func (manager *Manager) GetPermission(name string) (*Permission, error) {
if !manager.IsPermissionRegistered(name) {
return nil, UnknownPermission
}
return manager.permissions[name], nil
}
// IsPermissionRegistered checks if a permission with the given name is registered.
func (manager *Manager) IsPermissionRegistered(name string) bool {
var _, ok = manager.permissions[name]
return ok
}
// RegisterPermission registers a new permission.
func (manager *Manager) RegisterPermission(permission *Permission) {
manager.permissions[permission.GetName()] = permission
}
================================================
FILE: permissions/permissible.go
================================================
package permissions
// Permissible is an interface used to satisfy for permission holders.
type Permissible interface {
HasPermission(string) bool
RemovePermission(string)
AddPermission(*Permission)
}
================================================
FILE: permissions/permission.go
================================================
package permissions
// Permission is a struct with a name, a default level and children.
// Every child permission can in turn have its own child permissions.
type Permission struct {
name string
defaultLevel int
children map[string]*Permission
}
// NewPermission returns a new permission with the given name and default level.
func NewPermission(name string, defaultLevel int) *Permission {
return &Permission{name, defaultLevel & 0x04, make(map[string]*Permission)}
}
// GetName returns the name of the permission.
func (permission *Permission) GetName() string {
return permission.name
}
// GetDefaultLevel returns the default level of required to be granted the permission.
func (permission *Permission) GetDefaultLevel() int {
return permission.defaultLevel
}
// SetDefaultLevel sets the default level of the the permission.
func (permission *Permission) SetDefaultLevel(level int) {
permission.defaultLevel = level & 0x04
}
// GetChildren returns a name => permission child permission map of all children.
func (permission *Permission) GetChildren() map[string]*Permission {
return permission.children
}
// AddChild adds the given permission as child permission.
func (permission *Permission) AddChild(child *Permission) {
permission.children[child.GetName()] = child
}
// HasChild checks if the permission has a child with the given name.
func (permission *Permission) HasChild(name string) bool {
var _, ok = permission.children[name]
return ok
}
================================================
FILE: players/player.go
================================================
package players
import (
"github.com/google/uuid"
"github.com/irmine/worlds/entities"
"math"
)
type Player struct {
*entities.Entity
uuid uuid.UUID
xuid string
platform int32
playerName string
displayName string
skinId string
skinData []byte
capeData []byte
geometryName string
geometryData string
}
// NewPlayer returns a new player with the given name.
func NewPlayer(uuid uuid.UUID, xuid string, platform int32, name string) *Player {
var player = &Player{Entity: entities.New(entities.Player)}
player.uuid = uuid
player.xuid = xuid
player.platform = platform
player.playerName = name
player.displayName = name
return player
}
// GetName returns the username the player used to join the server.
func (player *Player) GetName() string {
return player.playerName
}
// SetName sets the player name of this player.
// Note: This function is internal, and should not be used by plugins.
func (player *Player) SetName(name string) {
player.playerName = name
}
// GetDisplayName returns the name the player shows in-game.
func (player *Player) GetDisplayName() string {
return player.displayName
}
// SetDisplayName sets the name other players can see in-game.
func (player *Player) SetDisplayName(name string) {
player.displayName = name
}
// GetUUID returns the UUID of the player.
func (player *Player) GetUUID() uuid.UUID {
return player.uuid
}
// GetXUID returns the XUID of the player.
func (player *Player) GetXUID() string {
return player.xuid
}
// GetPlatform returns the platform of the player.
func (player *Player) GetPlatform() int32 {
return player.platform
}
// SpawnPlayerTo spawns this player to the given other player.
func (player *Player) SpawnPlayerTo(viewer entities.Viewer) {
viewer.SendAddPlayer(player.GetUUID(), player)
}
// SpawnPlayerToAll spawns this player to all other players.
func (player *Player) SpawnPlayerToAll() {
for _, p := range player.Dimension.GetViewers() {
if p.GetUUID() == player.GetUUID() {
continue
}
if viewer, ok := p.(entities.Viewer); ok {
player.SpawnPlayerTo(viewer)
}
}
}
// SetSkinId sets the skin ID/name of the player.
func (player *Player) SetSkinId(id string) {
player.skinId = id
}
// GetSkinId returns the skin ID/name of the player.
func (player *Player) GetSkinId() string {
return player.skinId
}
// GetSkinData returns the skin data of the player. (RGBA byte array)
func (player *Player) GetSkinData() []byte {
return player.skinData
}
// SetSkinData sets the skin data of the player. (RGBA byte array)
func (player *Player) SetSkinData(data []byte) {
player.skinData = data
}
// GetCapeData returns the cape data of the player. (RGBA byte array)
func (player *Player) GetCapeData() []byte {
return player.capeData
}
// SetCapeData sets the cape data of the player. (RGBA byte array)
func (player *Player) SetCapeData(data []byte) {
player.capeData = data
}
// GetGeometryName returns the geometry name of the player.
func (player *Player) GetGeometryName() string {
return player.geometryName
}
// SetGeometryName sets the geometry name of the player.
func (player *Player) SetGeometryName(name string) {
player.geometryName = name
}
// GetGeometryData returns the geometry data (json string) of the player.
func (player *Player) GetGeometryData() string {
return player.geometryData
}
// SetGeometryData sets the geometry data (json string) of the player.
func (player *Player) SetGeometryData(data string) {
player.geometryData = data
}
// SyncMove synchronizes the server's player movement with the client movement.
func (player *Player) SyncMove(x, y, z, pitch, yaw, headYaw float64, onGround bool) {
player.Position.X = x
player.Position.Y = y
player.Position.Z = z
player.Rotation.Pitch = math.Mod(pitch, 360)
player.Rotation.Yaw = math.Mod(yaw, 360)
player.Rotation.HeadYaw = headYaw
player.OnGround = onGround
player.HasMovementUpdate = true
}
// Sends updated entity position and rotation to a certain viewer
// this overrides the base entity function.
func (player *Player) SendMovement(viewer entities.Viewer) {
viewer.SendMovePlayer(player.GetRuntimeId(), player.Position, player.Rotation, 0, player.OnGround, player.GetRidingId())
}
// Sends updated player position and rotation to all viewers,
// this overrides the base entity function.
func (player *Player) BroadcastMovement() {
for _, viewer := range player.GetViewers() {
viewer.SendMovePlayer(player.GetRuntimeId(), player.Position, player.Rotation, 0, player.OnGround, player.GetRidingId())
}
}
// Tick ticks the player, this overrides the base entity tick.
func (player Player) Tick() {
if player.HasEntityDataUpdate {
player.BroadcastUpdatedEntityData()
player.HasEntityDataUpdate = false
}
if player.HasMovementUpdate {
player.HasMovementUpdate = false
}
player.BroadcastMovement()
}
================================================
FILE: plugin.go
================================================
package gomine
type Manifest struct {
Name string
Description string
Version string
APIVersion string
Author string
Organisation string
}
type IManifest interface {
GetName() string
GetDescription() string
GetVersion() string
GetAPIVersion() string
GetAuthor() string
GetOrganisation() string
}
type IPlugin interface {
GetServer() *Server
OnEnable()
GetName() string
GetVersion() string
GetAuthor() string
GetOrganisation() string
GetAPIVersion() string
setManifest(IManifest)
}
type Plugin struct {
server *Server
manifest IManifest
}
func NewPlugin(server *Server) *Plugin {
return &Plugin{server, Manifest{}}
}
// GetName returns the name of the manifest.
func (manifest Manifest) GetName() string {
return manifest.Name
}
// GetVersion returns the version of the manifest.
func (manifest Manifest) GetVersion() string {
return manifest.Version
}
// GetOrganisation returns the author of the manifest.
func (manifest Manifest) GetOrganisation() string {
return manifest.Organisation
}
// GetAPIVersion returns the API Version of the manifest.
func (manifest Manifest) GetAPIVersion() string {
return manifest.APIVersion
}
// GetAuthor returns the author of the manifest.
func (manifest Manifest) GetAuthor() string {
return manifest.Author
}
// GetDescription returns the description of the manifest.
func (manifest Manifest) GetDescription() string {
return manifest.Description
}
// GetName returns the name of the plugin.
func (plug *Plugin) GetName() string {
return plug.manifest.GetName()
}
// GetVersion returns the version of the plugin.
func (plug *Plugin) GetVersion() string {
return plug.manifest.GetVersion()
}
// GetAuthor returns the author of the plugin.
func (plug *Plugin) GetOrganisation() string {
return plug.manifest.GetOrganisation()
}
// GetAPIVersion returns the API Version of the plugin.
func (plug *Plugin) GetAPIVersion() string {
return plug.manifest.GetAPIVersion()
}
// GetAuthor returns the author of the plugin.
func (plug *Plugin) GetAuthor() string {
return plug.manifest.GetAuthor()
}
// GetDescription returns the description of the plugin.
func (plug *Plugin) GetDescription() string {
return plug.manifest.GetDescription()
}
// SetManifest sets the manifest of this plugin.
func (plug *Plugin) setManifest(manifest IManifest) {
plug.manifest = manifest
}
// GetServer returns the main server.
func (plug *Plugin) GetServer() *Server {
return plug.server
}
================================================
FILE: plugin_manager.go
================================================
package gomine
import (
"errors"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"plugin"
"strings"
"github.com/google/uuid"
"github.com/irmine/gomine/text"
)
const (
ApiVersion = "0.0.1"
OutdatedPlugin = "plugin.Open: plugin was built with a different version of package"
NoPluginsSupported = "plugin: not implemented"
)
type PluginManager struct {
server *Server
plugins map[string]IPlugin
}
func NewPluginManager(server *Server) *PluginManager {
return &PluginManager{server, make(map[string]IPlugin)}
}
// GetPlugins returns all plugins currently loaded on the server.
func (manager *PluginManager) GetPlugins() map[string]IPlugin {
return manager.plugins
}
// GetServer returns the main server.
func (manager *PluginManager) GetServer() *Server {
return manager.server
}
// GetPlugin returns a plugin with the given name, or nil if none could be found.
func (manager *PluginManager) GetPlugin(name string) IPlugin {
if !manager.IsPluginLoaded(name) {
return nil
}
return manager.plugins[name]
}
// IsPluginLoaded checks if a plugin with the given name is loaded.
func (manager *PluginManager) IsPluginLoaded(name string) bool {
var _, exists = manager.plugins[name]
return exists
}
// LoadPlugins loads all plugins in the 'extensions/plugins' folder.
func (manager *PluginManager) LoadPlugins() {
var path = manager.server.ServerPath + "extensions/plugins/"
var files, _ = ioutil.ReadDir(path)
for _, file := range files {
if file.IsDir() {
continue
}
filePath := path + file.Name()
extension := filepath.Ext(filePath)
if extension != ".so" {
continue
}
err := manager.LoadPlugin(filePath)
if err != nil {
if err.Error() == NoPluginsSupported {
text.DefaultLogger.Error("Go does currently not support plugins for your operating system.")
return
}
}
text.DefaultLogger.LogError(err)
}
}
// CompilePlugin compiles a plugin.go at the given path during runtime, and opens it. This action is extremely time consuming.
func (manager *PluginManager) CompilePlugin(filePath string) (*plugin.Plugin, error) {
var compiledPath = strings.Replace(strings.Replace(filePath, ".go", "", 1), "\\", "/", -1)
compiledPath += "~" + uuid.Must(uuid.NewRandom()).String() + ".so"
var cmd = exec.Command("go", "build", "-buildmode=plugin", "-i", "-o", compiledPath, filePath)
var output, err = cmd.CombinedOutput()
if err != nil {
text.DefaultLogger.LogError(err)
text.DefaultLogger.Error(string(output))
}
plug, err := plugin.Open(compiledPath)
return plug, err
}
// RecompilePlugin recompiles a plugin.so at the given path, provided the main source file is at the same location suffixed with .go.
func (manager *PluginManager) RecompilePlugin(filePath string) (*plugin.Plugin, error) {
var decompiledPath = strings.Replace(strings.Replace(filePath, ".so", ".go", 1), "\\", "/", -1)
if strings.Contains(filePath, "~") {
decompiledPath = strings.Split(decompiledPath, "~")[0] + ".go"
}
os.Remove(filePath)
return manager.CompilePlugin(decompiledPath)
}
// LoadPlugin loads a plugin at the given file path and returns an error if applicable.
func (manager *PluginManager) LoadPlugin(filePath string) error {
var plug, err = plugin.Open(filePath)
if err != nil {
if strings.Contains(err.Error(), OutdatedPlugin) {
text.DefaultLogger.Notice("Outdated plugin. Recompiling plugin... This might take a bit.")
var newPlugin, newErr = manager.RecompilePlugin(filePath)
if newErr != nil {
return newErr
}
plug = newPlugin
} else {
return err
}
}
manifestSymbol, err := plug.Lookup("Manifest")
if err != nil {
return errors.New("Plugin at '" + filePath + "' does not have a Manifest.")
}
manifest, ok := manifestSymbol.(IManifest)
if !ok {
return errors.New("Plugin at '" + filePath + "' does not have a valid Manifest.")
}
err = manager.ValidateManifest(manifest, filePath)
if err != nil {
return err
}
newPluginSymbol, err := plug.Lookup("NewPlugin")
if err != nil {
return errors.New("Plugin at '" + filePath + "' does not have a NewPlugin function.")
}
pluginFunc, ok := newPluginSymbol.(func(server *Server) IPlugin)
if !ok {
return errors.New("Plugin at '" + filePath + "' does not have a valid NewPlugin function.")
}
var finalPlugin = pluginFunc(manager.server)
finalPlugin.setManifest(manifest)
manager.plugins[finalPlugin.GetName()] = finalPlugin
finalPlugin.OnEnable()
return nil
}
// ValidateManifest validates the plugin manifest and checks for duplicated plugins.
func (manager *PluginManager) ValidateManifest(manifest IManifest, path string) error {
if manifest.GetName() == "" {
return errors.New("Plugin manifest at " + path + " is missing a name.")
}
if manager.IsPluginLoaded(manifest.GetName()) {
return errors.New("Found duplicated plugin at " + path)
}
if manifest.GetDescription() == "" {
return errors.New("Plugin manifest at " + path + " is missing a description.")
}
var dotCount = strings.Count(manifest.GetVersion(), ".")
if dotCount < 1 {
return errors.New("Plugin manifest at " + path + " is missing a valid version.")
}
var digits = strings.Split(manifest.GetAPIVersion(), ".")
if len(digits) < 2 {
return errors.New("Plugin manifest at " + path + " is missing a valid API version.")
}
var currentDigits = strings.Split(ApiVersion, ".")
if digits[0] != currentDigits[0] {
return errors.New("Plugin manifest at " + path + " has an incompatible greater API version. Got: " + digits[0] + ".~, Expected: " + currentDigits[0] + ".~")
}
return nil
}
================================================
FILE: resources/gomine.yml.go
================================================
package resources
import (
"io/ioutil"
"os"
"gopkg.in/yaml.v2"
)
type GoMineConfig struct {
ServerName string `yaml:"Server LAN Name"`
ServerMotd string `yaml:"Server MOTD"`
ServerIp string `yaml:"Server IP"`
ServerPort uint16 `yaml:"Server Port"`
MaximumPlayers uint `yaml:"Maximum Players"`
DefaultGameMode byte `yaml:"Default Gamemode"`
DebugMode bool `yaml:"Debug Mode"`
DefaultLevel string `yaml:"Default Level"`
DefaultGenerator string `yaml:"Default Generator"`
ForceResourcePacks bool `yaml:"Forced Resource Packs"`
SelectedResourcePack string `yaml:"Selected Resource Pack"`
XBOXLiveAuth bool `yaml:"XBOX Live Auth"`
UseEncryption bool `yaml:"Use Encryption"`
AllowQuery bool `yaml:"Allow Query"`
AllowPluginQuery bool `yaml:"Allow Plugin Query"`
MaxViewDistance int32 `yaml:"Max View Distance"`
}
// NewGoMineConfig returns a new configuration struct.
// Creates the file if it does not yet exist.
func NewGoMineConfig(serverPath string) *GoMineConfig {
initializeConfig(serverPath)
return getGoMineConfig(serverPath)
}
// initializeConfig initializes the configuration file if it does not yet exist.
func initializeConfig(serverPath string) {
var path = serverPath + "gomine.yml"
var _, err = os.Stat(path)
if os.IsNotExist(err) {
var data, _ = yaml.Marshal(GoMineConfig{
ServerName: "GoMine Server",
ServerMotd: "GoMine Testing Server",
ServerIp: "0.0.0.0",
ServerPort: 19132,
MaximumPlayers: 20,
DefaultGameMode: 1,
DebugMode: true,
DefaultLevel: "world",
DefaultGenerator: "Flat",
ForceResourcePacks: false,
SelectedResourcePack: "",
XBOXLiveAuth: true,
UseEncryption: false,
AllowQuery: true,
AllowPluginQuery: true,
MaxViewDistance: 8,
})
var file, _ = os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
file.WriteString(string(data))
file.Sync()
}
}
// getGoMineConfig parses the configuration file into a struct.
func getGoMineConfig(serverPath string) *GoMineConfig {
var yamlFile, _ = ioutil.ReadFile(serverPath + "gomine.yml")
var config = &GoMineConfig{}
yaml.Unmarshal(yamlFile, config)
return config
}
================================================
FILE: server.go
================================================
package gomine
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"github.com/irmine/worlds/generation/defaults"
"github.com/irmine/worlds/providers"
"encoding/hex"
"errors"
"fmt"
"github.com/irmine/gomine/commands"
"github.com/irmine/gomine/net"
"github.com/irmine/gomine/net/info"
"github.com/irmine/gomine/net/packets/data"
"github.com/irmine/gomine/net/protocol"
"github.com/irmine/gomine/packs"
"github.com/irmine/gomine/permissions"
"github.com/irmine/gomine/resources"
"github.com/irmine/gomine/text"
"github.com/irmine/goraklib/server"
"github.com/irmine/query"
"github.com/irmine/worlds"
net2 "net"
"os"
"strings"
)
const (
GoMineName = "GoMine"
GoMineVersion = "0.0.1"
)
type Server struct {
isRunning bool
tick int64
privateKey *ecdsa.PrivateKey
token []byte
ServerPath string
Config *resources.GoMineConfig
CommandReader *text.CommandReader
CommandManager *commands.Manager
PackManager *packs.Manager
PermissionManager *permissions.Manager
LevelManager *worlds.Manager
SessionManager *net.SessionManager
NetworkAdapter *net.NetworkAdapter
PluginManager *PluginManager
QueryManager query.Manager
}
// AlreadyStarted gets returned during server startup,
// if the server has already been started.
var AlreadyStarted = errors.New("server is already started")
// NewServer returns a new server with the given server path.
func NewServer(serverPath string, config *resources.GoMineConfig) *Server {
var s = &Server{}
s.ServerPath = serverPath
s.Config = config
text.DefaultLogger.DebugMode = config.DebugMode
file, _ := os.OpenFile(serverPath+"gomine.log", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0700)
text.DefaultLogger.AddOutput(func(message []byte) {
_, err := file.WriteString(text.ColoredString(message).StripAll())
if err != nil {
text.DefaultLogger.LogError(err)
}
})
s.LevelManager = worlds.NewManager(serverPath)
s.CommandReader = text.NewCommandReader(os.Stdin)
s.CommandReader.AddReadFunc(s.attemptReadCommand)
s.CommandManager = commands.NewManager()
s.SessionManager = net.NewSessionManager()
s.NetworkAdapter = net.NewNetworkAdapter(NewPacketManager(s), s.SessionManager)
s.NetworkAdapter.GetRakLibManager().PongData = s.GeneratePongData()
s.NetworkAdapter.GetRakLibManager().RawPacketFunction = s.HandleRaw
s.NetworkAdapter.GetRakLibManager().DisconnectFunction = s.HandleDisconnect
s.PackManager = packs.NewManager(serverPath)
s.PermissionManager = permissions.NewManager()
s.PluginManager = NewPluginManager(s)
s.QueryManager = query.NewManager()
if config.UseEncryption {
var curve = elliptic.P384()
var err error
s.privateKey, err = ecdsa.GenerateKey(curve, rand.Reader)
text.DefaultLogger.LogError(err)
if !curve.IsOnCurve(s.privateKey.X, s.privateKey.Y) {
text.DefaultLogger.Error("Invalid private key generated")
}
var token = make([]byte, 128)
_, err = rand.Read(token)
if err != nil {
text.DefaultLogger.Error(err)
}
s.token = token
}
return s
}
// RegisterDefaultCommands registers all default commands of the server.
func (server *Server) RegisterDefaultCommands() {
server.CommandManager.RegisterCommand(NewStop(server))
server.CommandManager.RegisterCommand(NewList(server))
server.CommandManager.RegisterCommand(NewPing())
server.CommandManager.RegisterCommand(NewTest(server))
}
// IsRunning checks if the server is running.
func (server *Server) IsRunning() bool {
return server.isRunning
}
// Start starts the server and loads levels, plugins, resource packs etc.
// Start returns an error if one occurred during starting.
func (server *Server) Start() error {
if server.isRunning {
return AlreadyStarted
}
text.DefaultLogger.Info("GoMine "+GoMineVersion+" is now starting...", "("+server.ServerPath+")")
server.LevelManager.SetDefaultLevel(worlds.NewLevel("world", server.ServerPath))
var dimension = worlds.NewDimension("overworld", server.LevelManager.GetDefaultLevel(), worlds.OverworldId)
dimension.SetChunkProvider(providers.NewAnvil(server.ServerPath + "worlds/world/overworld/region/"))
server.LevelManager.GetDefaultLevel().SetDefaultDimension(dimension)
dimension.SetGenerator(defaults.NewFlatGenerator())
server.RegisterDefaultCommands()
server.PackManager.LoadResourcePacks() // Behavior packs may depend on resource packs, so always load resource packs first.
server.PackManager.LoadBehaviorPacks()
server.PluginManager.LoadPlugins()
server.isRunning = true
return server.NetworkAdapter.GetRakLibManager().Start(server.Config.ServerIp, int(server.Config.ServerPort))
}
// Shutdown shuts down the server, saving and disabling everything.
func (server *Server) Shutdown() {
if !server.isRunning {
return
}
text.DefaultLogger.Info("Server is shutting down.")
text.DefaultLogger.Notice("Server stopped.")
text.DefaultLogger.Wait()
server.isRunning = false
}
// GetMinecraftVersion returns the latest Minecraft game version.
// It is prefixed with a 'v', for example: "v1.2.10.1"
func (server *Server) GetMinecraftVersion() string {
return info.LatestGameVersion
}
// GetMinecraftNetworkVersion returns the latest Minecraft network version.
// For example: "1.2.10.1"
func (server *Server) GetMinecraftNetworkVersion() string {
return info.LatestGameVersionNetwork
}
// HasPermission returns if the server has a given permission.
// Always returns true to satisfy the ICommandSender interface.
func (server *Server) HasPermission(string) bool {
return true
}
// SendMessage sends a message to the server to satisfy the ICommandSender interface.
func (server *Server) SendMessage(message ...interface{}) {
text.DefaultLogger.Notice(message)
}
// GetEngineName returns 'GoMine'.
func (server *Server) GetEngineName() string {
return GoMineName
}
// GetName returns the LAN name of the server specified in the configuration.
func (server *Server) GetName() string {
return server.Config.ServerName
}
// GetPort returns the port of the server specified in the configuration.
func (server *Server) GetPort() uint16 {
return server.Config.ServerPort
}
// GetAddress returns the IP address specified in the configuration.
func (server *Server) GetAddress() string {
return server.Config.ServerIp
}
// GetMaximumPlayers returns the maximum amount of players on the server.
func (server *Server) GetMaximumPlayers() uint {
return server.Config.MaximumPlayers
}
// Returns the Message Of The Day of the server.
func (server *Server) GetMotd() string {
return server.Config.ServerMotd
}
// Returns the max view distance allowed by the server
func (server *Server) GetMaxViewDistance() int32 {
return server.Config.MaxViewDistance
}
// Returns the max view distance allowed by the server,
// if it's 0 it returns the given distance which is the
// distance given by a joining player
func (server *Server) GetAllowedViewDistance(distance int32) int32 {
var maxViewDistance int32
if maxViewDistance = server.GetMaxViewDistance(); maxViewDistance <= 0 {
return distance
}
return maxViewDistance
}
// GetCurrentTick returns the current tick the server is on.
func (server *Server) GetCurrentTick() int64 {
return server.tick
}
// BroadcastMessageTo broadcasts a message to all receivers.
func (server *Server) BroadcastMessageTo(receivers []*net.MinecraftSession, message ...interface{}) {
for _, session := range receivers {
session.SendMessage(message)
}
text.DefaultLogger.LogChat(message)
}
// Broadcast broadcasts a message to all players and the console in the server.
func (server *Server) BroadcastMessage(message ...interface{}) {
for _, session := range server.SessionManager.GetSessions() {
session.SendMessage(message)
}
text.DefaultLogger.LogChat(message)
}
// GetPrivateKey returns the ECDSA private key of the server.
func (server *Server) GetPrivateKey() *ecdsa.PrivateKey {
return server.privateKey
}
// GetPublicKey returns the ECDSA public key of the private key of the server.
func (server *Server) GetPublicKey() *ecdsa.PublicKey {
return &server.privateKey.PublicKey
}
// GetServerToken returns the server token byte sequence.
func (server *Server) GetServerToken() []byte {
return server.token
}
// GenerateQueryResult returns the query data of the server in a byte array.
func (server *Server) GenerateQueryResult() query.Result {
var plugs []string
for _, plug := range server.PluginManager.GetPlugins() {
plugs = append(plugs, plug.GetName()+" v"+plug.GetVersion())
}
var ps []string
for name := range server.SessionManager.GetSessions() {
ps = append(ps, name)
}
var result = query.Result{
MOTD: server.GetMotd(),
ListPlugins: server.Config.AllowPluginQuery,
PluginNames: plugs,
PlayerNames: ps,
GameMode: "SMP",
Version: server.GetMinecraftVersion(),
ServerEngine: server.GetEngineName(),
WorldName: server.LevelManager.GetDefaultLevel().GetName(),
OnlinePlayers: int(server.SessionManager.GetSessionCount()),
MaximumPlayers: int(server.Config.MaximumPlayers),
Whitelist: "off",
Port: server.Config.ServerPort,
Address: server.Config.ServerIp,
}
return result
}
// HandleRaw handles a raw packet, for instance a query packet.
func (server *Server) HandleRaw(packet []byte, addr *net2.UDPAddr) {
if string(packet[0:2]) == string(query.Header) {
if !server.Config.AllowQuery {
return
}
var q = query.NewFromRaw(packet, addr)
q.DecodeServer()
server.QueryManager.HandleQuery(q)
return
}
text.DefaultLogger.Debug("Unhandled raw packet:", hex.EncodeToString(packet))
}
// HandleDisconnect handles a disconnection from a session.
func (server *Server) HandleDisconnect(s *server.Session) {
text.DefaultLogger.Debug(s, "disconnected!")
session, ok := server.SessionManager.GetSessionByRakNetSession(s)
server.SessionManager.RemoveMinecraftSession(session)
if !ok {
return
}
if session.GetPlayer().Dimension != nil {
for _, online := range server.SessionManager.GetSessions() {
online.SendPlayerList(data.ListTypeRemove, map[string]protocol.PlayerListEntry{session.GetPlayer().GetName(): session.GetPlayer()})
}
session.GetPlayer().Close()
session.Connected = false
server.BroadcastMessage(text.Yellow+session.GetDisplayName(), "has left the server")
}
}
// GeneratePongData generates the GoRakLib pong data for the UnconnectedPong RakNet packet.
func (server *Server) GeneratePongData() string {
return fmt.Sprint("MCPE;", server.GetMotd(), ";", info.LatestProtocol, ";", server.GetMinecraftNetworkVersion(), ";", server.SessionManager.GetSessionCount(), ";", server.Config.MaximumPlayers, ";", server.NetworkAdapter.GetRakLibManager().ServerId, ";", server.GetEngineName(), ";Creative;")
}
// Tick ticks the entire server. (Levels, scheduler, GoRakLib server etc.)
// Internal. Not to be used by plugins.
func (server *Server) Tick() {
if !server.isRunning {
return
}
if server.tick%20 == 0 {
server.QueryManager.SetQueryResult(server.GenerateQueryResult())
server.NetworkAdapter.GetRakLibManager().PongData = server.GeneratePongData()
}
for _, session := range server.SessionManager.GetSessions() {
session.Tick()
}
for _, level := range server.LevelManager.GetLevels() {
level.Tick()
}
server.tick++
}
func (server *Server) attemptReadCommand(commandText string) {
args := strings.Split(commandText, " ")
commandName := args[0]
i := 1
for !server.CommandManager.IsCommandRegistered(commandName) {
if i == len(args) {
break
}
commandName += " " + args[i]
i++
}
manager := server.CommandManager
if !manager.IsCommandRegistered(commandName) {
text.DefaultLogger.Error("Command could not be found.")
return
}
args = args[i:]
command, _ := manager.GetCommand(commandName)
command.Execute(server, args)
}
================================================
FILE: text/command_reader.go
================================================
package text
import (
"bufio"
"io"
"strings"
)
// CommandReader implements command reading from io.Readers.
// CommandReader continuously processes incoming commands,
// and executes all associated functions with it.
type CommandReader struct {
// reader is the bufio.Reader encapsulating the input reader.
reader *bufio.Reader
// LineReadFunctions are all line read functions.
// These functions get executed every time a line gets read.
LineReadFunctions []func(line string)
}
// NewCommandReader returns a new CommandReader.
// The input io.Reader is encapsulated by a bufio.Reader
// and further used to continuously read from.
func NewCommandReader(inputReader io.Reader) *CommandReader {
reader := &CommandReader{bufio.NewReader(inputReader), []func(string){}}
go func() {
for {
reader.readLine()
}
}()
return reader
}
// AddReadFunc adds a new line read function to the command reader.
// The function passed will get called with the line read as argument,
// every time a command gets read from the input reader.
// Example:
// func(line string) { os.Stdout.Write([]byte("You wrote: " + line)) }
func (reader *CommandReader) AddReadFunc(outputFunc func(string)) {
reader.LineReadFunctions = append(reader.LineReadFunctions, outputFunc)
}
// readLine continuously reads lines from the input reader.
// Every time a line gets read from the input reader,
// all LineReadFunctions are executed with the line read.
func (reader *CommandReader) readLine() {
command, _ := reader.reader.ReadString('\n')
command = strings.Trim(command, "\n")
for _, f := range reader.LineReadFunctions {
f(command)
}
}
================================================
FILE: text/font.go
================================================
package text
import "strings"
const (
AnsiPre = "\u001b["
AnsiReset = AnsiPre + "0m"
AnsiBold = AnsiPre + "1m"
AnsiItalic = AnsiPre + "3m"
AnsiUnderlined = AnsiPre + "4m"
AnsiBlack = AnsiPre + "30m"
AnsiRed = AnsiPre + "31m"
AnsiGreen = AnsiPre + "32m"
AnsiYellow = AnsiPre + "33m"
AnsiBlue = AnsiPre + "34m"
AnsiMagenta = AnsiPre + "35m"
AnsiCyan = AnsiPre + "36m"
AnsiWhite = AnsiPre + "37m"
AnsiGray = AnsiPre + "30;1m"
AnsiBrightRed = AnsiPre + "31;1m"
AnsiBrightGreen = AnsiPre + "32;1m"
AnsiBrightYellow = AnsiPre + "33;1m"
AnsiBrightBlue = AnsiPre + "34;1m"
AnsiBrightMagenta = AnsiPre + "35;1m"
AnsiBrightCyan = AnsiPre + "36;1m"
AnsiBrightWhite = AnsiPre + "37;1m"
)
const (
Pre = "§"
Black = Pre + "0"
Blue = Pre + "1"
Green = Pre + "2"
Cyan = Pre + "3"
Red = Pre + "4"
Magenta = Pre + "5"
Orange = Pre + "6"
BrightGray = Pre + "7"
Gray = Pre + "8"
BrightBlue = Pre + "9"
BrightGreen = Pre + "a"
BrightCyan = Pre + "b"
BrightRed = Pre + "c"
BrightMagenta = Pre + "d"
Yellow = Pre + "e"
White = Pre + "f"
Obfuscated = Pre + "k"
Bold = Pre + "l"
StrikeThrough = Pre + "m"
Underlined = Pre + "n"
Italic = Pre + "o"
Reset = Pre + "r"
)
// ColoredString is a string containing colours.
// ColoredString has functions to manipulate the colours it holds.
type ColoredString string
// colorConvert is used to convert Minecraft colours
// to ANSI colours and the other way around.
var colorConvert = map[string]string{
Black: AnsiBlack,
Blue: AnsiBlue,
Green: AnsiGreen,
Cyan: AnsiCyan,
Red: AnsiRed,
Magenta: AnsiMagenta,
Orange: AnsiYellow,
BrightGray: AnsiWhite,
Gray: AnsiGray,
BrightBlue: AnsiBrightBlue,
BrightGreen: AnsiBrightGreen,
BrightCyan: AnsiBrightCyan,
BrightRed: AnsiBrightRed,
BrightMagenta: AnsiBrightMagenta,
Yellow: AnsiBrightYellow,
White: AnsiBrightWhite,
Bold: AnsiBold,
Underlined: AnsiUnderlined,
Italic: AnsiItalic,
Reset: AnsiReset,
StrikeThrough: AnsiUnderlined,
Obfuscated: AnsiUnderlined,
}
// ToANSI converts all Minecraft colors in a ColoredString to ANSI colors.
// A new string is returned with the colors converted.
func (str ColoredString) ToANSI() string {
text := string(str)
for toConvert, convertValue := range colorConvert {
text = strings.Replace(text, toConvert, convertValue, -1)
}
return text
}
// ToMinecraft converts all ANSI colors in a ColoredString to Minecraft colors.
// A new string is returned with the colors converted.
func (str ColoredString) ToMinecraft() string {
text := string(str)
for convertValue, toConvert := range colorConvert {
text = strings.Replace(text, toConvert, convertValue, -1)
}
return text
}
// StripMinecraft strips all Minecraft colors in a ColoredString.
// A new string is returned with the colors stripped.
func (str ColoredString) StripMinecraft() string {
text := string(str)
for toConvert := range colorConvert {
text = strings.Replace(text, toConvert, "", -1)
}
return text
}
// StripANSI strips all ANSI colors in a ColoredString.
// A new string is returned with the colors stripped.
func (str ColoredString) StripANSI() string {
text := string(str)
for _, toConvert := range colorConvert {
text = strings.Replace(text, toConvert, "", -1)
}
return text
}
// StripAll strips all colors (both ANSI and MCPE) in a ColoredString.
// A new string is returned with the colors stripped.
func (str ColoredString) StripAll() string {
text := string(str)
for mcpeColor, ansiColor := range colorConvert {
text = strings.Replace(text, mcpeColor, "", -1)
text = strings.Replace(text, ansiColor, "", -1)
}
return text
}
================================================
FILE: text/logger.go
================================================
package text
import (
"fmt"
"os"
"runtime/debug"
"strings"
)
const (
Debug = "[Debug]"
Info = "[Info]"
Notice = "[Notice]"
Alert = "[Alert]"
Error = "[Error]"
Warning = "[Warning]"
Critical = "[Critical]"
Chat = "[Chat]"
StackTrace = "[Stack Trace]"
)
// Logger is a helper for writing log information to multiple
// locations at the same time on a different goroutine.
// Each logger has a prefix, which all messages will be
// prefixed with, and a debug mode, which if turned on will
// write debug messages too.
type Logger struct {
// Prefix is the prefix of the logger.
// Every message is prefixed with this string.
// The prefix is enclosed in brackets, as such: [Prefix]
Prefix string
// DebugMode is the debug mode of the logger.
// If true, writes debug messages.
DebugMode bool
// OutputFunctions contains all logger output functions.
// Every output function gets called once a message gets logged.
OutputFunctions []func(message []byte)
// MessageQueue is the queue of messages to the processed.
// These messages will be continuously processed on a different goroutine.
MessageQueue chan string
// waiting and waitRelease are used to manage the waiting state of the logger.
// Both are used to notify the logger for waiting.
waiting bool
waitRelease chan bool
}
// DefaultLogger is the default GoMine logger.
// It has the prefix `GoMine` and has debug turned off.
// The default logger will write only to Stdout.
var DefaultLogger = NewLogger("GoMine", false)
// init initializes the output of the default logger.
// It writes to Stdout by default.
func init() {
DefaultLogger.AddOutput(func(message []byte) {
os.Stdout.Write(message)
})
}
// NewLogger returns a new logger with the given prefix and debug mode.
// Additional output functions can be added to the logger once an
// instance has been created using this function.
// The logger will be made to process immediately when creating a new logger.
func NewLogger(prefix string, debugMode bool) *Logger {
logger := &Logger{prefix, debugMode, []func([]byte){}, make(chan string, 128), false, make(chan bool)}
go logger.process()
return logger
}
// AddOutput adds a new output function to the logger.
// The function passed will get called with the message
// provided as argument every time a message gets logged.
// Example:
// func(message []byte) { os.Stdout.Write(message) }
func (logger *Logger) AddOutput(f func(message []byte)) {
logger.OutputFunctions = append(logger.OutputFunctions, f)
}
// Write writes a byte array to the logger.
// All Minecraft colors are first replaced with ANSI colors.
// after which they get added to the message queue.
// The message will then get processed on a different goroutine.
func (logger *Logger) Write(message []byte) {
logger.MessageQueue <- ColoredString(string(message)).ToANSI() + AnsiReset + "\n"
}
// Write writes a string to the logger.
// All Minecraft colors are first replaced with ANSI colors.
// after which they get added to the message queue.
// The message will then get processed on a different goroutine.
func (logger *Logger) WriteString(message string) {
logger.MessageQueue <- ColoredString(message).ToANSI() + AnsiReset + "\n"
}
// process continuously processes queued messages in the logger.
// Messages get fetched from the queue as soon as they're added,
// and will be ran through every output function.
func (logger *Logger) process() {
for {
if len(logger.MessageQueue) == 0 && logger.waiting {
logger.waitRelease <- true
return
}
message := "[" + logger.Prefix + "] " + <-logger.MessageQueue
for _, f := range logger.OutputFunctions {
f([]byte(message))
}
}
}
// Wait waits until the logger is done logging all messages
// currently in the message queue. The current goroutine will be
// blocked until the logger is done processing all messages,
// and the writing goroutine will be stopped.
// After waiting, the writing process gets restarted.
func (logger *Logger) Wait() {
logger.waiting = true
<-logger.waitRelease
logger.waiting = false
go logger.process()
}
// Notice logs a notice message.
func (logger *Logger) Notice(messages ...interface{}) {
logger.WriteString(Yellow + Notice + " " + strings.Trim(fmt.Sprint(messages), "[]"))
}
// Debug logs a debug message.
func (logger *Logger) Debug(messages ...interface{}) {
logger.WriteString(Orange + Debug + " " + strings.Trim(fmt.Sprint(messages), "[]"))
}
// Info logs an info message.
func (logger *Logger) Info(messages ...interface{}) {
logger.WriteString(BrightCyan + Info + " " + strings.Trim(fmt.Sprint(messages), "[]"))
}
// Alert logs an alert.
func (logger *Logger) Alert(messages ...interface{}) {
logger.WriteString(BrightRed + Alert + " " + strings.Trim(fmt.Sprint(messages), "[]"))
}
// Warning logs a warning message.
func (logger *Logger) Warning(messages ...interface{}) {
logger.WriteString(BrightRed + Bold + Warning + " " + strings.Trim(fmt.Sprint(messages), "[]"))
}
// Critical logs a critical warning message.
func (logger *Logger) Critical(messages ...interface{}) {
logger.WriteString(BrightRed + Underlined + Bold + Critical + " " + strings.Trim(fmt.Sprint(messages), "[]"))
}
// Error logs an error message.
func (logger *Logger) Error(messages ...interface{}) {
logger.WriteString(Red + Error + " " + strings.Trim(fmt.Sprint(messages), "[]"))
}
// LogChat logs a chat message to the logger.
func (logger *Logger) LogChat(messages ...interface{}) {
logger.WriteString(BrightCyan + Chat + " " + strings.Trim(fmt.Sprint(messages), "[]"))
}
// LogStack logs the stack trace.
func (logger *Logger) LogStack() {
logger.WriteString(Yellow + StackTrace + " " + string(debug.Stack()))
}
// LogError logs an actual error to the logger.
// A nil error may also be passed,
// which the logger will completely ignore.
func (logger *Logger) LogError(err error) {
if err == nil {
return
}
logger.Error(err.Error())
}
================================================
FILE: text/logger_test.go
================================================
package text
import (
"errors"
"os"
"testing"
)
func TestLogger(t *testing.T) {
logger := NewLogger("Test Logger", true)
logger.AddOutput(func(message []byte) {
os.Stdout.Write(message)
})
logger.WriteString("Raw message")
logger.Info("Logger working.")
logger.Debug("Debug message.", "another debug")
var err error
logger.LogError(err) // err is nil, does not print anything.
err = errors.New("error")
logger.LogError(err)
logger.LogStack()
logger.Wait()
}
func TestDefault(t *testing.T) {
DefaultLogger.Debug("Debug message")
DefaultLogger.Notice("Notice message")
DefaultLogger.LogStack()
DefaultLogger.Wait()
}
func TestMultipleWait(t *testing.T) {
logger := NewLogger("Test Logger", true)
logger.AddOutput(func(message []byte) {
os.Stdout.Write(message)
})
logger.LogStack()
logger.Wait()
logger.LogStack()
logger.Wait()
}
================================================
FILE: utils/encryption.go
================================================
package utils
import (
"crypto/aes"
"crypto/cipher"
"crypto/ecdsa"
"crypto/sha256"
"github.com/irmine/binutils"
)
type EncryptionData struct {
ClientPublicKey *ecdsa.PublicKey
ServerPrivateKey *ecdsa.PrivateKey
ServerToken []byte
SharedSecret []byte
DecryptSecretKeyBytes [32]byte
EncryptSecretKeyBytes [32]byte
DecryptIV []byte
EncryptIV []byte
DecryptCipher cipher.Block
EncryptCipher cipher.Block
SendCounter int64
}
func (data *EncryptionData) ComputeSharedSecret() {
var x, _ = data.ClientPublicKey.Curve.ScalarMult(data.ClientPublicKey.X, data.ClientPublicKey.Y, data.ServerPrivateKey.D.Bytes())
data.SharedSecret = x.Bytes()
}
func (data *EncryptionData) ComputeSecretKeyBytes() {
var secret = sha256.Sum256(append(data.ServerToken, data.SharedSecret...))
data.DecryptSecretKeyBytes = secret
data.EncryptSecretKeyBytes = secret
data.DecryptCipher, _ = aes.NewCipher(data.DecryptSecretKeyBytes[:])
data.EncryptCipher, _ = aes.NewCipher(data.EncryptSecretKeyBytes[:])
data.DecryptIV = data.DecryptSecretKeyBytes[:aes.BlockSize]
data.EncryptIV = data.DecryptSecretKeyBytes[:aes.BlockSize]
}
type EncryptionHandler struct {
Data *EncryptionData
}
func NewEncryptionHandler() *EncryptionHandler {
return &EncryptionHandler{&EncryptionData{}}
}
func (handler *EncryptionHandler) ComputeSendChecksum(d []byte) []byte {
var buffer []byte
var secret = handler.Data.EncryptSecretKeyBytes[:]
binutils.WriteLittleLong(&buffer, handler.Data.SendCounter)
handler.Data.SendCounter++
var hash = sha256.New()
hash.Write(buffer)
hash.Write(d)
hash.Write(secret)
var sum = hash.Sum(nil)
return sum[:8]
}
================================================
FILE: utils/utils.go
================================================
package utils
import (
"crypto/ecdsa"
"crypto/rand"
"crypto/sha512"
"crypto/x509"
"encoding/base64"
"encoding/json"
"fmt"
"strings"
)
type EncryptionHeader struct {
Algorithm string `json:"alg"`
X5u string `json:"x5u"`
}
type EncryptionPayload struct {
Token string `json:"salt"`
}
func DecodeJwtPayload(v string, t interface{}) {
v = strings.Split(v, ".")[1]
str, err := base64.RawURLEncoding.DecodeString(v)
if err != nil {
fmt.Println(err)
return
}
json.Unmarshal(str, t)
}
func DecodeJwt(v string) []string {
var splits = strings.Split(v, ".")
var jwt []string
for _, split := range splits {
str, err := base64.RawURLEncoding.DecodeString(split)
if err != nil {
fmt.Println(err)
continue
}
jwt = append(jwt, string(str))
}
return jwt
}
func ConstructEncryptionJwt(key *ecdsa.PrivateKey, token []byte) string {
var header = EncryptionHeader{}
header.Algorithm = "ES384"
var b, _ = x509.MarshalPKIXPublicKey(&key.PublicKey)
header.X5u = base64.RawStdEncoding.EncodeToString(b)
var payload = EncryptionPayload{}
payload.Token = base64.RawStdEncoding.EncodeToString(token)
var headerData, _ = json.Marshal(header)
var headerStr = base64.RawURLEncoding.EncodeToString(headerData)
var payloadData, _ = json.Marshal(payload)
var payloadStr = base64.RawURLEncoding.EncodeToString(payloadData)
var hash = sha512.New384()
hash.Write([]byte(headerStr + "." + payloadStr))
var r, s, err = ecdsa.Sign(rand.Reader, key, hash.Sum(nil))
if err != nil {
fmt.Println(err)
}
var signature = base64.RawURLEncoding.EncodeToString(append(r.Bytes(), s.Bytes()...))
return headerStr + "." + payloadStr + "." + signature
}