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 }