111 Commits
Author SHA1 Message Date
adman 96481da0a4 refactor: add context propagation to connection and event systems 2026-04-29 23:38:24 +02:00
adman 159437c1ca fix: handle empty kick status message properly in Status protocol state 2026-04-29 18:48:10 +02:00
adman 3d34ad59e9 allow for custom kick status message 2026-04-29 18:31:36 +02:00
adman 4878c6cc7b fix logger not being and other thing maybe 2026-04-28 22:25:36 +02:00
adman 719a3bacd8 Add fuzz testing for Section encoding/decoding with multiple edge cases 2026-04-28 09:31:35 +02:00
adman ce0c58e7c5 refactor shit 2026-04-27 01:50:45 +02:00
adman 8a4e44760e fix: TestRealStorageResizeDirect and TestRealStorageDirectModeNoPalette bugs
- resize() was writing to wrong array (r.data instead of newData) and using
  wrong elementsPerLong during resize loop
- TestRealStorageDirectModeNoPalette had invalid test data (0x1FFFF needs
  bpe>=17 but test used bpe=9)
2026-04-27 01:45:41 +02:00
adman e810a60848 111111 2026-04-27 01:26:56 +02:00
adman 0981339028 1111 2026-04-27 01:03:47 +02:00
adman 7746dd8ee9 real storage 2026-04-27 00:17:56 +02:00
adman 378b78312e storage=????? 2026-04-25 18:00:10 +02:00
adman bd3139754d add import storage and test 2026-04-23 22:36:34 +02:00
adman 841d91b91d storage 2026-04-23 21:30:17 +02:00
adman 895ec53698 parse the world 2026-04-21 22:24:12 +02:00
adman f6001c8b22 mojang cant get their cases straight
i had to implement like 4 cases just  to parse that file
2026-04-21 09:38:14 +02:00
adman d87c362270 anvil testdata 2026-04-21 08:35:40 +02:00
adman 236e414242 anvil start 2026-04-21 08:35:24 +02:00
adman 5222390b65 nbt changes 2026-04-21 08:35:18 +02:00
adman dc04fdc7d0 remove redundant bool arg 2026-04-20 23:38:02 +02:00
adman 8de34dfda1 mapstructure 2026-04-20 23:16:01 +02:00
adman 997594d7c3 fix chunk sections being one 2026-04-20 20:23:25 +02:00
adman c83a777eac mojang is weird i tell them 1 and they turn it into 4 2026-04-20 18:12:25 +02:00
adman 65fea61667 chunk builder 2026-04-19 17:41:13 +02:00
adman c2777f2ca9 chunk serialization fixes and fuzzies 2026-04-19 16:10:09 +02:00
adman dcb42f1970 chunk serialization 2026-04-19 11:04:37 +02:00
adman 7a2081f0a0 support kicker status 2026-04-17 21:51:47 +02:00
adman acc583f694 more logging 2026-04-14 09:20:00 +02:00
adman 76409d07a3 fix smart convert panic 2026-04-12 23:16:36 +02:00
adman bb89730781 fix bug with treshold 0 never compressing instead of always compressing 2026-04-12 23:16:19 +02:00
adman 9f6b1f6bcf more logging for status cli so you can debug address resolution 2026-04-12 23:15:50 +02:00
adman 1b8a9677c6 reduce binary size by not bundling bedrock data 2026-04-12 23:15:16 +02:00
adman 79ab13ca4b server encryption and online mode 2026-04-10 17:28:13 +02:00
adman 3cc414185a implement compression threshold setting 2026-04-10 14:30:35 +02:00
adman fe4a17dce2 Shutdownserver error 2026-04-08 00:31:05 +02:00
adman 891551f9dd simple api cli 2026-04-06 00:34:28 +02:00
adman 8fb48c6ea9 add version flag to status cmd 2026-04-05 23:39:08 +02:00
adman ec25c28061 Remove unused import 2026-04-05 23:38:57 +02:00
adman 45287f47d2 Expose lower register functions
Added RegisterLow and RegisterUntilLow

this gives more power to the user
2026-04-05 16:07:05 +02:00
adman 4cc4b6af5b fix earlies version
It was the latest version i dunno what i was thinking?
2026-04-05 16:05:33 +02:00
adman 09e6760d61 fix timer drifting off and delte old code 2026-04-05 16:04:53 +02:00
adman ca74a3c3d4 more explicit asyncPackets switch 2026-04-04 19:32:46 +02:00
adman fbc3693219 Revert "Send event"
This reverts commit abb0fa4d37.
2026-04-04 15:31:30 +02:00
Admin Else fb69fd93ec my code broke after i changed this last time and this fixed it 2026-04-03 19:01:33 +02:00
Admin Else a3a3c3714e Maybe fix contex done err 2026-04-03 19:00:52 +02:00
Admin Else 9294631c6c fix race condition 2026-04-03 09:05:24 +02:00
Admin Else 293f0b1eed this makes debugging easier trust me 2026-04-03 09:05:10 +02:00
Admin Else 272dcb4d36 warn instead of crash on unexpected packet 2026-04-02 18:49:50 +02:00
Admin Else abb0fa4d37 Send event 2026-04-02 18:49:30 +02:00
Admin Else e5212624af text components prints with tags 2026-04-01 16:43:47 +02:00
Admin Else 35772aca03 Cancle and signal errors shouldnt be voided 2026-03-31 21:57:04 +02:00
Admin Else 819d89b26e Delte schematic server 2026-03-31 17:04:44 +02:00
Admin Else 958102e43c fixes for config in 1.21.11 2026-03-31 16:51:07 +02:00
Admin Else f749d3bfc5 Add support for empty registry serving 2026-03-29 21:16:28 +02:00
Admin Else d207cf52cf 1.21.11 data registry 2026-03-29 21:09:05 +02:00
Admin Else 420ec5611c Fix register(critical)?untilLatest being inverted 2026-03-29 21:08:48 +02:00
Admin Else 4bfaf9b7c5 Remove redundant return 2026-03-29 21:07:23 +02:00
Admin Else 48beb861e8 Add vscode folder to gitignore 2026-03-29 17:50:33 +02:00
Admin Else 9fd2f517d9 Fix SmartConver for slices 2026-03-29 17:34:41 +02:00
Admin Else 1b5af63b75 Fix packet inspector 2026-03-29 17:13:11 +02:00
Admin Else dfb847eb3e Fix registry hunter 2026-03-29 17:11:40 +02:00
Admin Else 322bd77f7f Remove redundant return statement 2026-03-29 14:35:46 +02:00
Admin Else aefe850281 Fix send debug message 2026-03-29 14:21:54 +02:00
Admin Else 57f88f2176 Fix debug message 2026-03-29 14:20:02 +02:00
Admin Else 8dd566c0da Add DebugPrintPackets and fix not translating packet id 2026-03-29 14:15:31 +02:00
Admin Else e386a45d96 Add basic README.md 2026-03-28 18:14:41 +01:00
adman 8f771166a6 better unknown command text 2026-03-27 13:16:16 +01:00
adman 5d6a1a25ea fix packet spy 2026-03-27 13:09:07 +01:00
adman ca33185f20 fix LookupPacketInfoByType for undecoable 2026-03-27 12:59:10 +01:00
adman e921e9e9d2 fix routine thing 2026-03-27 00:03:01 +01:00
adman 7337068d86 more performance 2026-03-26 23:34:30 +01:00
adman 9983062522 fix timer 2026-03-26 22:39:48 +01:00
adman 4fd5324064 new event system (again) 2026-03-26 22:19:03 +01:00
adman df0e5e354b more srv stuff and packet inspetor 2026-03-25 19:00:18 +01:00
adman 23b5a5d9b7 move cmd to more sensible location 2026-03-25 14:53:38 +01:00
adman 46770ed980 Account changes
- now supports custom enviorments/servers
- cleaner code imo
2026-03-25 14:48:42 +01:00
adman 6179c5f6b6 fix proxy 2026-03-24 22:36:14 +01:00
adman f758a10575 performance fix 2026-03-24 21:50:52 +01:00
adman 41d1ab4c45 performance 2026-03-24 17:46:23 +01:00
adman 032fa26927 fix some race conditions 2026-03-22 22:51:29 +01:00
adman f7401a287e minecraft data raw code 2026-03-22 19:49:37 +01:00
adman 2d1e8ef4f6 go get does not support git submodules 2026-03-22 19:48:21 +01:00
adman 2802cc36ce litematic format 2026-03-22 19:45:05 +01:00
adman ea024fe808 look at the code if you want to know what i did 2026-03-22 00:35:07 +01:00
adman a6102b7ec9 working srv status 2026-03-20 22:19:07 +01:00
adman 254b75e76b remove onstart handler thing in favor of the user doing that themselves 2026-03-20 22:10:40 +01:00
adman 33cec6ecc9 mark lpvec3d as todo 2026-03-20 18:53:51 +01:00
adman fbf9bacd05 go vet fixup 2026-03-20 18:51:17 +01:00
adman 29baf8da9f test fixup 2026-03-20 17:57:03 +01:00
adman 43fea40d08 dns fix 2026-03-20 17:08:54 +01:00
adman b107eba37c dns 2026-03-19 23:50:28 +01:00
adman abc0f6aebb move some stuff and add registercritical 2026-03-19 22:28:20 +01:00
adman 8c6172dcda cleanup protodef type locations 2026-03-19 21:57:05 +01:00
adman 57e78478cd fix status cli 2026-03-18 21:23:48 +01:00
adman f554e91354 move a few tools to a more central utility cli 2026-03-18 20:45:54 +01:00
adman e0cf1834f1 items and struture loading 2026-03-12 23:13:25 +01:00
adman 047c4d02ff version compat system 2026-03-11 09:18:58 +01:00
adman c516cc5283 version compat system start 2026-03-09 21:30:31 +01:00
adman 165075ea02 Refactor event system 2026-03-08 13:56:36 +01:00
adman 2eee7bde24 refactor event systems 2026-03-08 11:52:25 +01:00
adman 73d4e07b06 block state 2026-03-06 21:24:08 +01:00
adman 5dfd8fc499 accidentally regen again and block state early work 2026-03-03 09:31:44 +01:00
adman 1b36949316 regenerate proto 2026-03-02 22:33:17 +01:00
adman 5c455bd2c8 regenerate proto 2026-03-02 22:26:40 +01:00
adman 8d1a4b581b shitty bitfield packing code 2026-03-02 22:24:26 +01:00
adman 085b83cb71 fix stupid protodef stuff 2026-03-02 21:40:36 +01:00
adman 65bf63d67e Update unCodable packet hunter to new scheme 2026-03-02 11:08:22 +01:00
adman d8cc9cab48 stuff 2026-03-02 00:14:57 +01:00
adman f7d7a72b7c nbt files 2026-03-01 13:28:49 +01:00
adman 29b1b92113 Add server infrastructure with login, status, and kick handlers 2026-02-24 23:28:25 +01:00
adman bd83b9689e Refactor server modules into main package and improve error handling 2026-02-24 23:27:56 +01:00
adman 85529b965b egg 2026-01-25 12:55:27 +01:00
1391 changed files with 14459787 additions and 11752 deletions
+2
View File
@@ -1,3 +1,5 @@
.devres/
.idea/
.failed_packets/
tools/.*/
.vscode/
-3
View File
@@ -1,3 +0,0 @@
[submodule "data/minecraft-data"]
path = data/minecraft-data
url = git@github.com:PrismarineJS/minecraft-data.git
+37 -178
View File
@@ -1,202 +1,61 @@
Copyright © 2007 Free Software Foundation, Inc. <https://fsf.org/>
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed.
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below.
0. Additional Definitions.
1. Definitions.
As used herein, “this License” refers to version 3 of the GNU Lesser General Public License, and the “GNU GPL” refers to version 3 of the GNU General Public License.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
“The Library” refers to a covered work governed by this License, other than an Application or a Combined Work as defined below.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
An “Application” is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
A “Combined Work” is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the “Linked Version”.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
The “Minimal Corresponding Source” for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
The “Corresponding Application Code” for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work.
1. Exception to Section 3 of the GNU GPL.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL.
2. Conveying Modified Versions.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version:
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or
b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
3. Object Code Incorporating Material from Library Header Files.
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following:
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License.
b) Accompany the object code with a copy of the GNU GPL and this license document.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Combined Works.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License.
b) Accompany the Combined Work with a copy of the GNU GPL and this license document.
c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document.
d) Do one of the following:
0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.
1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version.
e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.)
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
5. Combined Libraries.
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following:
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License.
b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
6. Revised Versions of the GNU Lesser General Public License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
The Free Software Foundation may publish revised and/or new versions of the GNU Lesser 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.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License “or any later version” applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library.
+5
View File
@@ -0,0 +1,5 @@
# STROM
# THIS IS WORK IN PROGRESS AND THE API WILL BREAK AS LONG AS THE THE PACKAGE VERSION IS v0.x.x
feel free to contact me on [discord](https://discord.gg/n2Vjc2vcMf)
+64 -37
View File
@@ -9,32 +9,40 @@ import (
"strings"
"time"
"github.com/admin-else/strom/crypto"
"github.com/google/uuid"
)
type Servers struct {
ServicesHost string
SessionHost string
ProfilesHost string
}
var DefaultServers = &Servers{
ServicesHost: "https://api.minecraftservices.com",
SessionHost: "https://sessionserver.mojang.com",
ProfilesHost: "https://api.mojang.com",
}
type Account struct {
*Servers
Name string
Uuid uuid.UUID
Ygg string
}
const (
BaseUrlApi = "https://api.minecraftservices.com"
)
func (a *Account) doMcApiRequest(method string, url string, from any, to any) (err error) {
var b []byte
if method != "GET" {
b, err = json.Marshal(from)
if err != nil {
return err
return
}
}
r, err := http.NewRequest(method, url, bytes.NewReader(b))
if err != nil {
return err
return
}
r.Header.Set("Content-Type", "application/json")
@@ -44,21 +52,22 @@ func (a *Account) doMcApiRequest(method string, url string, from any, to any) (e
resp, err := http.DefaultClient.Do(r)
if err != nil {
return err
return
}
defer resp.Body.Close()
if !(resp.StatusCode >= 200 && resp.StatusCode <= 299) {
return fmt.Errorf("bad status code %v", resp.StatusCode)
}
b, err = io.ReadAll(resp.Body)
if err != nil {
return err
return
}
err = json.Unmarshal(b, to)
if err != nil {
return err
return
}
return nil
return
}
type PlayerKeys struct {
@@ -73,7 +82,7 @@ type PlayerKeys struct {
}
func (a *Account) FetchKeys() (keys PlayerKeys, err error) {
err = a.doMcApiRequest("POST", BaseUrlApi+"/player/certificates", nil, &keys)
err = a.doMcApiRequest("POST", a.ServicesHost+"/player/certificates", nil, &keys)
return
}
@@ -90,10 +99,12 @@ func (a *Account) JoinServer(serverId string) (err error) {
if err != nil {
return
}
resp, err := http.Post("https://sessionserver.mojang.com/session/minecraft/join", "application/json", bytes.NewReader(body))
resp, err := http.Post(a.SessionHost+"/session/minecraft/join", "application/json", bytes.NewReader(body))
if err != nil {
return
}
defer resp.Body.Close()
if resp.StatusCode != 204 {
body, err = io.ReadAll(resp.Body)
if err != nil {
@@ -112,17 +123,10 @@ type NameChangResp struct {
}
func (a *Account) NameChangeInfo() (r NameChangResp, err error) {
err = a.doMcApiRequest("GET", "https://api.minecraftservices.com/minecraft/profile/namechange", nil, &r)
err = a.doMcApiRequest("GET", a.ServicesHost+"/minecraft/profile/namechange", nil, &r)
return
}
func NewOfflineAccount(name string) *Account {
return &Account{
Name: name,
Uuid: crypto.FromOfflinePlayer(name),
}
}
// PlayerProfile represents the player's profile information
type PlayerProfile struct {
ID string `json:"id"`
@@ -143,7 +147,7 @@ type PlayerProfile struct {
// QueryProfile queries the player's profile
func (a *Account) QueryProfile() (profile PlayerProfile, err error) {
err = a.doMcApiRequest("GET", BaseUrlApi+"/minecraft/profile", nil, &profile)
err = a.doMcApiRequest("GET", a.ServicesHost+"/minecraft/profile", nil, &profile)
return
}
@@ -181,7 +185,7 @@ type PlayerAttributes struct {
// QueryAttributes queries the player's attributes
func (a *Account) QueryAttributes() (attrs PlayerAttributes, err error) {
err = a.doMcApiRequest("GET", BaseUrlApi+"/player/attributes", nil, &attrs)
err = a.doMcApiRequest("GET", a.ServicesHost+"/player/attributes", nil, &attrs)
return
}
@@ -194,7 +198,7 @@ type ModifyAttributesRequest struct {
// ModifyAttributes modifies the player's attributes
func (a *Account) ModifyAttributes(req ModifyAttributesRequest) (attrs PlayerAttributes, err error) {
err = a.doMcApiRequest("POST", BaseUrlApi+"/player/attributes", req, &attrs)
err = a.doMcApiRequest("POST", a.ServicesHost+"/player/attributes", req, &attrs)
return
}
@@ -205,24 +209,23 @@ type BlockedUsers struct {
// GetBlocklist gets the list of blocked users
func (a *Account) GetBlocklist() (blocklist BlockedUsers, err error) {
err = a.doMcApiRequest("GET", BaseUrlApi+"/privacy/blocklist", nil, &blocklist)
err = a.doMcApiRequest("GET", a.ServicesHost+"/privacy/blocklist", nil, &blocklist)
return
}
// CheckNameAvailability checks if a name is available
type NameAvailability struct {
Status string `json:"status"` // DUPLICATE, AVAILABLE, NOT_ALLOWED
}
// CheckNameAvailability checks if a name is available
func (a *Account) CheckNameAvailability(name string) (avail NameAvailability, err error) {
err = a.doMcApiRequest("GET", BaseUrlApi+"/minecraft/profile/name/"+name+"/available", nil, &avail)
err = a.doMcApiRequest("GET", a.ServicesHost+"/minecraft/profile/name/"+name+"/available", nil, &avail)
return
}
// ChangeName changes the player's name
func (a *Account) ChangeName(newName string) (profile PlayerProfile, err error) {
err = a.doMcApiRequest("PUT", BaseUrlApi+"/minecraft/profile/name/"+newName, nil, &profile)
err = a.doMcApiRequest("PUT", a.ServicesHost+"/minecraft/profile/name/"+newName, nil, &profile)
return
}
@@ -234,19 +237,19 @@ type ChangeSkinRequest struct {
// ChangeSkin changes the player's skin
func (a *Account) ChangeSkin(req ChangeSkinRequest) (profile PlayerProfile, err error) {
err = a.doMcApiRequest("POST", BaseUrlApi+"/minecraft/profile/skins", req, &profile)
err = a.doMcApiRequest("POST", a.ServicesHost+"/minecraft/profile/skins", req, &profile)
return
}
// ResetSkin resets the player's skin to default
func (a *Account) ResetSkin() (profile PlayerProfile, err error) {
err = a.doMcApiRequest("DELETE", BaseUrlApi+"/minecraft/profile/skins/active", nil, &profile)
err = a.doMcApiRequest("DELETE", a.ServicesHost+"/minecraft/profile/skins/active", nil, &profile)
return
}
// HideCape hides the player's active cape
func (a *Account) HideCape() (profile PlayerProfile, err error) {
err = a.doMcApiRequest("DELETE", BaseUrlApi+"/minecraft/profile/capes/active", nil, &profile)
err = a.doMcApiRequest("DELETE", a.ServicesHost+"/minecraft/profile/capes/active", nil, &profile)
return
}
@@ -258,29 +261,53 @@ type ShowCapeRequest struct {
// ShowCape shows a specific cape
func (a *Account) ShowCape(capeID string) (profile PlayerProfile, err error) {
req := ShowCapeRequest{CapeID: capeID}
err = a.doMcApiRequest("PUT", BaseUrlApi+"/minecraft/profile/capes/active", req, &profile)
err = a.doMcApiRequest("PUT", a.ServicesHost+"/minecraft/profile/capes/active", req, &profile)
return
}
// CheckGiftCodeValidity checks if a gift code is valid
func (a *Account) CheckGiftCodeValidity(giftCode string) (valid bool, err error) {
url := BaseUrlApi + "/productvoucher/giftcode"
url := a.ServicesHost + "/productvoucher/" + giftCode
r, err := http.NewRequest("GET", url, nil)
if err != nil {
return false, err
return
}
r.Header.Set("Authorization", "Bearer "+a.Ygg)
resp, err := http.DefaultClient.Do(r)
if err != nil {
return false, err
return
}
defer resp.Body.Close()
// Returns HTTP 200 or 204 if valid, 404 if invalid
if resp.StatusCode == 200 || resp.StatusCode == 204 {
return true, nil
valid = true
return
}
return
}
func NewAccountFromYGG(ygg string) (a *Account, err error) {
a = &Account{
Ygg: ygg,
Servers: DefaultServers,
}
p, err := a.QueryProfile()
if err != nil {
return
}
a.Name = p.Name
a.Uuid, err = uuid.Parse(strings.ReplaceAll(p.ID, "-", ""))
return
}
func NewAccount(name string, uuid uuid.UUID, ygg string) (a *Account) {
return &Account{
Name: name,
Uuid: uuid,
Ygg: ygg,
Servers: DefaultServers,
}
return false, nil
}
+10
View File
@@ -0,0 +1,10 @@
package api
import "github.com/admin-else/strom/crypto"
func NewOfflineAccount(name string) *Account {
return &Account{
Name: name,
Uuid: crypto.FromOfflinePlayer(name),
}
}
+130
View File
@@ -0,0 +1,130 @@
package api
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"strings"
)
func BlockedServers() (s []string, err error) {
resp, err := http.Get("https://sessionserver.mojang.com/blockedservers")
if err != nil {
return
}
defer resp.Body.Close()
d, err := io.ReadAll(resp.Body)
if err != nil {
return
}
s = strings.Split(string(d), "\n")
return
}
type ProfileProperty struct {
Name string `json:"name"`
Signature string `json:"signature,omitempty"`
Value string `json:"value"`
}
type ProfileResp struct {
Id string `json:"id"`
Name string `json:"name"`
Legacy bool `json:"legacy"`
Properties []ProfileProperty `json:"properties"`
}
func HasJoined(name, server, ip string) (p ProfileResp, err error) {
url := "https://sessionserver.mojang.com/session/minecraft/hasJoined?username=" + name + "&serverId=" + server
if ip != "" {
url += "&ip=" + ip
}
resp, err := http.Get(url)
if err != nil {
return
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
err = fmt.Errorf("bad status code %v", resp.StatusCode)
return
}
err = json.NewDecoder(resp.Body).Decode(&p)
return
}
type MojangPublicKeysResp struct {
ProfilePropertyKeys []string `json:"profilePropertyKeys"`
PlayerCertificateKeys []string `json:"playerCertificateKeys"`
// AuthenticationKeys are undocumented by wiki.vg but they exist as of the time of writing so ill include them
AuthenticationKeys []string `json:"authenticationKeys"`
}
func MojangPublicKeys() (keys MojangPublicKeysResp, err error) {
resp, err := http.Get("https://sessionserver.mojang.com/session/minecraft/profile/public_key_by_token")
if err != nil {
return
}
defer resp.Body.Close()
err = json.NewDecoder(resp.Body).Decode(&keys)
return
}
func Profile(id string, unsinged bool) (p ProfileResp, err error) {
url := "https://sessionserver.mojang.com/session/minecraft/profile/" + id
if !unsinged {
url += "?unsigned=false"
}
resp, err := http.Get(url)
if err != nil {
return
}
defer resp.Body.Close()
err = json.NewDecoder(resp.Body).Decode(&p)
return
}
type IdName struct {
Id string `json:"id"`
Name string `json:"name"`
}
func NameToId(name string) (idName IdName, err error) {
// may also use these:
//https://api.mojang.com/users/profiles/minecraft/<player name>
//https://api.minecraftservices.com/minecraft/profile/lookup/name/<player name>
//https://api.mojang.com/minecraft/profile/lookup/name/<player name>
resp, err := http.Get("https://api.minecraftservices.com/minecraft/profile/lookup/name/" + name)
if err != nil {
return
}
defer resp.Body.Close()
err = json.NewDecoder(resp.Body).Decode(&idName)
return
}
func NameToIdBulk(names []string) (idNames []IdName, err error) {
b := bytes.NewBuffer(nil)
err = json.NewEncoder(b).Encode(names)
if err != nil {
return
}
// may also use these:
//https://api.mojang.com/profiles/minecraft
//https://api.minecraftservices.com/minecraft/profile/lookup/bulk/byname
//https://api.mojang.com/minecraft/profile/lookup/bulk/byname
r, err := http.NewRequest("POST", "https://api.minecraftservices.com/minecraft/profile/lookup/bulk/byname", b)
if err != nil {
return
}
r.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(r)
if err != nil {
return
}
defer resp.Body.Close()
err = json.NewDecoder(resp.Body).Decode(&idNames)
return
}
+22 -5
View File
@@ -1,22 +1,39 @@
package client
import (
"context"
"net"
"github.com/admin-else/strom/proto"
"github.com/admin-else/strom/proto_base"
)
// Connect connects to the given address note that it does not resolve the SRV records it's just a raw net.Dial
// You must close the connection when you're done with it
func Connect(addr string) (ret *proto.Conn, err error) {
ret = &proto.Conn{}
err = ret.SetVersion("1.21.8")
s, err := Status(addr)
if err != nil {
return
}
ret.SetState(proto_base.Handshaking)
ret.CompressionThreshold = -1
ret, err = ConnectVersionLess(addr)
if err != nil {
return
}
err = ret.SetProtocolVersion(s.Version.Protocol)
return
}
func ConnectVersionLess(addr string) (ret *proto.Conn, err error) {
return ConnectVersionLessCtx(context.Background(), addr)
}
// ConnectVersionLess connects to the given address without setting the version you should set it
// when using this function.
// You must close the connection when you're done with it
func ConnectVersionLessCtx(ctx context.Context, addr string) (ret *proto.Conn, err error) {
ret = proto.NewConnCtx(ctx)
ret.Actor = proto_base.Client
ret.Conn, err = net.Dial("tcp", addr)
ret.Conn, err = (&net.Dialer{}).DialContext(ctx, "tcp", addr)
if err != nil {
return
}
@@ -1,4 +1,4 @@
package modules
package client
import (
"errors"
@@ -14,8 +14,12 @@ type ConfigIgnorer struct {
*proto.Conn
}
func (c *ConfigIgnorer) Default(event any) (err error) {
fmt.Printf("%#v\n", event)
func (c *ConfigIgnorer) OnStart() (err error) {
return
}
func (c *ConfigIgnorer) Default(e event.Anything) (err error) {
c.Log.Debug("Config ignorer", "packet", fmt.Sprintf("%#v", e))
return
}
@@ -23,7 +27,7 @@ func (c *ConfigIgnorer) OnKnownPacks(packet *v1_21_8.ConfigurationToClientPacket
return c.Send(&v1_21_8.ConfigurationToServerPacketCommonSelectKnownPacks{Packs: packet.Packs})
}
func (c *ConfigIgnorer) OnFinish(_ v1_21_8.ConfigurationToClientPacketFinishConfiguration) (err error) {
func (c *ConfigIgnorer) OnFinish(_ *v1_21_8.ConfigurationToClientPacketFinishConfiguration) (err error) {
err = c.Send(&v1_21_8.ConfigurationToServerPacketFinishConfiguration{})
if err != nil {
return
@@ -44,7 +48,14 @@ func (c *ConfigIgnorer) OnKeepAlive(packet *v1_21_8.ConfigurationToClientPacketK
}
func IgnoreConfig(c *proto.Conn) (err error) {
err = c.Start(&ConfigIgnorer{c})
ci := &ConfigIgnorer{c}
ci.RegisterCritical(ci.Default)
ci.RegisterUntilLatest(ci.OnKnownPacks)
ci.RegisterUntilLatest(ci.OnFinish)
ci.RegisterUntilLatest(ci.OnPing)
ci.RegisterUntilLatest(ci.OnKeepAlive)
err = ci.StartConn()
if err != nil {
err = errors.Join(err, errors.New("failed to ignore config"))
}
+113 -9
View File
@@ -1,22 +1,126 @@
package client
import (
"context"
"errors"
"net"
"strconv"
"strings"
"time"
)
func LookupMinecraftSRV(host string, port uint16) (hostRet string, portRet uint16) {
hostRet = host // in case of error
portRet = port
if port != 25565 {
type DNSCheckFunc func(addr string) error
// DoDns resolves a DNS name to an IP address
// It should resolve a minecraft compatible address into something net.Dial
func DoDns(in string) (out string, host string, portN uint16, err error) {
return DoDNSChecked(in, nil)
}
// DoDNSChecked resolves a DNS name to an IP address
// It should resolve a minecraft compatible address into something net.Dial
// It also optionally accepts a function to "check" the address
func DoDNSChecked(in string, f DNSCheckFunc) (connectTo string, host string, portN uint16, err error) {
// https://mcsrc.dev/1/26.1-pre-1/net/minecraft/client/multiplayer/resolver/ServerAddress#L34
host, port, err := net.SplitHostPort(in)
if err != nil {
var addrErr *net.AddrError
if errors.As(err, &addrErr) && addrErr.Err == "missing port in address" {
err = nil
host = in
port = "25565"
} else {
return
}
}
porti, err := strconv.Atoi(port)
if err != nil {
return
}
_, srvs, err := net.LookupSRV("minecraft", "tcp", host)
if err != nil || len(srvs) == 0 {
if f == nil {
f = func(addr string) error { return nil }
}
err = f(host)
if err != nil {
return
}
target := strings.TrimSuffix(srvs[0].Target, ".")
hostRet = target
portRet = srvs[0].Port
portN = uint16(porti)
outIP, err := net.ResolveIPAddr("ip", host)
if err != nil {
return
}
err = f(outIP.String())
if err != nil {
return
}
connectTo = outIP.String() + ":" + port
if portN != 25565 {
return
}
ctx, cancel := context.WithTimeout(context.Background(), time.Second) // otherwise this takes 10+ seconds
defer cancel()
r := &net.Resolver{}
_, srvs, _ := r.LookupSRV(ctx, "minecraft", "tcp", host)
if len(srvs) == 0 {
return
}
host = strings.TrimSuffix(srvs[0].Target, ".")
portN = srvs[0].Port
outIP, err = net.ResolveIPAddr("ip", host)
if err != nil {
return
}
err = f(outIP.String())
if err != nil {
return
}
connectTo = outIP.String() + ":" + port
return
}
// DoDnsSimple resolves a DNS name to an IP address
// It should resolve a minecraft compatible address into something net.Dial
// It does not check the address
func DoDnsSimple(in string) (out string, err error) {
host, port, err := net.SplitHostPort(in)
if err != nil {
var addrErr *net.AddrError
if errors.As(err, &addrErr) && addrErr.Err == "missing port in address" {
err = nil
host = in
port = "25565"
} else {
return
}
}
porti, err := strconv.Atoi(port)
if err != nil {
return
}
portN := uint16(porti)
_, err = net.ResolveIPAddr("ip", host)
if err != nil {
return
}
out = host + ":" + strconv.Itoa(int(portN))
if portN != 25565 {
return
}
ctx, cancel := context.WithTimeout(context.Background(), time.Second) // otherwise this takes 10+ seconds
defer cancel()
r := &net.Resolver{}
_, srvs, _ := r.LookupSRV(ctx, "minecraft", "tcp", host)
if len(srvs) == 0 {
return
}
host = strings.TrimSuffix(srvs[0].Target, ".")
portN = srvs[0].Port
out = host + ":" + strconv.Itoa(int(portN))
return
}
+27
View File
@@ -0,0 +1,27 @@
package client
import "testing"
func TestDoDNS(t *testing.T) {
type Case struct {
Input string
Host string
portN uint16
}
cases := []Case{
{"localhost", "localhost", 25565},
{"play.hypixel.net", "play.hypixel.net", 25565},
{"1f2d.net", "tcpshield.1f2d.net", 25565},
}
for _, c := range cases {
t.Logf("Testing %s", c.Input)
_, host, portN, err := DoDns(c.Input)
if err != nil {
t.Error(err)
continue
}
if host != c.Host || portN != c.portN {
t.Errorf("expected %s:%d, got %s:%d", c.Host, c.portN, host, portN)
}
}
}
+31
View File
@@ -0,0 +1,31 @@
package client
import (
"net"
"strconv"
"github.com/admin-else/strom/proto"
"github.com/admin-else/strom/proto_base"
"github.com/admin-else/strom/proto_generated/v1_21_8"
)
func MakeHandshakePacket(c *proto.Conn, nextState proto_base.State) (p *v1_21_8.HandshakingToServerPacketSetProtocol, err error) {
return MakeHandshakePacketAddr(c, nextState, c.RemoteAddr().String())
}
// MakeHandshakePacketAddr gets a custom addr
func MakeHandshakePacketAddr(s *proto.Conn, nextState proto_base.State, addr string) (p *v1_21_8.HandshakingToServerPacketSetProtocol, err error) {
p = &v1_21_8.HandshakingToServerPacketSetProtocol{NextState: int32(nextState), ProtocolVersion: s.ProtocolVersion}
var portStr string
p.ServerHost, portStr, err = net.SplitHostPort(addr)
if err != nil {
return
}
var portUint uint64
portUint, err = strconv.ParseUint(portStr, 10, 16)
if err != nil {
return
}
p.ServerPort = uint16(portUint)
return
}
+59 -52
View File
@@ -1,4 +1,4 @@
package modules
package client
import (
"crypto/rand"
@@ -6,12 +6,8 @@ import (
"crypto/x509"
"encoding/json"
"errors"
"fmt"
"net"
"strconv"
"github.com/admin-else/strom/api"
"github.com/admin-else/strom/client"
"github.com/admin-else/strom/crypto"
"github.com/admin-else/strom/event"
"github.com/admin-else/strom/proto"
@@ -30,6 +26,12 @@ type LoginClient struct {
GivenAccount *v1_21_8.LoginToClientPacketSuccess
}
var (
NoTokenErr = errors.New("the account has no token so we cant join online servers")
BadPublicKeyTypeErr = errors.New("the public key is not rsa")
FailedToLoginErr = errors.New("failed to login")
)
type KickedDuringLoginErr struct {
text.Component
}
@@ -38,46 +40,20 @@ func (k KickedDuringLoginErr) Error() string {
return k.String()
}
func (s *LoginClient) Default(event event.Unhandled) (err error) {
err = fmt.Errorf("unexpected event: %#v", event)
return
}
func (s *LoginClient) OnCycle(_ event.Tick) (err error) {
return
}
func (s *LoginClient) OnStart(_ event.OnStart) (err error) {
if s.ServerHost == "" && s.ServerPort == 0 {
var portStr string
s.ServerHost, portStr, err = net.SplitHostPort(s.RemoteAddr().String())
if err != nil {
return
}
var portUint uint64
portUint, err = strconv.ParseUint(portStr, 10, 16)
if err != nil {
return
}
s.ServerPort = uint16(portUint)
}
err = s.Send(&v1_21_8.HandshakingToServerPacketSetProtocol{
ProtocolVersion: s.ProtocolVersion,
ServerHost: s.ServerHost,
ServerPort: s.ServerPort,
NextState: int32(proto_base.Login),
})
if err != nil {
func (s *LoginClient) OnDefault(event event.Unhandled) (err error) {
if _, ok := event.Val.(proto_base.EncodeDecodeAble); !ok {
return
}
s.SetState(proto_base.Login)
err = s.Send(&v1_21_8.LoginToServerPacketLoginStart{Username: s.Account.Name, PlayerUUID: s.Account.Uuid})
s.Log.Warn("unexpected packet during login client", "packet", event.Val)
return
}
func (s *LoginClient) OnClose(_ event.Close) (err error) {
return
}
func (s *LoginClient) OnCompress(compress *v1_21_8.LoginToClientPacketCompress) (err error) {
s.CompressionThreshold = compress.Threshold
s.SetCompressionThreshold(compress.Threshold)
return
}
@@ -87,7 +63,7 @@ func (s *LoginClient) OnEncrypt(packet *v1_21_8.LoginToClientPacketEncryptionBeg
if packet.ShouldAuthenticate {
if s.Account.Ygg == "" {
err = fmt.Errorf("the account has no token so we cant join online servers")
err = NoTokenErr
return
}
serverId := crypto.AuthDigest([]byte(packet.ServerId), sharedSecret, packet.PublicKey)
@@ -103,7 +79,7 @@ func (s *LoginClient) OnEncrypt(packet *v1_21_8.LoginToClientPacketEncryptionBeg
}
pub, ok := pubAny.(*rsa.PublicKey)
if !ok {
err = fmt.Errorf("public key is not rsa")
err = BadPublicKeyTypeErr
return
}
verifyTokenEnc, err := rsa.EncryptPKCS1v15(rand.Reader, pub, packet.VerifyToken)
@@ -145,28 +121,59 @@ func (s *LoginClient) OnSuccess(success *v1_21_8.LoginToClientPacketSuccess) (er
return
}
func Login(c *proto.Conn, account *api.Account) (err error) {
err = c.Start(&LoginClient{
func (s *LoginClient) OnStart() (err error) {
return
}
func LoginRaw(c *proto.Conn, account *api.Account, addr string) (err error) {
lc := &LoginClient{
Conn: c,
Account: account,
})
}
lc.RegisterCritical(lc.OnDefault)
lc.RegisterCritical(lc.OnClose)
lc.RegisterCriticalUntilLatest(lc.OnCompress)
lc.RegisterCriticalUntilLatest(lc.OnEncrypt)
lc.RegisterCriticalUntilLatest(lc.OnDisconnect)
lc.RegisterCriticalUntilLatest(lc.OnSuccess)
p, err := MakeHandshakePacket(lc.Conn, proto_base.Login)
if err != nil {
err = errors.Join(err, errors.New("failed to login"))
return
}
err = lc.Send(p)
if err != nil {
return
}
lc.SetState(proto_base.Login)
err = lc.Send(&v1_21_8.LoginToServerPacketLoginStart{Username: lc.Account.Name, PlayerUUID: lc.Account.Uuid})
err = lc.StartConn()
if err != nil {
err = errors.Join(FailedToLoginErr, err)
}
return
}
func ConnectAndLogin(connectTo string, account *api.Account) (c *proto.Conn, err error) {
c, err = client.Connect(connectTo)
func LoginNoSrv(connectTo string, account *api.Account) (c *proto.Conn, err error) {
c, err = Connect(connectTo)
if err != nil {
return
}
err = c.Start(&LoginClient{
Conn: c,
Account: account,
})
err = LoginRaw(c, account, connectTo)
return
}
func Login(connectTo string, account *api.Account) (c *proto.Conn, err error) {
connectTo, err = DoDnsSimple(connectTo)
if err != nil {
err = errors.Join(errors.New("failed to login"), err)
return
}
c, err = Connect(connectTo)
if err != nil {
return
}
err = LoginRaw(c, account, connectTo)
return
}
-54
View File
@@ -1,54 +0,0 @@
package modules
import (
"fmt"
"strconv"
"strings"
"github.com/admin-else/strom/event"
"github.com/admin-else/strom/proto"
"github.com/admin-else/strom/proto_base"
"github.com/admin-else/strom/data"
"github.com/admin-else/strom/proto_generated/v1_21_8"
)
type StatusClient struct {
*proto.Conn
Status string
}
func (s *StatusClient) Default(event any) (err error) {
err = fmt.Errorf("unexpected event: %v", event)
return
}
func (s *StatusClient) OnStart(_ event.OnStart) (err error) {
parts := strings.Split(s.RemoteAddr().String(), ":")
versionData, err := data.LookUpProtocolVersionByName(s.Version)
if err != nil {
return
}
port, err := strconv.Atoi(parts[1])
if err != nil {
return
}
err = s.Send(&v1_21_8.HandshakingToServerPacketSetProtocol{
ProtocolVersion: int32(versionData.Version),
ServerHost: parts[0],
ServerPort: uint16(port),
NextState: int32(proto_base.Status),
})
if err != nil {
return
}
s.SetState(proto_base.Status)
err = s.Send(&v1_21_8.StatusToServerPacketPingStart{})
return
}
func (s *StatusClient) OnStatus(p v1_21_8.StatusToClientPacketServerInfo) (err error) {
s.Status = p.Response
err = event.HandlerDoneErr{}
return
}
-63
View File
@@ -1,63 +0,0 @@
package modules
import "time"
type IntervalTask struct {
F func() error
LastTrigger time.Time
Interval time.Duration
}
type OnceTask struct {
F func() error
Trigger time.Time
}
type Timer struct {
Once []OnceTask
Interval []IntervalTask
Active bool
}
func (t *Timer) Tick() (err error) {
if !t.Active {
return
}
for i, task := range t.Interval {
if time.Since(task.LastTrigger) >= task.Interval {
task.LastTrigger = time.Now()
err = task.F()
if err != nil {
return
}
}
t.Interval[i] = task
}
var triggerLater []OnceTask
for _, task := range t.Once {
if task.Trigger.After(time.Now()) {
err = task.F()
if err != nil {
return
}
} else {
triggerLater = append(triggerLater, task)
}
}
t.Once = triggerLater
return
}
func (t *Timer) Every(interval time.Duration, f func() error) {
t.Interval = append(t.Interval, IntervalTask{
F: f,
Interval: interval,
})
}
func (t *Timer) In(interval time.Duration, f func() error) {
t.Once = append(t.Once, OnceTask{
F: f,
Trigger: time.Now().Add(interval),
})
}
+91
View File
@@ -0,0 +1,91 @@
package client
import (
"context"
"encoding/json"
"time"
"github.com/admin-else/strom/event"
"github.com/admin-else/strom/proto"
"github.com/admin-else/strom/proto_base"
"github.com/admin-else/strom/server"
"github.com/admin-else/strom/proto_generated/v1_21_8"
)
type StatusClient struct {
*proto.Conn
Status string
PingSendTime, PingReceiveTime time.Time
DoPingRoundTripTime bool
}
func (s *StatusClient) OnStatus(p *v1_21_8.StatusToClientPacketServerInfo) (err error) {
s.Status = p.Response
s.PingSendTime = time.Now()
if s.DoPingRoundTripTime {
err = s.Send(&v1_21_8.StatusToServerPacketPing{Time: s.PingSendTime.UnixMilli()})
} else {
err = event.HandlerDoneErr{}
}
return
}
func (s *StatusClient) OnPong(p *v1_21_8.StatusToClientPacketPing) (err error) {
s.PingReceiveTime = time.Now()
err = event.HandlerDoneErr{}
return
}
// StatusRaw returns a StatusClient that is not connected to a server.
// So ignore the resource leak warning. And maybe attach a warn ignore comment.
// It does not resolve SRV records.
func StatusRaw(ctx context.Context, addr string) (s *StatusClient, err error) {
c, err := ConnectVersionLess(addr)
if err != nil {
return
}
defer c.Close()
err = c.SetVersion("1.21.8")
if err != nil {
return
}
s = &StatusClient{
Conn: c,
}
s.RegisterCriticalUntilLatest(s.OnStatus)
s.RegisterCriticalUntilLatest(s.OnPong)
p, err := MakeHandshakePacketAddr(s.Conn, proto_base.Status, addr)
if err != nil {
return
}
err = s.Send(p)
if err != nil {
return
}
s.SetState(proto_base.Status)
err = s.Send(&v1_21_8.StatusToServerPacketPingStart{})
err = s.StartConn()
return
}
// StatusNoDns is like Status but does not resolve SRV records.
func StatusNoDns(addr string) (status server.StatusResponse, err error) {
s, err := StatusRaw(context.Background(), addr)
if err != nil {
return
}
err = json.Unmarshal([]byte(s.Status), &status)
return
}
// Status resolves the status of the minecraft server at the given address.
func Status(addr string) (status server.StatusResponse, err error) {
addr, err = DoDnsSimple(addr)
if err != nil {
return
}
return StatusNoDns(addr)
}
+60
View File
@@ -0,0 +1,60 @@
package api
import (
"encoding/json"
"errors"
"flag"
"os"
"github.com/admin-else/strom/api"
)
var cmd = flag.NewFlagSet("api", flag.ContinueOnError)
var tokenFlag = cmd.String("token", os.Getenv("MC_TOKEN"), "token to use")
var renameFlag = cmd.String("rename", "", "rename the player")
var uuidFlag = cmd.String("uuid", "", "get data about player with uuid")
var nameFlag = cmd.String("name", "", "get data about player with name")
var unsignedFlag = cmd.Bool("unsigned", true, "use unsigned mode for properties signatures")
var PleaseProvideArgumentErr = errors.New("please provide an argument")
func Run(args []string) (err error) {
err = cmd.Parse(args)
if err != nil {
return
}
a := &api.Account{}
if *tokenFlag != "" {
a, err = api.NewAccountFromYGG(*tokenFlag)
if err != nil {
return
}
}
var data any
if *renameFlag != "" {
data, err = a.ChangeName(*renameFlag)
if err != nil {
return
}
} else if *uuidFlag != "" || *nameFlag != "" {
if *nameFlag != "" {
var idName api.IdName
idName, err = api.NameToId(*nameFlag)
if err != nil {
return
}
*uuidFlag = idName.Id
}
data, err = api.Profile(*uuidFlag, *unsignedFlag)
if err != nil {
return
}
} else {
err = PleaseProvideArgumentErr
return
}
err = json.NewEncoder(os.Stdout).Encode(data)
return
}
+63
View File
@@ -0,0 +1,63 @@
package main
import (
"errors"
"fmt"
"log/slog"
"maps"
"os"
"slices"
"github.com/admin-else/strom/cmd/strom/api"
"github.com/admin-else/strom/cmd/strom/offline_uuid"
"github.com/admin-else/strom/cmd/strom/packet_inspector"
"github.com/admin-else/strom/cmd/strom/print_nbt"
"github.com/admin-else/strom/cmd/strom/status"
)
var ExpectedASubcommandErr = errors.New("expected a subcommand")
var UnknownSubcommandErr = errors.New("unknown subcommand try 'strom help' for a list of subcommands")
var subcommands = map[string]func(args []string) error{
"offline-uuid": offline_uuid.Run,
"print-nbt": print_nbt.Run,
"status": status.Run,
"packet-spy": packet_inspector.Run,
"api": api.Run,
}
func Help(args []string) (err error) {
fmt.Println("Available subcommands", slices.Collect(maps.Keys(subcommands)))
return
}
func mainE() (err error) {
subcommands["help"] = Help // this is here because if it isn't tools/cli/main.go:17:5: initialization cycle for subcommands
// tools/cli/main.go:17:5: subcommands refers to Help
// tools/cli/main.go:22:6: Help refers to subcommands
args := os.Args
// Debug stuff
slog.SetLogLoggerLevel(slog.LevelDebug)
//args = []string{"TEST", "api", "-name", "Admin_Else"}
if len(args) < 2 {
err = ExpectedASubcommandErr
return
}
f, ok := subcommands[args[1]]
if !ok {
err = UnknownSubcommandErr
return
}
err = f(args[2:])
return
}
func main() {
err := mainE()
if err != nil {
slog.Error("exiting", "err", err)
}
}
+20
View File
@@ -0,0 +1,20 @@
package offline_uuid
import (
"flag"
"fmt"
"github.com/admin-else/strom/crypto"
)
var cmd = flag.NewFlagSet("offline-uuid", flag.ContinueOnError)
var nameFlag = cmd.String("name", "Notch", "name of the player")
func Run(args []string) (err error) {
err = cmd.Parse(args)
if err != nil {
return
}
fmt.Println(crypto.FromOfflinePlayer(*nameFlag))
return
}
+129
View File
@@ -0,0 +1,129 @@
package packet_inspector
import (
"flag"
"log/slog"
"github.com/admin-else/strom/api"
"github.com/admin-else/strom/client"
"github.com/admin-else/strom/event"
"github.com/admin-else/strom/nbt"
"github.com/admin-else/strom/proto"
"github.com/admin-else/strom/proto_base"
"github.com/admin-else/strom/proto_generated/v1_21_11"
"github.com/admin-else/strom/server"
"github.com/admin-else/strom/text"
)
var Cmd = flag.NewFlagSet("packet-inspector", flag.ContinueOnError)
var OfflineNameFlag = Cmd.String("name", "packet_inspector", "offline player name")
var Token = Cmd.String("token", "", "token to use")
var TargetAddr = Cmd.String("addr", "127.0.0.1:25565", "address to connect to")
var ListenAddr = Cmd.String("listen", "127.0.0.1:25566", "address to listen on")
type Proxy struct {
Client, Servee *proto.Conn
Data nbt.Tag
}
func (p *Proxy) Start() (err error) {
p.Client.RegisterCritical(p.OnAnything)
p.Servee.RegisterCritical(p.OnAnything)
p.Servee.RegisterCriticalUntilLatest(p.OnFinishConfiguration)
errChan := make(chan error)
go func() {
errChan <- p.Client.StartConn()
}()
go func() {
errChan <- p.Servee.StartConn()
}()
err = <-errChan
return
}
func (p *Proxy) OnAnything(e event.Anything) (err error) {
packet, ok := e.Val.(proto_base.EncodeDecodeAble)
if !ok {
return
}
packetInfo, ok := proto.LookupPacketInfoByType(packet)
if !ok {
return
}
switch packetInfo.Direction {
case proto_base.ToServer:
err = p.Client.Send(packet)
case proto_base.ToClient:
err = p.Servee.Send(packet)
}
return
}
func (p *Proxy) OnFinishConfiguration(packet *v1_21_11.ConfigurationToServerPacketFinishConfiguration) (err error) {
err = p.Client.Send(packet)
if err != nil {
return
}
p.Servee.SetState(proto_base.Play)
p.Client.SetState(proto_base.Play)
err = event.DontForwardErr
return
}
var StatusResponse = server.StatusResponse{
Version: server.StatusResponseVersion{
Name: text.Pretty("STROM"),
Protocol: 772,
},
Players: server.StatusResponsePlayers{},
Description: text.Pretty("Packet spy forwards to " + *TargetAddr),
Favicon: "",
EnforcesSecureChat: false,
}
func Run(args []string) (err error) {
err = Cmd.Parse(args)
if err != nil {
return
}
slog.SetLogLoggerLevel(slog.LevelDebug)
slog.Info("Starting packet inspector", "listen", *ListenAddr)
err = server.StartServerWithOnConn(*ListenAddr, func(serveeConn *proto.Conn) (err error) {
serveeConn.DebugPrintPackets = []string{""}
p := &Proxy{Servee: serveeConn, Client: nil}
var acc = api.NewOfflineAccount(*OfflineNameFlag)
if *Token != "" {
acc, err = api.NewAccountFromYGG(*Token)
if err != nil {
return
}
}
_, err = server.ServeLogin(serveeConn, server.WithOtherAccount(acc), server.WithStatus(StatusResponse))
if err != nil {
return
}
*TargetAddr, err = client.DoDnsSimple(*TargetAddr)
if err != nil {
return
}
c, err := client.Connect(*TargetAddr)
if err != nil {
return
}
c.DebugPrintPackets = []string{""}
err = client.LoginRaw(c, acc, *TargetAddr)
if err != nil {
return
}
defer c.Close()
p.Client = c
err = p.Start()
return
})
return
}
+49
View File
@@ -0,0 +1,49 @@
package print_nbt
import (
"bytes"
"encoding/json"
"flag"
"os"
"github.com/admin-else/strom/nbt"
)
var (
cmd = flag.NewFlagSet("print-nbt", flag.ContinueOnError)
FileFlag = cmd.String("file", "", "The NBT file to print")
HashFormatFlag = cmd.Bool("snbt", false, "will print with go %#v")
)
func Run(args []string) (err error) {
err = cmd.Parse(args)
if err != nil {
return
}
f, err := os.Open(*FileFlag)
if err != nil {
return
}
defer f.Close()
n, err := nbt.ReadUnstructuredFile(f)
if err != nil {
return
}
var b []byte
if *HashFormatFlag {
bb := bytes.NewBuffer(nil)
err = nbt.PrintSNBTAny(n.Value, bb)
if err != nil {
return
}
b = bb.Bytes()
//b = []byte(strings.ReplaceAll(string(b), ",", ",\n"))
} else {
b, err = json.MarshalIndent(n, "", " ")
}
if err != nil {
return
}
_, err = os.Stdout.Write(b)
return
}
+98
View File
@@ -0,0 +1,98 @@
package status
import (
"flag"
"fmt"
"log/slog"
"time"
"github.com/admin-else/strom/client"
"github.com/admin-else/strom/event"
"github.com/admin-else/strom/proto"
"github.com/admin-else/strom/proto_base"
"github.com/admin-else/strom/proto_generated/v1_21_8"
)
var cmd = flag.NewFlagSet("status", flag.ContinueOnError)
var addrFlag = cmd.String("addr", "localhost:25565", "address to connect to")
var srvFlag = cmd.Bool("srv", false, "use SRV records to resolve the address")
var versionFlag = cmd.String("version", "1.21.8", "version to use")
type StatusClient struct {
*proto.Conn
Status string
PingSendTime, PingReceiveTime time.Time
DoPingRoundTripTime bool
}
func (s *StatusClient) OnStatus(p *v1_21_8.StatusToClientPacketServerInfo) (err error) {
s.Status = p.Response
s.PingSendTime = time.Now()
if s.DoPingRoundTripTime {
err = s.Send(&v1_21_8.StatusToServerPacketPing{Time: s.PingSendTime.UnixMilli()})
} else {
err = event.HandlerDoneErr{}
}
return
}
func (s *StatusClient) OnPong(p *v1_21_8.StatusToClientPacketPing) (err error) {
s.PingReceiveTime = time.Now()
err = event.HandlerDoneErr{}
return
}
// StatusRaw returns a StatusClient that is not connected to a server.
// So ignore the resource leak warning. And maybe attach a warn ignore comment.
// It does not resolve SRV records.
func StatusRaw(addr string) (s *StatusClient, err error) {
c, err := client.ConnectVersionLess(addr)
if err != nil {
return
}
defer c.Close()
err = c.SetVersion(*versionFlag)
if err != nil {
return
}
s = &StatusClient{
Conn: c,
}
s.RegisterCriticalUntilLatest(s.OnStatus)
s.RegisterCriticalUntilLatest(s.OnPong)
p, err := client.MakeHandshakePacketAddr(s.Conn, proto_base.Status, addr)
if err != nil {
return
}
err = s.Send(p)
if err != nil {
return
}
s.SetState(proto_base.Status)
err = s.Send(&v1_21_8.StatusToServerPacketPingStart{})
err = s.StartConn()
return
}
func Run(args []string) (err error) {
err = cmd.Parse(args)
if err != nil {
return
}
if *srvFlag {
*addrFlag, err = client.DoDnsSimple(*addrFlag)
if err != nil {
return
}
}
slog.Debug("resolved address to ", "addr", *addrFlag)
status, err := StatusRaw(*addrFlag)
if err != nil {
return
}
fmt.Println(status.Status)
return
}
+21 -6
View File
@@ -1,5 +1,7 @@
package data
import "errors"
// [
//
// {
@@ -14,7 +16,7 @@ package data
// },
type Biome struct {
Id int
Id int32
Name string
Category string
Temperature float64
@@ -24,22 +26,35 @@ type Biome struct {
Color int
}
var BiomesCache = make(map[string][]Biome)
var BiomesCache = make(map[string][]*Biome)
func BiomesForVersion(v string) (ret []Biome) {
func BiomesForVersion(v string) (ret []*Biome) {
var ok bool
if ret, ok = BiomesCache[v]; ok {
return
}
var b []Biome
var b []*Biome
must(LoadVersionedJson(v, "biomes", &b))
BiomesCache[v] = b
return b
}
func LookupBiomeById(version string, id int32) (biome Biome, ok bool) {
func LookupBiomeById(version string, id int32) (biome *Biome, ok bool) {
for _, b := range BiomesForVersion(version) {
if b.Id == int(id) {
if b.Id == id {
biome = b
ok = true
return
}
}
return
}
var UnknownBiomeNameErr = errors.New("unknown biome Name")
func LookupBiomeByName(version string, name string) (biome *Biome, ok bool) {
for _, b := range BiomesForVersion(version) {
if b.Name == name {
biome = b
ok = true
return
+146 -13
View File
@@ -1,6 +1,11 @@
package data
// {
import (
"errors"
"strconv"
)
// {
// "id": 0,
// "name": "air",
// "displayName": "Air",
@@ -35,34 +40,162 @@ type Block struct {
DefaultState int32
MinStateId int32
MaxStateId int32
States []struct {
Name string
Type string
NumValues int
}
Drops []int
BoundingBox string
States []BlockState
Drops []int
BoundingBox string
}
var BlocksCache = make(map[string][]Block)
type BlockState struct {
Name string
Type string
NumValues int32 `json:"num_values"`
Values []string
}
func BlocksForVersion(v string) (ret []Block) {
var BlocksCache = make(map[string][]*Block)
var BlocksCacheIdMap = make(map[string]map[int32]*Block)
func BlocksForVersion(v string) (ret []*Block) {
var ok bool
if ret, ok = BlocksCache[v]; ok {
return
}
var b []Block
var b []*Block
must(LoadVersionedJson(v, "blocks", &b))
BlocksCache[v] = b
return b
}
func LookupBlockByStateId(version string, stateId int32) (block Block, ok bool) {
func LookupBlockByStateId(version string, stateId int32) (block *Block, ok bool) {
idmap, found := BlocksCacheIdMap[version]
if !found {
idmap = make(map[int32]*Block)
for _, b := range BlocksForVersion(version) {
for i := range b.MaxStateId - b.MinStateId + 1 {
idmap[b.MinStateId+i] = b
}
}
BlocksCacheIdMap[version] = idmap
}
block, ok = idmap[stateId]
return
}
var UnknownBlockNameErr = errors.New("unknown block Name")
func LookupBlockByName(version string, name string) (block *Block, ok bool) {
for _, b := range BlocksForVersion(version) {
if b.MinStateId <= stateId && stateId <= b.MaxStateId {
if b.Name == name {
block = b
ok = true
}
}
return
}
func StateIdFromBlocKAndStateMap(version string, name string, stateMap map[string]string) (stateId int32, err error) {
b, ok := LookupBlockByName(version, name)
if !ok {
err = BlockNotFoundErr
return
}
stateId, err = b.IdFromStateMap(stateMap)
return
}
var BlockStateOutOfRangeErr = errors.New("stateId out of range")
var BlockNotFoundErr = errors.New("block not found")
var InvalidBoolValueErr = errors.New("invalid bool value")
var BadBlockStateTypeErr = errors.New("bad block state type")
var BadEnumErr = errors.New("bad enum")
var SupplyAllStatesErr = errors.New("supply all states")
func FromBlockState(version string, stateId int32) (b *Block, stateData map[string]string, err error) {
b, ok := LookupBlockByStateId(version, stateId)
if !ok {
err = BlockNotFoundErr
}
stateData, err = b.StateMapFromId(stateId)
return
}
func mcDataBlockStateTypeParse(n int32, s *BlockState) (v string, err error) {
switch s.Type {
case "int":
v = strconv.Itoa(int(n))
case "enum":
v = s.Values[n]
case "bool":
if n == 0 { // MOJANK: 0 is true, 1 is false
v = "true"
} else if n == 1 {
v = "false"
} else {
err = InvalidBoolValueErr
}
default:
err = BadBlockStateTypeErr
}
return
}
func mcDataBlockStateTypeSerialize(v string, s *BlockState) (n int32, err error) {
if v == "true" {
return 0, nil
}
if v == "false" {
return 1, nil
}
n1, errMabye := strconv.Atoi(v)
if errMabye == nil {
return int32(n1), nil
}
for i, enumValue := range s.Values {
if enumValue == v {
return int32(i), nil
}
}
err = BadEnumErr
return
}
func (b *Block) StateMapFromId(stateId int32) (ret map[string]string, err error) {
if stateId < b.MinStateId || stateId > b.MaxStateId {
err = BlockStateOutOfRangeErr
return
}
ret = make(map[string]string)
stateId -= b.MinStateId
a := int32(1)
for i := range len(b.States) {
s := b.States[len(b.States)-1-i]
ret[s.Name], err = mcDataBlockStateTypeParse((stateId/a)%s.NumValues, &s)
if err != nil {
return
}
a *= s.NumValues
}
return
}
func (b *Block) IdFromStateMap(m map[string]string) (ret int32, err error) {
a := int32(1)
for i := range len(b.States) {
s := b.States[len(b.States)-1-i]
v, found := m[s.Name]
if !found {
err = SupplyAllStatesErr
return
}
var n int32
n, err = mcDataBlockStateTypeSerialize(v, &s)
if err != nil {
return
}
ret += n * a
a *= s.NumValues
}
ret += b.MinStateId
return
}
+18
View File
@@ -14,3 +14,21 @@ func TestLookupBlockByStateId(t *testing.T) {
}
fmt.Println(b)
}
func TestBlockStates(t *testing.T) {
var stateId int32 = 8301
b, stateMap, err := data.FromBlockState("1.21.8", stateId)
if err != nil {
t.Fatal(err)
return
}
t.Logf("Block state map: %v, block: %v", stateMap, b.Name)
stateId2, err := b.IdFromStateMap(stateMap)
if err != nil {
t.Fatal(err)
return
}
if stateId2 != stateId {
t.Fatal("state id mismatch")
}
}
+1 -1
View File
@@ -6,7 +6,7 @@ import (
"errors"
)
//go:embed minecraft-data/data
//go:embed minecraft-data/data/pc minecraft-data/data/dataPaths.json
var MinecraftData embed.FS
func LoadJson(path string, data any) (err error) {
+7
View File
@@ -0,0 +1,7 @@
#!/usr/bin/env sh
# Go get / install does not clone git submodules so we have to do it like this
rm -rf minecraft-data/
git clone --depth 1 git@github.com:PrismarineJS/minecraft-data.git
rm -rf minecraft-data/.git/
+57
View File
@@ -0,0 +1,57 @@
package data
import "sync"
// {
// "id": 2,
// "name": "granite",
// "displayName": "Granite",
// "stackSize": 64
// },
type Item struct {
Id int32
Name string
DisplayName string
StackSize int32
}
var ItemsCache = make(map[string][]*Item)
var ItemsCacheMutex = &sync.RWMutex{}
func ItemsForVersion(v string) (ret []*Item) {
var ok bool
ItemsCacheMutex.RLock()
if ret, ok = ItemsCache[v]; ok {
ItemsCacheMutex.RUnlock()
return
}
ItemsCacheMutex.RUnlock()
var b []*Item
must(LoadVersionedJson(v, "items", &b))
ItemsCacheMutex.Lock()
ItemsCache[v] = b
ItemsCacheMutex.Unlock()
return b
}
func LookupItemById(version string, id int32) (item *Item, ok bool) {
for _, i := range ItemsForVersion(version) {
if i.Id == id {
item = i
ok = true
return
}
}
return
}
func LookupItemByName(version string, name string) (item *Item, ok bool) {
for _, i := range ItemsForVersion(version) {
if i.Name == name {
item = i
ok = true
}
}
return
}
+2
View File
@@ -0,0 +1,2 @@
# These are supported funding model platforms
@@ -0,0 +1,4 @@
---
name: Blank Issue
about: Open a new issue
---
@@ -0,0 +1,14 @@
---
name: New Version
about: Add a new minecraft version to MCD
title: 'Add version: '
---
- [ ] Version folder containing "version.json" created
- [ ] "version.json" filled out appropriately
- [ ] Added version to dataPath.json
- [ ] "version" field of dataPath.json points to new version folder
- [ ] Version added to common/versions.json
- [ ] Version added to ReadMe
Additional Requirements For This Update:
+93
View File
@@ -0,0 +1,93 @@
PrismarineJS/minecraft-data holds Minecraft data including game versions, protocol schemas, block/item/entity states, and more. It provides a comprehensive cross-language set of data files combined with schemas that can be consumed in any language.
## Directory Structure
- `.github/`
- `workflows/`
- `ci.yml` -- Runs CI here (cd tools/js && npm install && npm test)
- `bedrock-ci.yml` -- Runs CI for Bedrock Edition via workflow dispatch to PrismarineJS/bedrock-protocol. This allows for testing and validation of Bedrock-specific changes.
- `commands.yml` -- Slash commands
- `handleMcpcGeneratedArtifacts.js`
- `helper-bot/` -- Files for the GitHub Actions cron helper.
- `data/`
- `dataPaths.json` -- map of { ['pc' | 'bedrock']: { '$version': { 'dataType': 'folderPath' } } }
- `[pc | bedrock]/` -- Minecraft Java Edition data
- `1.20/` -- Versioned data for Minecraft Java Edition 1.20
- `$version/` -- Versioned data for Minecraft Java Edition $version
- `common/` -- Common data for all Minecraft Java Edition versions
- `protocolVersions.json` -- List of protocol versions for Minecraft Java Edition
- `latest/` -- This holds the latest protocol yaml files. They are used to generate the protocol.json files in each versioned folder (more on this below).
- `schemas/` -- JSON schemas for validating data files
- `tools/js` -- JavaScript utilities for working with repo (holds tests, some data generators and helper scripts)
## Setup
No setup is needed as we just hold data. To set up the development environment that runs tests, run the following commands:
```bash
cd tools/js
npm install
```
This will ensure all data matches schema among other checks.
There are no 'releases' in this repo beyond updating the data itself. Instead, we have a workflow that will automatically create tagged releases if the user runs the /makerelease slash command, so you can inform the user about that if a release is needed.
## Testing
Always run tests after data changes to ensure local tests are passing:
```sh
cd tools/js
npm install
npm test -- --bail 2>&1 | tail -100
```
## Data
Most data is generated with data generators. For mcpc, data is generated with [minecraft-data-generator](https://github.com/PrismarineJS/minecraft-data-generator).
Avoid updating data manually unless necessary, like when specific data is not automated yet (for tips on what is and not, see doc/add-data-new-version.md). Instead, update the data generators or add new ones as needed. Since you don't have capability to make changes outside this repo, please inform the user what changes need to be made and be as detailed as possible.
Note we do not currently have any automation setup to generate bedrock edition data.
## Github Workflows
### Automation
Our GH Actions workflow (.github/helper-bot) auto updates the pc protocolVersions.json with new detected versions and also auto opens a scaffolding PR.
cron workflow .github/workflows/update-helper.yml -> .github/helper-bot/.
helper-bot dispatches to minecraft-data-generator so that it can run data generators for new versions. minecraft-data-generator then sends a workflow dispatch back to us so we can then take its generated artifacts and commit them here.
.github/workflows/handle-mcpc-generator.yml ->
.github/helper-bot/handleMcpcGeneratedArtifacts.js
## Notes
Not all data is generated. Some data (like protocol schemas) is manually curated on both pc and bedrock.
### Protocol data
We use a special YAML-like DSL to generate protocol.json files. Refer to doc/protocol.md for info.
These files are stored inside proto.yml (and an imported types.yml support file on bedrock) files in the latest/ folder (like bedrock/latest/proto.yml) for the latest version, otherwise in the versioned folder (like pc/1.20/proto.yml).
👉 Run `npm run build` in tools/js to regenerate protocol.json files after making changes to the protocol yaml files.
❌ Don't make changes to protocol.json files directly. Instead, update the relevant proto.yml file in latest/ and regenerate protocol.json by running `npm run build` in tools/js.
If you need to edit many files at once, consider writing a simple Node.js script to replace. E.g., from `cd tools/js && npm i && node __replace_something.js`):
```js
const cp = require('child_process')
const fs = require('fs')
const glob = require('glob')
const pcVersionsOrdered = require('../../data/pc/common/versions.json')
const after1_20_5 = pcVersionsOrdered.slice(pcVersionsOrdered.indexOf('1.20.5')) // everything after 1.20.5...
for (const version of pcVersionsOrdered) {
// globSync, fs.readFileSync...fs.writeFileSync ; avoid async
}
```
When updating, apply minimum required changes when possible.
1. Do not rename fields or packets unless their value has changed, even if they may have 'officially' changed. If their values *have* significantly changed (varint -> i32 maybe insignificant from primitive standpoint, string to number is), it maybe a good idea to rename the field or packet.
2. Do not rename existing enums or other data, even if they were renamed in the game. You may add or remove data as is needed as this does not unnecessarily affect existing code relying on old names.
3. Always read the information in doc/protocol.md to understand our YAML format.
4. Try to inline types as much as possible--this includes mappers and switch statements. Don't create extra types unless they are actually re-used in multiple places, or very large to warrant seperation.
@@ -0,0 +1,113 @@
const fs = require('fs')
const github = require('gh-helpers')()
const { join } = require('path')
const { extractPcEntityMetadata } = require('../../tools/js/extractPcEntityMetadata')
const { exec, createInitialPR } = require('./utils')
const artifactsDir = join(__dirname, './artifacts')
const root = join(__dirname, '..', '..')
async function handle (ourPR, genPullNo, version, artifactURL, shouldPull) {
const branchNameVersion = version.replace(/[^a-zA-Z0-9]/g, '_').toLowerCase()
const branch = ourPR.headBranch || `pc-${branchNameVersion}`
if (shouldPull) exec('git', ['pull', 'origin'])
// if external PR:
// const branch = ourPR.headBranch
// exec('git', ['remote', 'add', 'fo', ourPR.headCloneURL])
// exec('git', ['fetch', 'fo', branch])
// exec('git', ['checkout', '-b', branch, `fo/` + branch])
try {
exec('git', ['checkout', branch])
} catch (err) {
console.error('Error checking out branch:', err)
process.exit(1)
}
const dataPaths = require('../../data/dataPaths.json')
const dataPath = dataPaths.pc[version]
const destDir = join(root, `./data/pc/${version}`)
if (!fs.existsSync(destDir) || !dataPath) {
console.warn(`⚠️ Version ${version} not found (checked ${destDir}) ; cannot continue.`, fs.existsSync(destDir), dataPath)
process.exit(1)
}
if (ourPR.body) {
await github.updateIssue(ourPR.number, {
body: ourPR.body.replace('<!--minecraft-data-generator-placeholder-->', `- https://github.com/PrismarineJS/minecraft-data-generator/pull/${genPullNo}`)
})
}
console.log('Handling PR:', ourPR)
fs.mkdirSync(artifactsDir, { recursive: true })
// https://github.com/PrismarineJS/minecraft-data-generator/actions/runs/17261281146/artifacts/3861320839
const s = artifactURL.split('github.com/')[1]
const [ownerName, repoName, _actions, _runs, _runId, _artifacts, artifactId] = s.split('/') // eslint-disable-line
console.log('Downloading artifacts', { ownerName, repoName, artifactId, artifactsDir })
await github.artifacts.downloadIdFrom(ownerName, repoName, artifactId, artifactsDir)
console.log(fs.readdirSync(artifactsDir))
// Now copy artifacts/${version}/*.json to data/pc/$version/*.json
const versionArtifactsDir = join(artifactsDir, version)
for (const file of fs.readdirSync(versionArtifactsDir)) {
if (file.endsWith('.json')) {
const src = join(versionArtifactsDir, file)
const dest = join(destDir, file)
fs.mkdirSync(destDir, { recursive: true })
console.log(`copy ${src} => ${dest}`)
fs.copyFileSync(src, dest)
dataPath[file.replace('.json', '')] = 'pc/' + version
}
}
// Commit the new dataPath
fs.writeFileSync(join(root, 'data', 'dataPaths.json'), JSON.stringify(dataPaths, null, 2))
try {
process.chdir(join(__dirname, '../../tools/js'))
extractPcEntityMetadata(version, version, { write: true, cloneIfMissing: true })
} catch (e) {
console.log('Failed to extract PC entity metadata', e)
}
// Now, we need to commit the changes
exec('git', ['config', 'user.name', 'github-actions[bot]'])
exec('git', ['config', 'user.email', '41898282+github-actions[bot]@users.noreply.github.com'])
exec('git', ['add', '--all'])
exec('git', ['commit', '-m', `[Auto] Apply generated data from PrismarineJS/minecraft-data-generator#${genPullNo}`])
exec('git', ['push', 'origin', branch])
}
async function main (versions, genPullNo, artifactUrl, createPR) {
const version = versions.at(-1)
const pr = await github.findPullRequest({ titleIncludes: '🎈', author: null })
console.log('Found PR', pr)
if (pr && pr.isOpen) {
const details = await github.getPullRequest(pr.id)
console.log('PR', details)
await handle(details, genPullNo, version, artifactUrl, true)
} else if (createPR) {
const pr = await createInitialPR('pc', '(This issue was created for a minecraft-data-generator PR)', {
version,
protocolVersion: null
})
console.log('Created PR', pr)
const details = await github.getPullRequest(pr.number)
console.log('PR', details)
await handle(details, genPullNo, version, artifactUrl, false)
} else {
process.exit(1)
}
}
main(
JSON.parse(process.env.TRIGGER_MC_VERSIONS),
process.env.TRIGGER_PR_NO,
process.env.TRIGGER_ARTIFACT_URL,
process.env.CREATE_PR_IF_NONE
)
+215
View File
@@ -0,0 +1,215 @@
/* eslint-disable no-return-assign, no-sequences */
const fs = require('fs')
const github = require('gh-helpers')()
const pcManifestURL = 'https://launchermeta.mojang.com/mc/game/version_manifest.json'
const changelogURL = 'https://feedback.minecraft.net/hc/en-us/sections/360001186971-Release-Changelogs'
const { exec, createInitialPR } = require('./utils')
const download = (url, dest) => exec('curl', ['-L', url, '-o', dest])
function buildFirstIssue (title, result, jarData) {
const protocolVersion = jarData?.protocol_version || 'Failed to obtain from JAR'
const name = jarData?.name || result.id
const date = result.releaseTime
return {
title,
body: `
A new Minecraft Java Edition version is available (as of ${date}), version **${result.id}**
## Official Changelog
* ${changelogURL}
## Protocol Details
(I will close this issue automatically if "${result.id}" is added to data/pc/common/versions.json on "master")
<table>
<tr><td><b>Name</b></td><td>${name}</td>
<tr><td><b>Protocol ID</b></td><td>${protocolVersion}</td>
<tr><td><b>Release Date</b></td><td>${date}</td>
<tr><td><b>Release Type</b></td><td>${result.type}</td>
<tr><td><b>Data Version</b></td><td>${jarData?.world_version}</td>
<tr><td><b>Java Version</b></td><td>${jarData?.java_version}</td>
</table>
<hr/>
🤖 I am a bot, I check for updates every 2 hours without a trigger. You can close this issue to prevent any further updates.
<!-- RP: ${JSON.stringify({ title, versionJson: jarData, latestVersionData: result, latestVersion: result.id })} -->
`
}
}
const protocolVersions = {
pc: require('../../data/pc/common/protocolVersions.json'),
bedrock: require('../../data/bedrock/common/protocolVersions.json')
}
const supportedVersions = {
pc: require('../../data/pc/common/versions.json'),
bedrock: require('../../data/bedrock/common/versions.json')
}
async function updateManifestPC () {
const manifest = await fetch(pcManifestURL).then(res => res.json())
// fs.writeFileSync('./manifest.json', JSON.stringify(manifest, null, 2))
const knownVersions = protocolVersions.pc.reduce((acc, cur) => (acc[cur.minecraftVersion] = cur, acc), {})
const latestVersion = manifest.latest.snapshot
const latestReleaseVersion = manifest.latest.release
const latestVersionData = manifest.versions.find(v => v.id === latestVersion)
const latestVersionIsSnapshot = latestVersionData.type !== 'release'
const title = `Support Minecraft PC ${latestReleaseVersion}`
const issueStatus = await github.findIssue({ titleIncludes: title, author: null }) || {}
console.log('issueStatus', issueStatus)
if (issueStatus?.isOpen) {
if (supportedVersions.pc.includes(latestReleaseVersion)) {
// If the issue is open and the latest release version is supported, we can close the issue
github.close(issueStatus.id, `Closing as PC ${latestReleaseVersion} is now supported`)
return
}
}
if (latestVersionIsSnapshot) {
// don't make issues for snapshots
if (supportedVersions.pc.includes(latestVersion) || knownVersions[latestVersion]) {
console.log('Latest version is a known snapshot, no work to do')
return
}
} else if (knownVersions[latestVersion]) {
console.log(`Latest PC version ${latestVersion} is known in protocolVersions.json, but not in versions.json (protocol version ${knownVersions[latestVersion].version})`)
return // We expect to have already handled this version.
} else {
console.log(`Latest PC version ${latestVersion} is not known in protocolVersions.json, adding and making issue`)
}
let versionJson
try {
versionJson = await addEntryFor(latestVersion, latestVersionData)
} catch (e) {
console.error(e)
if (latestVersionIsSnapshot) {
console.warn('Failed to update protocolVersions.json for the snapshot', latestVersion)
} else {
console.log('Latest PC version is not supported and we failed to load data. Opening issue...')
const issuePayload = buildFirstIssue(title, latestVersionData)
github.createIssue(issuePayload)
fs.writeFileSync('./issue.md', issuePayload.body)
console.log('OK, wrote to ./issue.md', issuePayload)
}
}
if (!latestVersionIsSnapshot && !issueStatus.isOpen && !issueStatus.isClosed) {
await openIssueAndDispatch(title, versionJson, latestVersionData, latestVersion)
}
async function addEntryFor (releaseVersion, releaseData) {
const latestReleaseManifest = await fetch(releaseData.url).then(res => res.json())
// Download client jar
if (!fs.existsSync(`./${releaseVersion}.jar`)) {
const clientJarUrl = latestReleaseManifest.downloads.client.url
console.log('Downloading client jar', clientJarUrl)
download(clientJarUrl, `./${releaseVersion}.jar`)
}
// Log the byte size of the client jar
const clientJarSize = fs.statSync(`./${releaseVersion}.jar`).size
console.log(`Downloaded client jar ${releaseVersion}.jar (${clientJarSize} bytes), extracting its version.json...`)
// unzip with tar / unzip, Actions image uses 7z
if (process.platform === 'win32') exec('tar', ['-xf', `./${releaseVersion}.jar`, 'version.json'])
else exec('7z', ['-y', 'e', `./${releaseVersion}.jar`, 'version.json'])
const versionJson = require('./version.json')
let majorVersion
if (versionJson.id && versionJson.id.includes('.')) {
majorVersion = versionJson.id.split('.', 2).join('.')
} else {
console.error(`Version '${versionJson.id}' appears to be a snapshot (no dot in name). Using the last known majorVersion in protocolVersions.json for this version (may be incorrect)`)
majorVersion = protocolVersions.pc[0].majorVersion
}
const newEntry = {
minecraftVersion: versionJson.id,
version: versionJson.protocol_version,
dataVersion: versionJson.world_version,
usesNetty: true,
majorVersion,
releaseType: latestVersionData.type
}
console.log('Adding new entry to pc protocolVersions.json', newEntry)
const updatedProtocolVersions = [newEntry, ...protocolVersions.pc]
fs.writeFileSync('../../data/pc/common/protocolVersions.json', JSON.stringify(updatedProtocolVersions, null, 2))
if (process.env.CI) {
console.log('Committing changes to protocolVersions.json')
// https://github.com/actions/checkout/pull/1184
exec('git', ['config', 'user.name', 'github-actions[bot]'])
exec('git', ['config', 'user.email', '41898282+github-actions[bot]@users.noreply.github.com'])
exec('git', ['add', '../../data/pc/common/protocolVersions.json'])
exec('git', ['commit', '-m', `Add ${versionJson.id} to pc protocolVersions.json`])
exec('git', ['push'])
}
return versionJson
}
}
async function openIssueAndDispatch (title, versionJson, latestVersionData, latestVersion) {
console.log('Opening issue', versionJson)
const issuePayload = buildFirstIssue(title, latestVersionData, versionJson)
const issue = await github.createIssue(issuePayload)
console.log('Created issue', issue)
// Now create an initial PR with the new version data
const pr = await createInitialPR('pc', issue.url, {
minecraftVersion: versionJson.id,
version: latestVersion,
protocolVersion: versionJson.protocol_version
})
console.log('Created PR', pr)
// Ask minecraft-data-generator to handle new update
const dispatchPayload = {
owner: 'PrismarineJS',
repo: 'minecraft-data-generator',
workflow: 'handle-mcdata-update.yml',
branch: 'main',
inputs: {
version: latestVersion,
issue_number: String(issue?.number),
pr_number: String(pr?.number)
}
}
console.log('Sending workflow dispatch', dispatchPayload)
await github.sendWorkflowDispatch(dispatchPayload)
// Ask node-minecraft-protocol to handle new update
const nodeDispatchPayload = {
owner: 'PrismarineJS',
repo: 'node-minecraft-protocol',
workflow: 'update-from-minecraft-data.yml',
branch: 'master',
inputs: {
new_mc_version: latestVersion,
mcdata_branch: pr.branchName,
mcdata_pr_url: pr.url
}
}
console.log('Sending workflow dispatch', nodeDispatchPayload)
await github.sendWorkflowDispatch(nodeDispatchPayload)
// node-minecraft-protocol would then dispatch to mineflayer
return { issue, pr }
}
if (process.env.FORCE_CREATE_PR) {
const payload = JSON.parse(process.env.FORCE_CREATE_PR)
openIssueAndDispatch(payload.title, payload.versionJson, payload.latestVersionData, payload.latestVersion).then(() => {
console.log('Done with manual PR')
}).catch(err => {
console.error('Failed to do manual PR', err)
process.exit(1)
})
} else {
updateManifestPC()
}
module.exports = { openIssueAndDispatch, updateManifestPC }
+11
View File
@@ -0,0 +1,11 @@
{
"scripts": {
"fix": "standard --fix"
},
"dependencies": {
"gh-helpers": "^1.0.0"
},
"devDependencies": {
"standard": "^17.1.2"
}
}
+50
View File
@@ -0,0 +1,50 @@
const cp = require('child_process')
const github = require('gh-helpers')()
const path = require('path')
const pm = process.versions.bun ? process.execPath : 'npm'
function exec (file, args = [], options = {}) {
const opts = { stdio: ['inherit', 'inherit', 'inherit'], ...options }
console.log('> ', file, args.join(' '), options.cwd ? `(cwd: ${options.cwd})` : '')
return github.mock ? undefined : cp.execFileSync(file, args, opts)
}
const toolsJs = path.join(__dirname, '..', '..', 'tools', 'js')
const sanitizeBranch = (branchName) => branchName.replace(/[^a-zA-Z0-9]/g, '_').toLowerCase()
async function createInitialPR (edition, issueUrl, { version, protocolVersion }) {
exec(pm, ['install'], { cwd: toolsJs })
if (protocolVersion) exec(pm, ['run', 'version', edition, version, protocolVersion], { cwd: toolsJs })
exec(pm, ['run', 'build'], { cwd: toolsJs })
const branchNameVersion = sanitizeBranch(version)
const branchName = `${edition}_${branchNameVersion}`
const title = `🎈 Add Minecraft ${edition} ${version} data`
// First, delete any existing branch
try {
exec('git', ['branch', '-D', branchName])
} catch (e) {
// Branch doesn't exist, ignore error
}
exec('git', ['checkout', '-b', branchName])
exec('git', ['config', 'user.name', 'github-actions[bot]'])
exec('git', ['config', 'user.email', '41898282+github-actions[bot]@users.noreply.github.com'])
exec('git', ['add', '--all'])
exec('git', ['commit', '--allow-empty', '-m', title])
exec('git', ['push', 'origin', branchName, '--force'])
const body = `
This automated PR sets up the relevant boilerplate for Minecraft ${edition} version ${version}. Fixes ${issueUrl}.
Related:
- Issue: ${issueUrl}
- Protocol Version: ${protocolVersion}
<!--minecraft-data-generator-placeholder-->
* You can help contribute to this PR by opening a PR against this <code branch>${branchName}</code> branch instead of <code>master</code>.
`
const pr = await github.createPullRequest(title, body, branchName, 'master')
pr.branchName = branchName
return pr
}
module.exports = { createInitialPR, exec }
+59
View File
@@ -0,0 +1,59 @@
name: bedrock-ci
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
build:
runs-on: ubuntu-latest
timeout-minutes: 14
steps:
- name: Use Node.js 22.x
uses: actions/setup-node@v1
with:
node-version: 22.x
# Old versions of bedrock use old libssl that Ubuntu no longer ships with; need manual install
- name: Install libssl 1.1
run: |
wget http://archive.ubuntu.com/ubuntu/pool/main/o/openssl/libssl1.1_1.1.1f-1ubuntu2_amd64.deb
sudo dpkg -i libssl1.1_1.1.1f-1ubuntu2_amd64.deb
- name: Checkout node-minecraft-data
uses: actions/checkout@v2
with:
repository: PrismarineJS/node-minecraft-data
submodules: recursive
path: node-mcdata
# Erase the submodule for minecraft-data with a checkout of the current minecraft-data repo
- run: rm -rf node-mcdata/minecraft-data
- name: Checkout minecraft-data
uses: actions/checkout@v2
with:
path: node-mcdata/minecraft-data
# Now install local node-minecraft-data. Running "prepare" should gen data and types
- name: Install minecraft-data
run: |
cd node-mcdata
npm install
npm run prepare
npm link
- name: Checkout bedrock-protocol
uses: actions/checkout@v2
with:
repository: PrismarineJS/bedrock-protocol
path: bedrock-protocol
# I forget the correct install order, do both
- name: Install bedrock-protocol
run: |
cd bedrock-protocol
npm install ../node-mcdata
npm i
npm install ../node-mcdata
- name: Running bedrock-protocol tests
run: |
curl -o node_modules/protodef/src/serializer.js https://raw.githubusercontent.com/extremeheat/node-protodef/refs/heads/dlog/src/serializer.js && curl -o node_modules/protodef/src/compiler.js https://raw.githubusercontent.com/extremeheat/node-protodef/refs/heads/dlog/src/compiler.js
npm test
working-directory: bedrock-protocol
@@ -0,0 +1,92 @@
name: Bedrock Version Bump
on:
workflow_dispatch:
inputs:
minecraftVersion:
description: 'Bedrock Minecraft version (e.g., 1.21.124)'
required: true
type: string
protocolVersion:
description: 'Protocol version number (e.g., 860)'
required: true
type: string
createPr:
description: 'Create a pull request with the changes'
required: false
type: boolean
default: true
jobs:
bump-bedrock-version:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
token: ${{ secrets.PAT_PASSWORD || secrets.GITHUB_TOKEN }}
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 22
- name: Install dependencies
run: |
cd tools/js
npm install
- name: Run version bump script
run: |
cd tools/js
npm run version bedrock ${{ github.event.inputs.minecraftVersion }} ${{ github.event.inputs.protocolVersion }}
- name: Build protocol files
run: |
cd tools/js
npm run build
- name: Run tests
run: |
cd tools/js
npm test
- name: Create Pull Request
if: ${{ github.event.inputs.createPr == 'true' }}
uses: peter-evans/create-pull-request@v6
with:
token: ${{ secrets.PAT_PASSWORD || secrets.GITHUB_TOKEN }}
commit-message: |
Add bedrock ${{ github.event.inputs.minecraftVersion }} protocol data
Updates:
- Protocol version → ${{ github.event.inputs.protocolVersion }}
- Minecraft version → ${{ github.event.inputs.minecraftVersion }}
- Moved previous proto.yml to versioned folder
- Updated protocolVersions.json, versions.json, and dataPaths.json
- Regenerated protocol.json files
branch: bedrock-bump-${{ github.event.inputs.minecraftVersion }}
delete-branch: true
title: Add bedrock ${{ github.event.inputs.minecraftVersion }} data
body: |
## Bedrock Version Bump
This PR adds support for Minecraft Bedrock Edition version **${{ github.event.inputs.minecraftVersion }}** with protocol version **${{ github.event.inputs.protocolVersion }}**.
### Changes
- ✅ Protocol version bumped to ${{ github.event.inputs.protocolVersion }}
- ✅ Minecraft version updated to ${{ github.event.inputs.minecraftVersion }}
- ✅ Previous proto.yml files moved to versioned folder
- ✅ Updated protocolVersions.json and versions.json
- ✅ Regenerated all protocol.json files
- ✅ All tests passing
### How this was generated
This PR was automatically generated using the bedrock version bump workflow.
---
🤖 Generated by GitHub Actions workflow
labels: |
bedrock
version-bump
automated
@@ -0,0 +1,22 @@
name: Repository Dispatch
on:
repository_dispatch:
types: [node-mcData-release]
jobs:
myEvent:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
ref: web
- name: Use Node.js 22.x
uses: actions/setup-node@v1
with:
node-version: 22.x
- name: Run tool
run: |
npm install
git config user.name 'rom1504bot'
git config user.email 'rom1504bot@users.noreply.github.com'
npm run gh-publish
+27
View File
@@ -0,0 +1,27 @@
name: CI
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [22.x]
steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- run: npm install
working-directory: tools/js
- run: npm test
working-directory: tools/js
+22
View File
@@ -0,0 +1,22 @@
name: Repo Commands
on:
issue_comment: # Handle comment commands
types: [created]
pull_request: # Handle renamed PRs
types: [edited]
jobs:
comment-trigger:
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v3
- name: Run command handlers
uses: PrismarineJS/prismarine-repo-actions@master
with:
# NOTE: You must specify a Personal Access Token (PAT) with repo access here. While you can use the default GITHUB_TOKEN, actions taken with it will not trigger other actions, so if you have a CI workflow, commits created by this action will not trigger it.
token: ${{ secrets.PAT_PASSWORD }}
# See `Options` section below for more info on these options
install-command: cd tools/js && npm install
/fixlint.fix-command: cd tools/js && npm run fix
@@ -0,0 +1,39 @@
name: handle-mcpc-generator
on:
workflow_dispatch:
inputs:
versions:
description: 'JSON stringified list of known versions in generator (last is expected to be latest)'
required: true
default: '["1.21.6"]'
mergedArtifactURL:
description: 'Merged artifact download URL'
required: true
prNumber:
description: 'Relevant minecraft-data-generator PR #'
required: false
createPr:
description: 'Whether to create a new PR if none exists'
required: false
default: 'false'
jobs:
update-mcdata:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: true
- uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- run: cd .github/helper-bot && bun install
- run: cd tools/js && bun install
- run: bun handleMcpcGeneratedArtifacts.js
working-directory: .github/helper-bot
env:
TRIGGER_MC_VERSIONS: ${{ github.event.inputs.versions }}
TRIGGER_PR_NO: ${{ github.event.inputs.prNumber }}
TRIGGER_ARTIFACT_URL: ${{ github.event.inputs.mergedArtifactURL }}
GITHUB_TOKEN: ${{ secrets.PAT_PASSWORD }}
CREATE_PR_IF_NONE: ${{ github.event.inputs.createPr }}
+34
View File
@@ -0,0 +1,34 @@
name: 'tag'
on:
push:
branches:
- master
jobs:
tag:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions-ecosystem/action-regex-match@v2
id: regex-match
with:
text: ${{ github.event.head_commit.message }}
regex: '^Release ([0-9]+\.[0-9]+\.[0-9]+)'
- name: Create Release
if: steps.regex-match.outputs.match != ''
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ steps.regex-match.outputs.group1 }}
release_name: Release ${{ steps.regex-match.outputs.group1 }}
body: ${{ steps.regex-match.outputs.group1 }}
draft: false
prerelease: false
- uses: peter-evans/repository-dispatch@v1
if: ${{ steps.regex-match.outputs.match != '' }}
with:
token: ${{ secrets.PAT_PASSWORD }}
repository: PrismarineJS/node-minecraft-data
event-type: mcData-release
client-payload: '{"tag": "${{ steps.regex-match.outputs.group1 }}"}'
+32
View File
@@ -0,0 +1,32 @@
name: Update Helper
on:
workflow_dispatch:
inputs:
createPr:
description: 'If provided, force create an update PR. JSON encoded payload {title, versionJson, latestVersionData, latestVersion}'
required: false
default: ''
schedule:
- cron: "0 */2 * * *"
jobs:
helper:
name: update-checker
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@master
- name: Set up Node.js
uses: actions/setup-node@master
with:
node-version: 22.0.0
- name: Install Github Actions toolkit
run: npm i
working-directory: .github/helper-bot
# The env vars contain the relevant trigger information, so we don't need to pass it
- name: Runs helper
run: node index.js
working-directory: .github/helper-bot
env:
GITHUB_TOKEN: ${{ secrets.PAT_PASSWORD }}
FORCE_CREATE_PR: ${{ github.event.inputs.createPr }}
+154
View File
@@ -0,0 +1,154 @@
# minecraft-data
[![Discord](https://img.shields.io/badge/chat-on%20discord-brightgreen.svg)](https://discord.gg/GsEFRM8)
[![Gitter](https://img.shields.io/badge/chat-on%20gitter-brightgreen.svg)](https://gitter.im/PrismarineJS/general)
[![Irc](https://img.shields.io/badge/chat-on%20irc-brightgreen.svg)](https://irc.gitter.im/)
[![Build Status](https://github.com/PrismarineJS/minecraft-data/workflows/CI/badge.svg)](https://github.com/PrismarineJS/minecraft-data/actions?query=workflow%3A%22CI%22)
Language independent module providing minecraft data for minecraft clients, servers and libraries.
Supports
* Minecraft PC version 0.30c (classic), 1.7.10, 1.8.8, 1.9 (15w40b, 1.9, 1.9.1-pre2, 1.9.2, 1.9.4),
1.10 (16w20a, 1.10-pre1, 1.10, 1.10.1, 1.10.2), 1.11 (16w35a, 1.11, 1.11.2), 1.12 (17w15a, 17w18b, 1.12-pre4, 1.12, 1.12.1, 1.12.2), 1.13 (17w50a, 1.13, 1.13.1, 1.13.2-pre1, 1.13.2-pre2, 1.13.2), 1.14 (1.14, 1.14.1, 1.14.3, 1.14.4), 1.15 (1.15, 1.15.1, 1.15.2), 1.16 (20w13b, 20w14a, 1.16-rc1, 1.16, 1.16.1, 1.16.2, 1.16.3, 1.16.4, 1.16.5), 1.17, 1.17.1, 1.18 (1.18, 1.18.1, 1.18.2), 1.19 (1.19, 1.19.2, 1.19.3, 1.19.4), 1.20 (1.20, 1.20.1, 1.20.2, 1.20.3, 1.20.4, 1.20.5, 1.20.6), 1.21 (1.21, 1.21.1, 1.21.3), 1.21.8, 1.21.9, 1.21.10
<!--NEXT PC-->
* Minecraft bedrock version 0.14, 0.15, 1.0, 1.16.201, 1.16.210, 1.16.220, 1.17.0, 1.17.10, 1.17.30, 1.17.40, 1.18.0, 1.18.11, 1.18.30, 1.19.1, 1.19.10, 1.19.20, 1.19.21, 1.19.30, 1.19.40, 1.19.50, 1.19.60, 1.19.62, 1.19.63, 1.19.70, 1.19.80, 1.20.0, 1.20.10, 1.20.30, 1.20.40, 1.20.50, 1.20.61, 1.20.71, 1.20.80, 1.21.0, 1.21.2, 1.21.20, 1.21.30, 1.21.42, 1.21.50, 1.21.60, 1.21.70, 1.21.80, 1.21.90, 1.21.93, 1.21.100, 1.21.111, 1.21.120, 1.21.124, 1.21.130, 1.26.0
<!--NEXT BEDROCK-->
## Wrappers
Minecraft-data is language independent, you can use it with these language specific modules :
| Wrapper name | Language | Data provided |
| --- | --- | --- |
| [node-minecraft-data](https://github.com/PrismarineJS/node-minecraft-data) | Node.js | everything |
| [python-minecraft-data](https://github.com/SpockBotMC/python-minecraft-data) | python | everything |
| [minebase](https://github.com/py-mine/minebase) | python | everything |
| [McData](https://github.com/McEx/McData) | Elixir | protocol |
| [ProtocolGen](https://github.com/Johni0702/ProtocolGen) | java | generated java files from protocol.json to read and write minecraft packets |
| [mcdata](https://github.com/wlwanpan/mcdata) | Go | everything |
| [minecraft-data-rs](https://github.com/Trivernis/minecraft-data-rs) | Rust | everything |
If you want to use minecraft-data in a new language, we advise you to [create a new wrapper](doc/make-a-new-wrapper.md)
## Data
Data provided:
| Data | Description |
| --- | --- |
| Biomes | |
| Blocks | |
| Enchantment | |
| Effect | Status/Potion effects |
| Entities | |
| Language | language code and their english strings |
| Instruments | sounds emitted by blocks |
| Items | |
| Map icons | Provides all available map icons as used by (crafted) maps |
| Materials | to be used with blocks data. Provides tool speed multipliers against material types. |
| Particles | Id and names of minecraft particles |
| Protocol | a complete description of the protocol, can be used to automatically implement the protocol, see [protodef doc](https://github.com/ProtoDef-io/ProtoDef) |
| Protocol Versions | the whole list of minecraft protocol versions |
| Recipes | to be used with blocks and items data |
| Windows | the different kind of windows and their characteristics |
| Version | |
| Versions | all the versions supported in minecraft-data |
| Block collision shapes | |
| Foods | list of foods each with there id, saturation, foodpoints and more |
| Commands | a tree structure for vanilla minecraft server commands, and some info needed to implement sub-parsers.
| Legacy | mappings between legacy (1.12) and post-flattening (1.13+) blocks and items ids
| Skin data | (bedrock edition) Skin geometry and texture data for steve skin
| Features | This can be used to check is a specific feature is available in the current Minecraft version. This is usually only required for handling version-specific functionality.
| Sounds | For versions that use a sound id over the network (1.9+) this provides mappings between network sound id and sound name.
See more information about this data in the [documentation](http://prismarinejs.github.io/minecraft-data/)
## Documentation
* See [doc/history.md](doc/history.md)
* [Documentation generated using the json schemas and docson](http://prismarinejs.github.io/minecraft-data)
* [Textual documentation of the recipe format](doc/recipes.md)
## Projects using minecraft-data
These are the projects that use minecraft-data:
| Project name | Language | Project type | Data used |
| --- | --- | --- | --- |
| [mineflayer](https://github.com/PrismarineJS/mineflayer) | Node.js | bot library | biomes, blocks, entities, instruments, items, materials, recipes |
| [node-minecraft-protocol](https://github.com/PrismarineJS/node-minecraft-protocol) | Node.js | protocol serialization | protocol |
| [flying-squid](https://github.com/PrismarineJS/flying-squid) | Node.js | server library | biomes, blocks, materials |
| [SpockBot](https://github.com/SpockBotMC/SpockBot) | Python | bot library | biomes, blocks, items, materials, windows, recipes |
| [PhaseBot](https://github.com/phase/PhaseBot) | Java | bot | blocks, items, materials |
| [McEx](https://github.com/hansihe/McEx) | Elixir | server | blocks |
| [VoxelGamesLib](https://github.com/MiniDigger/VoxelGamesLib) | Java | minigames server library | blocks, items |
| [Phpcraft](https://github.com/Phpcraft/core) | PHP | all-around library | blocks, items, entities, protocol |
| [MineSharp](https://github.com/psu-de/MineSharp) | C# | Bots | Protocol, Blocks, Items, Entities, Biomes, Recipes, Enchantments |
## Extraction
### Java Edition
The recommended way to extract the data is documented at [add-data-new-version.md](https://github.com/PrismarineJS/minecraft-data/blob/master/doc/add-data-new-version.md).
The [minecraft-data-auto-updater](https://github.com/PrismarineJS/minecraft-data-auto-updater) calls [minecraft-data-generator-server](https://github.com/PrismarineJS/minecraft-data-generator-server).
### Bedrock Edition
See [bedrock.md](./doc/bedrock.md) for information on updating data.
Specifically, [minecraft-data-auto-updater](https://github.com/PrismarineJS/minecraft-data-auto-updater) calls [minecraft-data-generator-server](https://github.com/PrismarineJS/minecraft-data-generator-server)
Projects that provide data:
| Project name | Language | Source | Data provided |
| --- | --- | --- | --- |
| [minecraft-wiki-extractor](https://github.com/PrismarineJS/minecraft-wiki-extractor) | Node.js | [minecraft wiki](https://minecraft.wiki) | blocks, items, entities, recipes |
| [mcdevs-wiki-extractor](https://github.com/PrismarineJS/mcdevs-wiki-extractor) | Node.js | [wiki.vg](http://wiki.vg/Protocol) | a partial protocol, entities |
| [node-minecraft-extractor](https://github.com/PrismarineJS/node-minecraft-extractor) | Node.js | merge between wiki.vg and mcwiki | a complete entities file |
| [minecraft-jar-extractor](https://github.com/PrismarineJS/minecraft-jar-extractor) | Node.js | minecraft server jar | a very partial but completely up to date protocol |
| [minecraft-jar-extractor in python](https://github.com/pangeacake/minecraft-jar-extractor) | Python | minecraft server jar | about the same protocol information as minecraft-jar-extractor in node.js |
| [burger](https://github.com/mcdevs/Burger) with [burger-extractor](https://github.com/PrismarineJS/burger-extractor) | Node.js | minecraft server jar | used to provide items, blocks, biomes and recipes |
| [burger 1.16](https://github.com/Pokechu22/Burger/tree/1.16-dev) | --//-- | --// -- | --//-- |
| [command-generator](https://github.com/Miro-Andrin/mc-data-command-generator) | Python | [Minecraft data generator](https://wiki.vg/Data_Generators) | Data used for parsing all vanilla server commands. |
| [minecraft-data-generator](https://github.com/Archengius/minecraft-data-generator) | Java | minecraft client | biomes, block collision shapes, blocks, effects, enchantments, entities, foods, items, particles, tints. Required Fabric updated to the provided minecraft version. |
Pages interesting to manually update the data if necessary:
| Page | Data |
| ---- | ---- |
| [wiki.vg Inventory page](http://wiki.vg/Inventory) | windows |
| [wiki.vg](http://wiki.vg/Block_Actions) | instruments |
| [a mineflayer PR](https://github.com/PrismarineJS/mineflayer/pull/197) | biomes |
| [mcwiki effects page](https://minecraft.wiki/w/Effect) | effects |
| [wiki.vg protocol version numbers](http://wiki.vg/Protocol_version_numbers) | protocolVersions |
## Data quality
Minecraft data provides scripts to audit the data, they can be useful to check the data is correct :
* [audit_blocks](tools/js/test/audit_blocks.js)
* [audit_items](tools/js/test/audit_items.js)
* [audit_recipes](tools/js/test/audit_recipes.js)
* [audit_shapes](tools/js/test/audit_shapes.js)
Minecraft data also provides json schemas in enums_schemas/ that are used in
test/test.js to check the json file are valid relative to these schemas.
These schemas can also be used to understand better how the json files are
formatted in order to use it.
## Contribute
Please read https://github.com/PrismarineJS/prismarine-contribute
## License
MIT
Some of the data was extracted manually or automatically from wiki.vg and minecraft.gamepedia.com.
If required by one of the sources the license might change to something more appropriate.
## Related project
[minecraft-assets](https://github.com/rom1504/minecraft-assets) which provide minecraft assets along with json files that help to use them.
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,5 @@
{
"version":70,
"minecraftVersion":"0.14.3",
"majorVersion":"0.14"
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,5 @@
{
"version":82,
"minecraftVersion":"0.15.6",
"majorVersion":"0.15"
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,5 @@
{
"version":100,
"minecraftVersion":"1.0.0",
"majorVersion":"1.0"
}
@@ -0,0 +1,107 @@
[
{
"name": "followRange",
"resource": "generic.follow_range",
"default": 32,
"min": 0,
"max": 2048
},
{
"name": "knockbackResistance",
"resource": "generic.knockback_resistance",
"default": 0,
"min": 0,
"max": 1
},
{
"name": "movementSpeed",
"resource": "generic.movement_speed",
"default": 0.7,
"min": 0,
"max": 1024
},
{
"name": "attackDamage",
"resource": "generic.attack_damage",
"default": 2,
"min": 0,
"max": 2048
},
{
"name": "horseJumpStrength",
"resource": "horse.jump_strength",
"default": 0.7,
"min": 0,
"max": 2
},
{
"name": "zombieSpawnReinforcements",
"resource": "zombie.spawn_reinforcements",
"default": 0,
"min": 0,
"max": 1
},
{
"name": "flyingSpeed",
"resource": "generic.flying_speed",
"default": 0.4,
"min": 0,
"max": 1024
},
{
"name": "luck",
"resource": "generic.luck",
"default": 0,
"min": -1024,
"max": 1024
},
{
"name": "health",
"resource": "health",
"default": 20,
"min": 0,
"max": 20
},
{
"name": "absorption",
"resource": "absorption",
"default": 0,
"min": 0,
"max": 1024
},
{
"name": "exhaustion",
"resource": "player.exhaustion",
"default": 0,
"min": 0,
"max": 5
},
{
"name": "experienceProgress",
"resource": "player.experience",
"default": 0,
"min": 0,
"max": 1
},
{
"name": "experienceLevel",
"resource": "player.level",
"default": 0,
"min": 0,
"max": 24791
},
{
"name": "hunger",
"resource": "player.hunger",
"default": 20,
"min": 0,
"max": 20
},
{
"name": "saturation",
"resource": "player.saturation",
"default": 20,
"min": 0,
"max": 20
}
]
@@ -0,0 +1,833 @@
[
{
"id": 0,
"name": "protection",
"displayName": "Protection",
"maxLevel": 4,
"minCost": {
"a": 11,
"b": -10
},
"maxCost": {
"a": 11,
"b": 1
},
"treasureOnly": false,
"curse": false,
"exclude": [
"fire_protection",
"blast_protection",
"projectile_protection"
],
"category": "armor",
"weight": 10,
"tradeable": true,
"discoverable": true
},
{
"id": 1,
"name": "fire_protection",
"displayName": "Fire Protection",
"maxLevel": 4,
"minCost": {
"a": 8,
"b": 2
},
"maxCost": {
"a": 8,
"b": 10
},
"treasureOnly": false,
"curse": false,
"exclude": [
"protection",
"blast_protection",
"projectile_protection"
],
"category": "armor",
"weight": 5,
"tradeable": true,
"discoverable": true
},
{
"id": 2,
"name": "feather_falling",
"displayName": "Feather Falling",
"maxLevel": 4,
"minCost": {
"a": 6,
"b": -1
},
"maxCost": {
"a": 6,
"b": 5
},
"treasureOnly": false,
"curse": false,
"exclude": [],
"category": "armor_feet",
"weight": 5,
"tradeable": true,
"discoverable": true
},
{
"id": 3,
"name": "blast_protection",
"displayName": "Blast Protection",
"maxLevel": 4,
"minCost": {
"a": 8,
"b": -3
},
"maxCost": {
"a": 8,
"b": 5
},
"treasureOnly": false,
"curse": false,
"exclude": [
"protection",
"fire_protection",
"projectile_protection"
],
"category": "armor",
"weight": 2,
"tradeable": true,
"discoverable": true
},
{
"id": 4,
"name": "projectile_protection",
"displayName": "Projectile Protection",
"maxLevel": 4,
"minCost": {
"a": 6,
"b": -3
},
"maxCost": {
"a": 6,
"b": 3
},
"treasureOnly": false,
"curse": false,
"exclude": [
"protection",
"fire_protection",
"blast_protection"
],
"category": "armor",
"weight": 5,
"tradeable": true,
"discoverable": true
},
{
"id": 5,
"name": "thorns",
"displayName": "Thorns",
"maxLevel": 3,
"minCost": {
"a": 20,
"b": -10
},
"maxCost": {
"a": 10,
"b": 51
},
"treasureOnly": false,
"curse": false,
"exclude": [],
"category": "armor_chest",
"weight": 1,
"tradeable": true,
"discoverable": true
},
{
"id": 6,
"name": "respiration",
"displayName": "Respiration",
"maxLevel": 3,
"minCost": {
"a": 10,
"b": 0
},
"maxCost": {
"a": 10,
"b": 30
},
"treasureOnly": false,
"curse": false,
"exclude": [],
"category": "armor_head",
"weight": 2,
"tradeable": true,
"discoverable": true
},
{
"id": 7,
"name": "depth_strider",
"displayName": "Depth Strider",
"maxLevel": 3,
"minCost": {
"a": 10,
"b": 0
},
"maxCost": {
"a": 10,
"b": 15
},
"treasureOnly": false,
"curse": false,
"exclude": [
"frost_walker"
],
"category": "armor_feet",
"weight": 2,
"tradeable": true,
"discoverable": true
},
{
"id": 8,
"name": "aqua_affinity",
"displayName": "Aqua Affinity",
"maxLevel": 1,
"minCost": {
"a": 0,
"b": 1
},
"maxCost": {
"a": 0,
"b": 41
},
"treasureOnly": false,
"curse": false,
"exclude": [],
"category": "armor_head",
"weight": 2,
"tradeable": true,
"discoverable": true
},
{
"id": 9,
"name": "sharpness",
"displayName": "Sharpness",
"maxLevel": 5,
"minCost": {
"a": 11,
"b": -10
},
"maxCost": {
"a": 11,
"b": 10
},
"treasureOnly": false,
"curse": false,
"exclude": [
"smite",
"bane_of_arthropods"
],
"category": "weapon",
"weight": 10,
"tradeable": true,
"discoverable": true
},
{
"id": 10,
"name": "smite",
"displayName": "Smite",
"maxLevel": 5,
"minCost": {
"a": 8,
"b": -3
},
"maxCost": {
"a": 8,
"b": 17
},
"treasureOnly": false,
"curse": false,
"exclude": [
"sharpness",
"bane_of_arthropods"
],
"category": "weapon",
"weight": 5,
"tradeable": true,
"discoverable": true
},
{
"id": 11,
"name": "bane_of_arthropods",
"displayName": "Bane of Arthropods",
"maxLevel": 5,
"minCost": {
"a": 8,
"b": -3
},
"maxCost": {
"a": 8,
"b": 17
},
"treasureOnly": false,
"curse": false,
"exclude": [
"sharpness",
"smite"
],
"category": "weapon",
"weight": 5,
"tradeable": true,
"discoverable": true
},
{
"id": 12,
"name": "knockback",
"displayName": "Knockback",
"maxLevel": 2,
"minCost": {
"a": 20,
"b": -15
},
"maxCost": {
"a": 10,
"b": 51
},
"treasureOnly": false,
"curse": false,
"exclude": [],
"category": "weapon",
"weight": 5,
"tradeable": true,
"discoverable": true
},
{
"id": 13,
"name": "fire_aspect",
"displayName": "Fire Aspect",
"maxLevel": 2,
"minCost": {
"a": 20,
"b": -10
},
"maxCost": {
"a": 10,
"b": 51
},
"treasureOnly": false,
"curse": false,
"exclude": [],
"category": "weapon",
"weight": 2,
"tradeable": true,
"discoverable": true
},
{
"id": 14,
"name": "looting",
"displayName": "Looting",
"maxLevel": 3,
"minCost": {
"a": 9,
"b": 6
},
"maxCost": {
"a": 10,
"b": 51
},
"treasureOnly": false,
"curse": false,
"exclude": [
"silk_touch"
],
"category": "weapon",
"weight": 2,
"tradeable": true,
"discoverable": true
},
{
"id": 15,
"name": "efficiency",
"displayName": "Efficiency",
"maxLevel": 5,
"minCost": {
"a": 10,
"b": -9
},
"maxCost": {
"a": 10,
"b": 51
},
"treasureOnly": false,
"curse": false,
"exclude": [],
"category": "digger",
"weight": 10,
"tradeable": true,
"discoverable": true
},
{
"id": 16,
"name": "silk_touch",
"displayName": "Silk Touch",
"maxLevel": 1,
"minCost": {
"a": 0,
"b": 15
},
"maxCost": {
"a": 10,
"b": 51
},
"treasureOnly": false,
"curse": false,
"exclude": [
"looting",
"fortune",
"luck_of_the_sea"
],
"category": "digger",
"weight": 1,
"tradeable": true,
"discoverable": true
},
{
"id": 17,
"name": "unbreaking",
"displayName": "Unbreaking",
"maxLevel": 3,
"minCost": {
"a": 8,
"b": -3
},
"maxCost": {
"a": 10,
"b": 51
},
"treasureOnly": false,
"curse": false,
"exclude": [],
"category": "breakable",
"weight": 5,
"tradeable": true,
"discoverable": true
},
{
"id": 18,
"name": "fortune",
"displayName": "Fortune",
"maxLevel": 3,
"minCost": {
"a": 9,
"b": 6
},
"maxCost": {
"a": 10,
"b": 51
},
"treasureOnly": false,
"curse": false,
"exclude": [
"silk_touch"
],
"category": "digger",
"weight": 2,
"tradeable": true,
"discoverable": true
},
{
"id": 19,
"name": "power",
"displayName": "Power",
"maxLevel": 5,
"minCost": {
"a": 10,
"b": -9
},
"maxCost": {
"a": 10,
"b": 6
},
"treasureOnly": false,
"curse": false,
"exclude": [],
"category": "bow",
"weight": 10,
"tradeable": true,
"discoverable": true
},
{
"id": 20,
"name": "punch",
"displayName": "Punch",
"maxLevel": 2,
"minCost": {
"a": 20,
"b": -8
},
"maxCost": {
"a": 20,
"b": 17
},
"treasureOnly": false,
"curse": false,
"exclude": [],
"category": "bow",
"weight": 2,
"tradeable": true,
"discoverable": true
},
{
"id": 21,
"name": "flame",
"displayName": "Flame",
"maxLevel": 1,
"minCost": {
"a": 0,
"b": 20
},
"maxCost": {
"a": 0,
"b": 50
},
"treasureOnly": false,
"curse": false,
"exclude": [],
"category": "bow",
"weight": 2,
"tradeable": true,
"discoverable": true
},
{
"id": 22,
"name": "infinity",
"displayName": "Infinity",
"maxLevel": 1,
"minCost": {
"a": 0,
"b": 20
},
"maxCost": {
"a": 0,
"b": 50
},
"treasureOnly": false,
"curse": false,
"exclude": [
"mending"
],
"category": "bow",
"weight": 1,
"tradeable": true,
"discoverable": true
},
{
"id": 23,
"name": "luck_of_the_sea",
"displayName": "Luck of the Sea",
"maxLevel": 3,
"minCost": {
"a": 9,
"b": 6
},
"maxCost": {
"a": 10,
"b": 51
},
"treasureOnly": false,
"curse": false,
"exclude": [
"silk_touch"
],
"category": "fishing_rod",
"weight": 2,
"tradeable": true,
"discoverable": true
},
{
"id": 24,
"name": "lure",
"displayName": "Lure",
"maxLevel": 3,
"minCost": {
"a": 9,
"b": 6
},
"maxCost": {
"a": 10,
"b": 51
},
"treasureOnly": false,
"curse": false,
"exclude": [],
"category": "fishing_rod",
"weight": 2,
"tradeable": true,
"discoverable": true
},
{
"id": 25,
"name": "frost_walker",
"displayName": "Frost Walker",
"maxLevel": 2,
"minCost": {
"a": 10,
"b": 0
},
"maxCost": {
"a": 10,
"b": 15
},
"treasureOnly": true,
"curse": false,
"exclude": [
"depth_strider"
],
"category": "armor_feet",
"weight": 2,
"tradeable": true,
"discoverable": true
},
{
"id": 26,
"name": "mending",
"displayName": "Mending",
"maxLevel": 1,
"minCost": {
"a": 25,
"b": 0
},
"maxCost": {
"a": 25,
"b": 50
},
"treasureOnly": true,
"curse": false,
"exclude": [
"infinity"
],
"category": "breakable",
"weight": 2,
"tradeable": true,
"discoverable": true
},
{
"id": 27,
"name": "binding_curse",
"displayName": "Curse of Binding",
"maxLevel": 1,
"minCost": {
"a": 0,
"b": 25
},
"maxCost": {
"a": 0,
"b": 50
},
"treasureOnly": true,
"curse": true,
"exclude": [],
"category": "wearable",
"weight": 1,
"tradeable": true,
"discoverable": true
},
{
"id": 28,
"name": "vanishing_curse",
"displayName": "Curse of Vanishing",
"maxLevel": 1,
"minCost": {
"a": 0,
"b": 25
},
"maxCost": {
"a": 0,
"b": 50
},
"treasureOnly": true,
"curse": true,
"exclude": [],
"category": "vanishable",
"weight": 1,
"tradeable": true,
"discoverable": true
},
{
"id": 29,
"name": "impaling",
"displayName": "Impaling",
"maxLevel": 5,
"minCost": {
"a": 8,
"b": -7
},
"maxCost": {
"a": 8,
"b": 13
},
"treasureOnly": false,
"curse": false,
"exclude": [],
"category": "trident",
"weight": 2,
"tradeable": true,
"discoverable": true
},
{
"id": 30,
"name": "riptide",
"displayName": "Riptide",
"maxLevel": 3,
"minCost": {
"a": 7,
"b": 10
},
"maxCost": {
"a": 0,
"b": 50
},
"treasureOnly": false,
"curse": false,
"exclude": [
"loyalty",
"channeling"
],
"category": "trident",
"weight": 2,
"tradeable": true,
"discoverable": true
},
{
"id": 31,
"name": "loyalty",
"displayName": "Loyalty",
"maxLevel": 3,
"minCost": {
"a": 7,
"b": 5
},
"maxCost": {
"a": 0,
"b": 50
},
"treasureOnly": false,
"curse": false,
"exclude": [
"riptide"
],
"category": "trident",
"weight": 5,
"tradeable": true,
"discoverable": true
},
{
"id": 32,
"name": "channeling",
"displayName": "Channeling",
"maxLevel": 1,
"minCost": {
"a": 0,
"b": 25
},
"maxCost": {
"a": 0,
"b": 50
},
"treasureOnly": false,
"curse": false,
"exclude": [
"riptide"
],
"category": "trident",
"weight": 1,
"tradeable": true,
"discoverable": true
},
{
"id": 33,
"name": "multishot",
"displayName": "Multishot",
"maxLevel": 1,
"minCost": {
"a": 0,
"b": 20
},
"maxCost": {
"a": 0,
"b": 50
},
"treasureOnly": false,
"curse": false,
"exclude": [
"piercing"
],
"category": "crossbow",
"weight": 2,
"tradeable": true,
"discoverable": true
},
{
"id": 34,
"name": "piercing",
"displayName": "Piercing",
"maxLevel": 4,
"minCost": {
"a": 10,
"b": -9
},
"maxCost": {
"a": 0,
"b": 50
},
"treasureOnly": false,
"curse": false,
"exclude": [
"multishot"
],
"category": "crossbow",
"weight": 10,
"tradeable": true,
"discoverable": true
},
{
"id": 35,
"name": "quick_charge",
"displayName": "Quick Charge",
"maxLevel": 3,
"minCost": {
"a": 20,
"b": -8
},
"maxCost": {
"a": 0,
"b": 50
},
"treasureOnly": false,
"curse": false,
"exclude": [],
"category": "crossbow",
"weight": 5,
"tradeable": true,
"discoverable": true
},
{
"id": 36,
"name": "soul_speed",
"displayName": "Soul Speed",
"maxLevel": 3,
"minCost": {
"a": 10,
"b": 0
},
"maxCost": {
"a": 10,
"b": 15
},
"treasureOnly": true,
"curse": false,
"exclude": [],
"category": "armor_feet",
"weight": 1,
"tradeable": false,
"discoverable": false
}
]
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
@@ -0,0 +1,978 @@
# !StartDocs: Types
BehaviourPackInfos: []li16
uuid: string
version: string
size: lu64
content_key: string
sub_pack_name: string
content_identity: string
has_scripts: bool
TexturePackInfos: []li16
uuid: string
version: string
size: lu64
content_key: string
sub_pack_name: string
content_identity: string
has_scripts: bool
rtx_enabled: bool
ResourcePackIdVersions: []varint
# The ID of the resource pack.
uuid: string
# The version of the resource pack.
version: string
# The subpack name of the resource pack.
name: string
ResourcePackIds: string[]li16
Experiment:
name: string
enabled: bool
Experiments: Experiment[]li32
GameMode: zigzag32 =>
0: survival
1: creative
2: adventure
3: survival_spectator
4: creative_spectator
5: fallback
GameRule:
name: string
type: varint =>
1: bool
2: int
3: float
value: type?
if bool: bool
if int: zigzag32
if float: lf32
GameRules: GameRule[]varint
# CacheBlob represents a blob as used in the client side blob cache protocol. It holds a hash of its data and
# the full data of it.
Blob:
# Hash is the hash of the blob. The hash is computed using xxHash, and must be deterministic for the same
# chunk data.
hash: lu64
# Payload is the data of the blob. When sent, the client will associate the Hash of the blob with the
# Payload in it.
payload: ByteArray
BlockPalette: []varint
name: string
state: nbt
Itemstates: []varint
name: string
runtime_id: li16
component_based: bool
Item:
network_id: zigzag32
_: network_id?
if 0: void
default:
auxiliary_value: zigzag32
has_nbt: lu16 =>
0xffff: 'true'
0x0000: 'false'
nbt: has_nbt?
if true:
version: u8
nbt: nbt
default: void
can_place_on: string[]zigzag32
can_destroy: string[]zigzag32
_: network_id?
if 355:
blocking_tick: zigzag64
vec3i:
x: zigzag32
y: zigzag32
z: zigzag32
vec3u:
x: varint
y: varint
z: varint
vec3f:
x: lf32
y: lf32
z: lf32
vec2f:
x: lf32
z: lf32
MetadataDictionary: []varint
# https://github.com/pmmp/PocketMine-MP/blob/stable/src/pocketmine/entity/Entity.php#L101
key: varint =>
0: index
1: health #int (minecart/boat)
2: variant #int
3: color #byte
4: nametag #string
5: owner_eid #long
6: target_eid #long
7: air #short
8: potion_color #int (ARGB!)
9: potion_ambient #byte
10: jump_duration #long
11: hurt_time #int (minecart/boat)
12: hurt_direction #int (minecart/boat)
13: paddle_time_left #float
14: paddle_time_right #float
15: experience_value #int (xp orb)
16: minecart_display_block #int (id | (data << 16))
17: minecart_display_offset #int
18: minecart_has_display #byte (must be 1 for minecart to show block inside)
20: old_swell
21: swell_dir
22: charge_amount
23: enderman_held_runtime_id #short
24: entity_age #short
26: player_flags
27: player_index
28: player_bed_position #block coords
29: fireball_power_x #float
30: fireball_power_y
31: fireball_power_z
32: aux_power
33: fish_x
34: fish_z
35: fish_angle
36: potion_aux_value #short
37: lead_holder_eid #long
38: scale
39: interactive_tag #string
40: npc_skin_id #string
41: url_tag #string
42: max_airdata_max_air
43: mark_variant #int
44: container_type #byte
45: container_base_size #int
46: container_extra_slots_per_strength #int
47: block_target
48: wither_invulnerable_ticks #int
49: wither_target_1 #long
50: wither_target_2 #long
51: wither_target_3 #long
52: aerial_attack
53: boundingbox_width
54: boundingbox_height
55: fuse_length
56: rider_seat_position #vector3f
57: rider_rotation_locked #byte
58: rider_max_rotation #float
59: rider_min_rotation #float
60: area_effect_cloud_radius #float
61: area_effect_cloud_waiting #int
62: area_effect_cloud_particle_id #int
63: shulker_peek_id #int
64: shulker_attach_face #byte
65: shulker_attached #short
66: shulker_attach_pos
67: trading_player_eid #long
68: trading_career
69: has_command_block
70: command_block_command #string
71: command_block_last_output #string
72: command_block_track_output #byte
73: controlling_rider_seat_number #byte
74: strength #int
75: max_strength #int
76: spell_casting_color #int
77: limited_life
78: armor_stand_pose_index # int
79: ender_crystal_time_offset # int
80: always_show_nametag # byte
81: color_2 # byte
82: name_author
83: score_tag #String
84: balloon_attached_entity # long
85: pufferfish_size
86: bubble_time
87: agent
88: sitting_amount
89: sitting_amount_previous
90: eating_counter
91: flags_extended
92: laying_amount
93: laying_amount_previous
94: duration
95: spawn_time
96: change_rate
97: change_on_pickup
98: pickup_count
99: interact_text
100: trade_tier
101: max_trade_tier
102: trade_experience
103: skin_id
104: spawning_frames
105: command_block_tick_delay
106: command_block_execute_on_first_tick
107: ambient_sound_interval
108: ambient_sound_interval_range
109: ambient_sound_event_name
110: fall_damage_multiplier
111: name_raw_text
112: can_ride_target
113: low_tier_cured_discount
114: high_tier_cured_discount
115: nearby_cured_discount
116: nearby_cured_discount_timestamp
117: hitbox
118: is_buoyant
119: buoyancy_data
type: varint =>
0: byte
1: short
2: int
3: float
4: string
5: compound
6: vec3i
7: long
8: vec3f
value: type?
if byte: i8
if short: li16
if int: zigzag32
if float: lf32
if string: string
if compound: nbt
if vec3i: vec3i
if long: zigzag64
if vec3f: vec3f
Link:
ridden_entity_id: zigzag64
rider_entity_id: zigzag64
type: u8
immediate: bool
rider_initiated: bool
Links: Link[]varint
EntityAttributes: []varint
name: string
min: lf32
value: lf32
max: lf32
Rotation:
yaw: byterot
pitch: byterot
head_yaw: byterot
BlockCoordinates: # mojang...
x: zigzag32
y: varint
z: zigzag32
PlayerAttributes: []varint
min: lf32
max: lf32
current: lf32
default: lf32
name: string
Transaction:
# LegacyRequestID is an ID that is only non-zero at times when sent by the client. The server should
# always send 0 for this. When this field is not 0, the LegacySetItemSlots slice below will have values
# in it.
# LegacyRequestID ties in with the ItemStackResponse packet. If this field is non-0, the server should
# respond with an ItemStackResponse packet. Some inventory actions such as dropping an item out of the
# hotbar are still one using this packet, and the ItemStackResponse packet needs to tie in with it.
legacy_request_id: zigzag32
# `legacy_transactions` are only present if the LegacyRequestID is non-zero. These item slots inform the
# server of the slots that were changed during the inventory transaction, and the server should send
# back an ItemStackResponse packet with these slots present in it. (Or false with no slots, if rejected.)
legacy_transactions: legacy_request_id?
if 0: void
default: []varint
container_id: u8
changed_slots: []varint
slot_id: u8
transaction_type: varint =>
# Sent for container UI operations depending on if ItemStackNetManager is enabled
0: normal
# Sent from server to client to reject a transaction
1: inventory_mismatch
# Sent for a player performing right click style item use.
# See the contained ItemUseInventoryTransaction::ActionType for the expected use case.
2: item_use
# Sent for a player right clicking on an entity or attacking them.
# See ItemUseInventoryTransaction::ActionType for which it is.
3: item_use_on_entity
# Sent when releasing right click on a chargeable item like a bow or finishing charging like a crossbow.
# This is different than canceling item use early which would be in Player Auth Input.
# See ItemReleaseInventoryTransaction::ActionType for which it is.
4: item_release
network_ids: bool
inventory_actions: []varint
source_type: varint =>
0: container
1: global
2: world_interaction
3: creative
100: craft_slot
99999: craft
_: source_type?
if container or creative:
inventory_id: varint
if world_interaction:
flags: varint
if craft or craft_slot:
action: varint
default: void
slot: varint
old_item: Item
new_item: Item
new_item_stack_id: ../network_ids?
if true: zigzag32
default: void
transaction_data: transaction_type?
if normal or inventory_mismatch: void
# UseItemTransactionData represents an inventory transaction data object sent when the client uses an item on
# a block.
if item_use:
# ActionType is the type of the UseItem inventory transaction. It is one of the action types found above,
# and specifies the way the player interacted with the block.
action_type: varint =>
0: click_block
1: click_air
2: break_block
# BlockPosition is the position of the block that was interacted with. This is only really a correct
# block position if ActionType is not UseItemActionClickAir.
block_position: BlockCoordinates
# BlockFace is the face of the block that was interacted with. When clicking the block, it is the face
# clicked. When breaking the block, it is the face that was last being hit until the block broke.
face: varint
# HotBarSlot is the hot bar slot that the player was holding while clicking the block. It should be used
# to ensure that the hot bar slot and held item are correctly synchronised with the server.
hotbar_slot: varint
# HeldItem is the item that was held to interact with the block. The server should check if this item
# is actually present in the HotBarSlot.
held_item: Item
# Position is the position of the player at the time of interaction. For clicking a block, this is the
# position at that time, whereas for breaking the block it is the position at the time of breaking.
player_pos: vec3f
# ClickedPosition is the position that was clicked relative to the block's base coordinate. It can be
# used to find out exactly where a player clicked the block.
click_pos: vec3f
# BlockRuntimeID is the runtime ID of the block that was clicked. It may be used by the server to verify
# that the player's world client-side is synchronised with the server's.
block_runtime_id: varint
# UseItemOnEntityTransactionData represents an inventory transaction data object sent when the client uses
# an item on an entity.
if item_use_on_entity:
# TargetEntityRuntimeID is the entity runtime ID of the target that was clicked. It is the runtime ID
# that was assigned to it in the AddEntity packet.
entity_runtime_id: varint64
# ActionType is the type of the UseItemOnEntity inventory transaction. It is one of the action types
# found in the constants above, and specifies the way the player interacted with the entity.
action_type: varint =>
# Right click interact with actor.
0: interact
# Left click style attack of actor or elytra spin attack. Server is expected to deal damage to the entity with visuals.
1: attack
# HotBarSlot is the hot bar slot that the player was holding while clicking the entity. It should be used
# to ensure that the hot bar slot and held item are correctly synchronised with the server.
hotbar_slot: zigzag32
# HeldItem is the item that was held to interact with the entity. The server should check if this item
# is actually present in the HotBarSlot.
held_item: Item
# Position is the position of the player at the time of clicking the entity.
player_pos: vec3f
# ClickedPosition is the position that was clicked relative to the entity's base coordinate. It can be
# used to find out exactly where a player clicked the entity.
click_pos: vec3f
# ReleaseItemTransactionData represents an inventory transaction data object sent when the client releases
# the item it was using, for example when stopping while eating or stopping the charging of a bow.
if item_release:
# ActionType is the type of the ReleaseItem inventory transaction. It is one of the action types found
# in the constants above, and specifies the way the item was released.
# As of 1.13, the ActionType is always 0. This field can be ignored, because releasing food (by consuming
# it) or releasing a bow (to shoot an arrow) is essentially the same.
action_type: varint =>
# Release right click and hold style item use, like firing a bow
0: release
# Finish right click and hold style item use, like charging a crossbow
1: consume
# HotBarSlot is the hot bar slot that the player was holding while releasing the item. It should be used
# to ensure that the hot bar slot and held item are correctly synchronised with the server.
hotbar_slot: zigzag32
# HeldItem is the item that was released. The server should check if this item is actually present in the
# HotBarSlot.
held_item: Item
# HeadPosition is the position of the player's head at the time of releasing the item. This is used
# mainly for purposes such as spawning eating particles at that position.
head_pos: vec3f
# An "ItemStack" here represents an Item instance. You can think about it like a pointer
# to an item class. The data for the class gets updated with the data in the `item` field
ItemStack:
# StackNetworkID is the network ID of the item stack. If the stack is empty, 0 is always written for this
# field. If not, the field should be set to 1 if the server authoritative inventories are disabled in the
# StartGame packet, or to a unique stack ID if it is enabled.
runtime_id: zigzag32
# Stack is the actual item stack of the item instance.
item: Item
ItemStacks: ItemStack[]varint
RecipeIngredient:
network_id: zigzag32
_: network_id?
if 0: void
default:
network_data: zigzag32
count: zigzag32
PotionTypeRecipes: []varint
input_item_id: zigzag32
input_item_meta: zigzag32
ingredient_id: zigzag32
ingredient_meta: zigzag32
output_item_id: zigzag32
output_item_meta: zigzag32
PotionContainerChangeRecipes: []varint
input_item_id: zigzag32
ingredient_id: zigzag32
output_item_id: zigzag32
Recipes: []varint
type: zigzag32 =>
'0': 'shapeless' #'ENTRY_SHAPELESS',
'1': 'shaped' #'ENTRY_SHAPED',
'2': 'furnace' # 'ENTRY_FURNACE',
# `furnace_with_metadata` is a recipe specifically used for furnace-type crafting stations. It is equal to
# `furnace`, except it has an input item with a specific metadata value, instead of any metadata value.
'3': 'furnace_with_metadata' # 'ENTRY_FURNACE_DATA', // has metadata
'4': 'multi' #'ENTRY_MULTI', //TODO
'5': 'shulker_box' #'ENTRY_SHULKER_BOX', //TODO
'6': 'shapeless_chemistry' #'ENTRY_SHAPELESS_CHEMISTRY', //TODO
'7': 'shaped_chemistry' #'ENTRY_SHAPED_CHEMISTRY', //TODO
recipe: type?
if shapeless or shulker_box or shapeless_chemistry:
recipe_id: LatinString
input: RecipeIngredient[]varint
output: Item[]varint
uuid: uuid
block: string
priority: zigzag32
network_id: zigzag32
if shaped or shaped_chemistry:
recipe_id: LatinString
width: zigzag32
height: zigzag32
# todo: can this become
# RecipeIngredient[$height][$width] or RecipeIngredient[]$height[]$width ?
input: []$width
_: RecipeIngredient[]$height
output: Item[]varint
uuid: uuid
block: string
priority: zigzag32
network_id: zigzag32
if furnace:
input_id: zigzag32
output: Item
block: string
if furnace_with_metadata:
input_id: zigzag32
input_meta: zigzag32
output: Item
block: string
if multi:
uuid: uuid
network_id: zigzag32
SkinImage:
width: li32
height: li32
data: string
Skin:
skin_id: string
skin_resource_pack: string
skin_data: SkinImage
animations: []li32
skin_image: SkinImage
animation_type: li32
animation_frames: lf32
expression_type: lf32
cape_data: SkinImage
geometry_data: string
animation_data: string
premium: string
persona: bool
cape_on_classic: bool
cape_id: string
full_skin_id: string
arm_size: string
skin_color: string
personal_pieces: []li32
piece_id: string
piece_type: string
pack_id: string
is_default_piece: bool
product_id: string
piece_tint_colors: []li32
piece_type: string
colors: string[]li32
PlayerRecords:
type: u8 =>
0: add
1: remove
records_count: varint
records: []$records_count
_: type?
if add:
uuid: uuid
entity_unique_id: zigzag64
username: string
xbox_user_id: string
platform_chat_id: string
build_platform: li32
skin_data: Skin
is_teacher: bool
is_host: bool
if remove:
uuid: uuid
verified: bool[]$records_count
ScoreEntries:
type: u8 =>
0: change
1: remove
entries: []varint
scoreboard_id: zigzag64
objective_name: string
score: li32
_: type?
if remove:
entry_type: i8 =>
1: player
2: entity
3: fake_player
entity_unique_id: entry_type?
if player or entity: zigzag64
custom_name: entry_type?
if fake_player: string
ScoreboardIdentityEntries:
type: i8 =>
0: TYPE_REGISTER_IDENTITY
1: TYPE_CLEAR_IDENTITY
entries: []varint
scoreboard_id: zigzag64
entity_unique_id: type ?
if TYPE_REGISTER_IDENTITY: zigzag64
default: void
Enchant:
id: u8
level: u8
EnchantOptions: []varint
cost: varint
slot_flags: li32
equip_enchants: Enchant[]varint
held_enchants: Enchant[]varint
self_enchants: Enchant[]varint
name: string
option_id: zigzag32
StackRequestSlotInfo:
container_id: u8
slot_id: u8
stack_id: zigzag32
#
ItemStackRequests: []varint
# RequestID is a unique ID for the request. This ID is used by the server to send a response for this
# specific request in the ItemStackResponse packet.
request_id: zigzag32
actions: []varint
type_id: u8 =>
# TakeStackRequestAction is sent by the client to the server to take x amount of items from one slot in a
# container to the cursor.
0: take
# PlaceStackRequestAction is sent by the client to the server to place x amount of items from one slot into
# another slot, such as when shift clicking an item in the inventory to move it around or when moving an item
# in the cursor into a slot.
1: place
# SwapStackRequestAction is sent by the client to swap the item in its cursor with an item present in another
# container. The two item stacks swap places.
2: swap
# DropStackRequestAction is sent by the client when it drops an item out of the inventory when it has its
# inventory opened. This action is not sent when a player drops an item out of the hotbar using the Q button
# (or the equivalent on mobile). The InventoryTransaction packet is still used for that action, regardless of
# whether the item stack network IDs are used or not.
3: drop
# DestroyStackRequestAction is sent by the client when it destroys an item in creative mode by moving it
# back into the creative inventory.
4: destroy
# ConsumeStackRequestAction is sent by the client when it uses an item to craft another item. The original
# item is 'consumed'.
5: consume
# CreateStackRequestAction is sent by the client when an item is created through being used as part of a
# recipe. For example, when milk is used to craft a cake, the buckets are leftover. The buckets are moved to
# the slot sent by the client here.
# Note that before this is sent, an action for consuming all items in the crafting table/grid is sent. Items
# that are not fully consumed when used for a recipe should not be destroyed there, but instead, should be
# turned into their respective resulting items.
6: create
# LabTableCombineStackRequestAction is sent by the client when it uses a lab table to combine item stacks.
7: lab_table_combine
# BeaconPaymentStackRequestAction is sent by the client when it submits an item to enable effects from a
# beacon. These items will have been moved into the beacon item slot in advance.
8: beacon_payment
# CraftRecipeStackRequestAction is sent by the client the moment it begins crafting an item. This is the
# first action sent, before the Consume and Create item stack request actions.
# This action is also sent when an item is enchanted. Enchanting should be treated mostly the same way as
# crafting, where the old item is consumed.
9: craft_recipe
# AutoCraftRecipeStackRequestAction is sent by the client similarly to the CraftRecipeStackRequestAction. The
# only difference is that the recipe is automatically created and crafted by shift clicking the recipe book.
10: craft_recipe_auto #recipe book?
# CraftCreativeStackRequestAction is sent by the client when it takes an item out fo the creative inventory.
# The item is thus not really crafted, but instantly created.
11: craft_creative
# CraftRecipeOptionalStackRequestAction is sent when using an anvil. When this action is sent, the
# CustomNames field in the respective stack request is non-empty and contains the name of the item created
# using the anvil.
12: optional
# CraftNonImplementedStackRequestAction is an action sent for inventory actions that aren't yet implemented
# in the new system. These include, for example, anvils.
13: non_implemented #anvils aren't fully implemented yet
# CraftResultsDeprecatedStackRequestAction is an additional, deprecated packet sent by the client after
# crafting. It holds the final results and the amount of times the recipe was crafted. It shouldn't be used.
# This action is also sent when an item is enchanted. Enchanting should be treated mostly the same way as
# crafting, where the old item is consumed.
14: results_deprecated
_: type_id ?
if take or place:
count: u8
source: StackRequestSlotInfo
destination: StackRequestSlotInfo
if swap:
# Source and Destination point to the source slot from which Count of the item stack were taken and the
# destination slot to which this item was moved.
source: StackRequestSlotInfo
destination: StackRequestSlotInfo
if drop:
# Count is the count of the item in the source slot that was taken towards the destination slot.
count: u8
# Source is the source slot from which items were dropped to the ground.
source: StackRequestSlotInfo
# Randomly seems to be set to false in most cases. I'm not entirely sure what this does, but this is what
# vanilla calls this field.
randomly: bool
if destroy or consume:
# Count is the count of the item in the source slot that was destroyed.
count: u8
# Source is the source slot from which items came that were destroyed by moving them into the creative
# inventory.
source: StackRequestSlotInfo
if create:
# ResultsSlot is the slot in the inventory in which the results of the crafting ingredients are to be
# placed.
result_slot_id: u8
if beacon_payment:
# PrimaryEffect and SecondaryEffect are the effects that were selected from the beacon.
primary_effect: zigzag32
secondary_effect: zigzag32
if craft_recipe:
# RecipeNetworkID is the network ID of the recipe that is about to be crafted. This network ID matches
# one of the recipes sent in the CraftingData packet, where each of the recipes have a RecipeNetworkID as
# of 1.16.
recipe_network_id: varint
if craft_recipe_auto:
# RecipeNetworkID is the network ID of the recipe that is about to be crafted. This network ID matches
# one of the recipes sent in the CraftingData packet, where each of the recipes have a RecipeNetworkID as
recipe_network_id: varint
times_crafted: u8
if craft_creative:
# CreativeItemNetworkID is the network ID of the creative item that is being created. This is one of the
# creative item network IDs sent in the CreativeContent packet.
creative_item_network_id: varint
if optional:
# For the cartography table, if a certain MULTI recipe is being called, this points to the network ID that was assigned.
recipe_network_id: varint
# Most likely the index in the request's filter strings that this action is using
filtered_string_index: li32
if non_implemented: void
if results_deprecated:
result_items: Item[]varint
times_crafted: u8
# CustomNames is a list of custom names involved in the request. This is typically filled with one string
# when an anvil is used.
# * Used for the server to determine which strings should be filtered. Used in anvils to verify a renamed item.
custom_names: string[]varint
ItemStackResponses: []varint
result: u8
request_id: varint
containers: []varint
container_id: u8
slots: []varint
slot: u8
hotbar_slot: u8
count: u8
item_stack_id: varint
custom_name: string
ItemComponentList: []varint
# Name is the name of the item, which is a name like 'minecraft:stick'.
name: string
# Data is a map containing the components and properties of the item.
nbt: nbt
CommandOrigin:
# Origin is one of the values above that specifies the origin of the command. The origin may change,
# depending on what part of the client actually called the command. The command may be issued by a
# websocket server, for example.
type: varint =>
0: player
1: block
2: minecart_block
3: dev_console
4: test
5: automation_player
6: client_automation
7: dedicated_server
8: entity
9: virtual
10: game_argument
11: entity_server
# UUID is the UUID of the command called. This UUID is a bit odd as it is not specified by the server. It
# is not clear what exactly this UUID is meant to identify, but it is unique for each command called.
uuid: uuid
# RequestID is an ID that identifies the request of the client. The server should send a CommandOrigin
# with the same request ID to ensure it can be matched with the request by the caller of the command.
# This is especially important for websocket servers and it seems that this field is only non-empty for
# these websocket servers.
request_id: string
# PlayerUniqueID is an ID that identifies the player, the same as the one found in the AdventureSettings
# packet. Filling it out with 0 seems to work.
# PlayerUniqueID is only written if Origin is CommandOriginDevConsole or CommandOriginTest.
player_entity_id: type?
if dev_console or test:
player_entity_id: zigzag64
# Some arbitrary definitions from CBMC, Window IDs are normally
# unique + sequential
WindowID: i8 =>
-100: drop_contents
-24: beacon
-23: trading_output
-22: trading_use_inputs
-21: trading_input_2
-20: trading_input_1
-17: enchant_output
-16: enchant_material
-15: enchant_input
-13: anvil_output
-12: anvil_result
-11: anvil_material
-10: container_input
-5: crafting_use_ingredient
-4: crafting_result
-3: crafting_remove_ingredient
-2: crafting_add_ingredient
-1: none
0: inventory
1: first
100: last
119: offhand
120: armor
121: creative
122: hotbar
123: fixed_inventory
124: ui
WindowIDVarint: varint =>
-100: drop_contents
-24: beacon
-23: trading_output
-22: trading_use_inputs
-21: trading_input_2
-20: trading_input_1
-17: enchant_output
-16: enchant_material
-15: enchant_input
-13: anvil_output
-12: anvil_result
-11: anvil_material
-10: container_input
-5: crafting_use_ingredient
-4: crafting_result
-3: crafting_remove_ingredient
-2: crafting_add_ingredient
-1: none
0: inventory
1: first
100: last
119: offhand
120: armor
121: creative
122: hotbar
123: fixed_inventory
124: ui
WindowType: u8 =>
0: container
1: workbench
2: furnace
3: enchantment
4: brewing_stand
5: anvil
6: dispenser
7: dropper
8: hopper
9: cauldron
10: minecart_chest
11: minecart_hopper
12: horse
13: beacon
14: structure_editor
15: trading
16: command_block
17: jukebox
18: armor
19: hand
20: compound_creator
21: element_constructor
22: material_reducer
23: lab_table
24: loom
25: lectern
26: grindstone
27: blast_furnace
28: smoker
29: stonecutter
30: cartography
31: hud
32: jigsaw_editor
33: smithing_table
# TODO: remove?
LegacyEntityType: li32 =>
10: chicken
11: cow
12: pig
13: sheep
14: wolf
15: villager
16: mooshroom
17: squid
18: rabbit
19: bat
20: iron_golem
21: snow_golem
22: ocelot
23: horse
24: donkey
25: mule
26: skeleton_horse
27: zombie_horse
28: polar_bear
29: llama
30: parrot
31: dolphin
32: zombie
33: creeper
34: skeleton
35: spider
36: zombie_pigman
37: slime
38: enderman
39: silverfish
40: cave_spider
41: ghast
42: magma_cube
43: blaze
44: zombie_villager
45: witch
46: stray
47: husk
48: wither_skeleton
49: guardian
50: elder_guardian
51: npc
52: wither
53: ender_dragon
54: shulker
55: endermite
56: agent # LEARN_TO_CODE_MASCOT
57: vindicator
58: phantom
61: armor_stand
62: tripod_camera
63: player
64: item
65: tnt
66: falling_block
67: moving_block
68: xp_bottle
69: xp_orb
70: eye_of_ender_signal
71: ender_crystal
72: fireworks_rocket
73: thrown_trident
74: turtle
75: cat
76: shulker_bullet
77: fishing_hook
78: chalkboard
79: dragon_fireball
80: arrow
81: snowball
82: egg
83: painting
84: minecart
85: fireball
86: splash_potion
87: ender_pearl
88: leash_knot
89: wither_skull
90: boat
91: wither_skull_dangerous
93: lightning_bolt
94: small_fireball
95: area_effect_cloud
96: hopper_minecart
97: tnt_minecart
98: chest_minecart
100: command_block_minecart
101: lingering_potion
102: llama_spit
103: evocation_fang
104: evocation_illager
105: vex
106: ice_bomb
107: balloon
108: pufferfish
109: salmon
110: drowned
111: tropicalfish
112: cod
113: panda
@@ -0,0 +1,6 @@
{
"version": 422,
"minecraftVersion": "1.16.201",
"majorVersion": "1.16",
"releaseType": "release"
}
@@ -0,0 +1,177 @@
[
{
"id": "cursor",
"name": "cursor",
"slots": [
{
"name": "cursor",
"index": 0
}
]
},
{
"id": "armor",
"name": "armor",
"slots": [
{
"name": "armor",
"index": 0,
"size": 4
}
]
},
{
"id": "hand",
"name": "hand",
"slots": [
{
"name": "offhand",
"index": 0
}
]
},
{
"id": "container",
"name": "container",
"slots": [
{
"name": "container",
"index": 0,
"size": 28
}
]
},
{
"id": "inventory",
"name": "Inventory",
"slots": [
{
"name": "hotbar_and_inventory",
"index": 0,
"size": 36
},
{
"name": "inventory",
"index": 9,
"size": 36
},
{
"name": "hotbar",
"index": 0,
"size": 9
}
]
},
{
"id": "workbench",
"name": "Crafting Table",
"slots": [
{
"name": "crafting_input",
"index": 0,
"size": 4
}
]
},
{
"id": "furnace",
"name": "furnace",
"slots": [
{
"name": "furnace_fuel",
"index": 1
},
{
"name": "furnace_ingredient",
"index": 0
}
]
},
{
"id": "enchantment",
"name": "enchantment",
"slots": [
{
"name": "enchanting_lapis",
"index": 15
},
{
"name": "enchanting_input",
"index": 14
}
]
},
{
"id": "brewing_stand",
"name": "brewing_stand",
"slots": [
{
"name": "brewing_input",
"index": 1,
"size": 3
},
{
"name": "brewing_fuel",
"index": 4
}
]
},
{
"id": "anvil",
"name": "anvil",
"slots": [
{
"name": "anvil_input",
"index": 1
},
{
"name": "anvil_material",
"index": 2
}
]
},
{
"id": "dispenser",
"name": "dispenser",
"slots": [
{
"name": "container",
"index": 0,
"size": 9
}
]
},
{
"id": "dropper",
"name": "dropper",
"slots": [
{
"name": "container",
"index": 0,
"size": 9
}
]
},
{
"id": "hopper",
"name": "hopper",
"slots": [
{
"name": "container",
"index": 0,
"size": 4
}
]
},
{
"id": "minecart_hopper",
"name": "minecart_hopper",
"slots": [
{
"name": "container",
"index": 0,
"size": 4
}
]
}
]
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,6 @@
{
"version": 428,
"minecraftVersion": "1.16.210",
"majorVersion": "1.16",
"releaseType": "release"
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,6 @@
{
"version": 431,
"minecraftVersion": "1.16.220",
"majorVersion": "1.16",
"releaseType": "release"
}
@@ -0,0 +1,968 @@
[
{
"id": 0,
"name": "ocean",
"category": "ocean",
"precipitation": "rain",
"depth": -1,
"dimension": "overworld",
"displayName": "Ocean",
"color": 112,
"rainfall": 0.5,
"temperature": 0.5
},
{
"id": 1,
"name": "plains",
"category": "plains",
"precipitation": "rain",
"depth": 0.125,
"dimension": "overworld",
"displayName": "Plains",
"color": 9286496,
"rainfall": 0.4000000059604645,
"temperature": 0.800000011920929,
"child": 129
},
{
"id": 2,
"name": "desert",
"category": "desert",
"precipitation": "none",
"depth": 0.125,
"dimension": "overworld",
"displayName": "Desert",
"color": 16421912,
"rainfall": 0,
"temperature": 2,
"child": 130
},
{
"id": 3,
"name": "extreme_hills",
"category": "extreme_hills",
"precipitation": "rain",
"depth": 1,
"dimension": "overworld",
"displayName": "Mountains",
"color": 6316128,
"rainfall": 0.30000001192092896,
"temperature": 0.20000000298023224,
"child": 131
},
{
"id": 4,
"name": "forest",
"category": "forest",
"precipitation": "rain",
"depth": 0.1,
"dimension": "overworld",
"displayName": "Forest",
"color": 353825,
"rainfall": 0.800000011920929,
"temperature": 0.699999988079071,
"child": 132
},
{
"id": 5,
"name": "taiga",
"category": "taiga",
"precipitation": "rain",
"depth": 0.2,
"dimension": "overworld",
"displayName": "Taiga",
"color": 747097,
"rainfall": 0.800000011920929,
"temperature": 0.25,
"child": 133
},
{
"id": 6,
"name": "swampland",
"category": "swamp",
"precipitation": "rain",
"depth": -0.2,
"dimension": "overworld",
"displayName": "Swamp",
"color": 522674,
"rainfall": 0.5,
"temperature": 0.800000011920929,
"child": 134
},
{
"id": 7,
"name": "river",
"category": "river",
"precipitation": "rain",
"depth": -0.5,
"dimension": "overworld",
"displayName": "River",
"color": 255,
"rainfall": 0.5,
"temperature": 0.5
},
{
"id": 8,
"name": "hell",
"category": "",
"precipitation": "rain",
"depth": 0,
"dimension": "overworld",
"displayName": "hell",
"color": 0,
"rainfall": 0,
"temperature": 2
},
{
"id": 9,
"name": "the_end",
"category": "the_end",
"precipitation": "none",
"depth": 0.1,
"dimension": "end",
"displayName": "The End",
"color": 8421631,
"rainfall": 0.5,
"temperature": 0.5
},
{
"id": 11,
"name": "frozen_river",
"category": "river",
"precipitation": "snow",
"depth": -0.5,
"dimension": "overworld",
"displayName": "Frozen River",
"color": 10526975,
"rainfall": 0.5,
"temperature": 0
},
{
"id": 12,
"name": "ice_plains",
"category": "icy",
"precipitation": "snow",
"depth": 0.125,
"dimension": "overworld",
"displayName": "Snowy Tundra",
"color": 16777215,
"rainfall": 0.5,
"temperature": 0,
"child": 140
},
{
"id": 13,
"name": "ice_mountains",
"category": "icy",
"precipitation": "snow",
"depth": 0.45,
"dimension": "overworld",
"displayName": "Snowy Mountains",
"color": 10526880,
"rainfall": 0.5,
"temperature": 0
},
{
"id": 14,
"name": "mushroom_island",
"category": "mushroom",
"precipitation": "rain",
"depth": 0.2,
"dimension": "overworld",
"displayName": "Mushroom Fields",
"color": 16711935,
"rainfall": 1,
"temperature": 0.8999999761581421
},
{
"id": 15,
"name": "mushroom_island_shore",
"category": "mushroom",
"precipitation": "rain",
"depth": 0,
"dimension": "overworld",
"displayName": "Mushroom Fields Shore",
"color": 10486015,
"rainfall": 1,
"temperature": 0.8999999761581421
},
{
"id": 16,
"name": "beach",
"category": "beach",
"precipitation": "rain",
"depth": 0,
"dimension": "overworld",
"displayName": "Beach",
"color": 16440917,
"rainfall": 0.4000000059604645,
"temperature": 0.800000011920929
},
{
"id": 17,
"name": "desert_hills",
"category": "desert",
"precipitation": "none",
"depth": 0.45,
"dimension": "overworld",
"displayName": "Desert Hills",
"color": 13786898,
"rainfall": 0,
"temperature": 2
},
{
"id": 18,
"name": "forest_hills",
"category": "forest",
"precipitation": "rain",
"depth": 0.45,
"dimension": "overworld",
"displayName": "Wooded Hills",
"color": 2250012,
"rainfall": 0.800000011920929,
"temperature": 0.699999988079071
},
{
"id": 19,
"name": "taiga_hills",
"category": "taiga",
"precipitation": "rain",
"depth": 0.45,
"dimension": "overworld",
"displayName": "Taiga Hills",
"color": 1456435,
"rainfall": 0.800000011920929,
"temperature": 0.25
},
{
"id": 20,
"name": "extreme_hills_edge",
"category": "extreme_hills",
"precipitation": "rain",
"depth": 0.8,
"dimension": "overworld",
"displayName": "Mountain Edge",
"color": 7501978,
"rainfall": 0.30000001192092896,
"temperature": 0.20000000298023224
},
{
"id": 21,
"name": "jungle",
"category": "jungle",
"precipitation": "rain",
"depth": 0.1,
"dimension": "overworld",
"displayName": "Jungle",
"color": 5470985,
"rainfall": 0.8999999761581421,
"temperature": 0.949999988079071,
"child": 149
},
{
"id": 22,
"name": "jungle_hills",
"category": "jungle",
"precipitation": "rain",
"depth": 0.45,
"dimension": "overworld",
"displayName": "Jungle Hills",
"color": 2900485,
"rainfall": 0.8999999761581421,
"temperature": 0.949999988079071
},
{
"id": 23,
"name": "jungle_edge",
"category": "jungle",
"precipitation": "rain",
"depth": 0.1,
"dimension": "overworld",
"displayName": "Jungle Edge",
"color": 6458135,
"rainfall": 0.800000011920929,
"temperature": 0.949999988079071,
"child": 151
},
{
"id": 24,
"name": "deep_ocean",
"category": "ocean",
"precipitation": "rain",
"depth": -1.8,
"dimension": "overworld",
"displayName": "Deep Ocean",
"color": 48,
"rainfall": 0.5,
"temperature": 0.5
},
{
"id": 25,
"name": "stone_beach",
"category": "none",
"precipitation": "rain",
"depth": 0.1,
"dimension": "overworld",
"displayName": "Stone Shore",
"color": 10658436,
"rainfall": 0.30000001192092896,
"temperature": 0.20000000298023224
},
{
"id": 26,
"name": "cold_beach",
"category": "beach",
"precipitation": "snow",
"depth": 0,
"dimension": "overworld",
"displayName": "Snowy Beach",
"color": 16445632,
"rainfall": 0.30000001192092896,
"temperature": 0.05000000074505806
},
{
"id": 27,
"name": "birch_forest",
"category": "forest",
"precipitation": "rain",
"depth": 0.1,
"dimension": "overworld",
"displayName": "Birch Forest",
"color": 3175492,
"rainfall": 0.6000000238418579,
"temperature": 0.6000000238418579,
"child": 155
},
{
"id": 28,
"name": "birch_forest_hills",
"category": "forest",
"precipitation": "rain",
"depth": 0.45,
"dimension": "overworld",
"displayName": "Birch Forest Hills",
"color": 2055986,
"rainfall": 0.6000000238418579,
"temperature": 0.6000000238418579,
"child": 156
},
{
"id": 29,
"name": "roofed_forest",
"category": "forest",
"precipitation": "rain",
"depth": 0.1,
"dimension": "overworld",
"displayName": "Dark Forest",
"color": 4215066,
"rainfall": 0.800000011920929,
"temperature": 0.699999988079071,
"child": 157
},
{
"id": 30,
"name": "cold_taiga",
"category": "taiga",
"precipitation": "snow",
"depth": 0.2,
"dimension": "overworld",
"displayName": "Snowy Taiga",
"color": 3233098,
"rainfall": 0.4000000059604645,
"temperature": -0.5,
"child": 158
},
{
"id": 31,
"name": "cold_taiga_hills",
"category": "taiga",
"precipitation": "snow",
"depth": 0.45,
"dimension": "overworld",
"displayName": "Snowy Taiga Hills",
"color": 2375478,
"rainfall": 0.4000000059604645,
"temperature": -0.5
},
{
"id": 32,
"name": "mega_taiga",
"category": "taiga",
"precipitation": "rain",
"depth": 0.2,
"dimension": "overworld",
"displayName": "Giant Tree Taiga",
"color": 5858897,
"rainfall": 0.800000011920929,
"temperature": 0.30000001192092896,
"child": 160
},
{
"id": 33,
"name": "mega_taiga_hills",
"category": "taiga",
"precipitation": "rain",
"depth": 0.45,
"dimension": "overworld",
"displayName": "Giant Tree Taiga Hills",
"color": 4542270,
"rainfall": 0.800000011920929,
"temperature": 0.30000001192092896,
"child": 161
},
{
"id": 34,
"name": "extreme_hills_plus_trees",
"category": "extreme_hills",
"precipitation": "rain",
"depth": 1,
"dimension": "overworld",
"displayName": "Wooded Mountains",
"color": 5271632,
"rainfall": 0.30000001192092896,
"temperature": 0.20000000298023224,
"child": 162
},
{
"id": 35,
"name": "savanna",
"category": "savanna",
"precipitation": "none",
"depth": 0.125,
"dimension": "overworld",
"displayName": "Savanna",
"color": 12431967,
"rainfall": 0,
"temperature": 1.2000000476837158,
"child": 163
},
{
"id": 36,
"name": "savanna_plateau",
"category": "savanna",
"precipitation": "none",
"depth": 1.5,
"dimension": "overworld",
"displayName": "Savanna Plateau",
"color": 10984804,
"rainfall": 0,
"temperature": 1,
"child": 164
},
{
"id": 37,
"name": "mesa",
"category": "mesa",
"precipitation": "none",
"depth": 0.1,
"dimension": "overworld",
"displayName": "Badlands",
"color": 14238997,
"rainfall": 0,
"temperature": 2,
"child": 165
},
{
"id": 38,
"name": "mesa_plateau_stone",
"category": "mesa",
"precipitation": "none",
"depth": 1.5,
"dimension": "overworld",
"displayName": "Wooded Badlands Plateau",
"color": 11573093,
"rainfall": 0,
"temperature": 2,
"child": 166
},
{
"id": 39,
"name": "mesa_plateau",
"category": "mesa",
"precipitation": "none",
"depth": 1.5,
"dimension": "overworld",
"displayName": "Badlands Plateau",
"color": 13274213,
"rainfall": 0,
"temperature": 2,
"child": 167
},
{
"id": 40,
"name": "warm_ocean",
"category": "ocean",
"precipitation": "rain",
"depth": -1,
"dimension": "overworld",
"displayName": "Warm Ocean",
"color": 172,
"rainfall": 0.5,
"temperature": 0.5
},
{
"id": 41,
"name": "deep_warm_ocean",
"category": "ocean",
"precipitation": "rain",
"depth": -1.8,
"dimension": "overworld",
"displayName": "Deep Warm Ocean",
"color": 80,
"rainfall": 0.5,
"temperature": 0.5
},
{
"id": 42,
"name": "lukewarm_ocean",
"category": "ocean",
"precipitation": "rain",
"depth": -1,
"dimension": "overworld",
"displayName": "Lukewarm Ocean",
"color": 144,
"rainfall": 0.5,
"temperature": 0.5
},
{
"id": 43,
"name": "deep_lukewarm_ocean",
"category": "ocean",
"precipitation": "rain",
"depth": -1.8,
"dimension": "overworld",
"displayName": "Deep Lukewarm Ocean",
"color": 64,
"rainfall": 0.5,
"temperature": 0.5
},
{
"id": 44,
"name": "cold_ocean",
"category": "ocean",
"precipitation": "rain",
"depth": -1,
"dimension": "overworld",
"displayName": "Cold Ocean",
"color": 2105456,
"rainfall": 0.5,
"temperature": 0.5
},
{
"id": 45,
"name": "deep_cold_ocean",
"category": "ocean",
"precipitation": "rain",
"depth": -1.8,
"dimension": "overworld",
"displayName": "Deep Cold Ocean",
"color": 2105400,
"rainfall": 0.5,
"temperature": 0.5
},
{
"id": 46,
"name": "frozen_ocean",
"category": "ocean",
"precipitation": "snow",
"depth": -1,
"dimension": "overworld",
"displayName": "Frozen Ocean",
"color": 7368918,
"rainfall": 0.5,
"temperature": 0
},
{
"id": 47,
"name": "deep_frozen_ocean",
"category": "ocean",
"precipitation": "rain",
"depth": -1.8,
"dimension": "overworld",
"displayName": "Deep Frozen Ocean",
"color": 4210832,
"rainfall": 0.5,
"temperature": 0
},
{
"id": 48,
"name": "bamboo_jungle",
"category": "jungle",
"precipitation": "rain",
"depth": 0.1,
"dimension": "overworld",
"displayName": "Bamboo Jungle",
"color": 7769620,
"rainfall": 0.8999999761581421,
"temperature": 0.949999988079071
},
{
"id": 49,
"name": "bamboo_jungle_hills",
"category": "jungle",
"precipitation": "rain",
"depth": 0.45,
"dimension": "overworld",
"displayName": "Bamboo Jungle Hills",
"color": 3884810,
"rainfall": 0.8999999761581421,
"temperature": 0.949999988079071
},
{
"id": 129,
"name": "sunflower_plains",
"category": "plains",
"precipitation": "rain",
"depth": 0.125,
"dimension": "overworld",
"displayName": "Sunflower Plains",
"color": 11918216,
"rainfall": 0.4000000059604645,
"temperature": 0.800000011920929,
"parent": "plains"
},
{
"id": 130,
"name": "desert_mutated",
"category": "desert",
"precipitation": "none",
"depth": 0.125,
"dimension": "overworld",
"displayName": "Desert Lakes",
"color": 16759872,
"rainfall": 0,
"temperature": 2,
"parent": "desert"
},
{
"id": 131,
"name": "extreme_hills_mutated",
"category": "extreme_hills",
"precipitation": "rain",
"depth": 1,
"dimension": "overworld",
"displayName": "Gravelly Mountains",
"color": 8947848,
"rainfall": 0.30000001192092896,
"temperature": 0.20000000298023224,
"parent": "mountains"
},
{
"id": 132,
"name": "flower_forest",
"category": "forest",
"precipitation": "rain",
"depth": 0.1,
"dimension": "overworld",
"displayName": "Flower Forest",
"color": 2985545,
"rainfall": 0.800000011920929,
"temperature": 0.699999988079071,
"parent": "forest"
},
{
"id": 133,
"name": "taiga_mutated",
"category": "taiga",
"precipitation": "rain",
"depth": 0.3,
"dimension": "overworld",
"displayName": "Taiga Mountains",
"color": 3378817,
"rainfall": 0.800000011920929,
"temperature": 0.25,
"parent": "taiga"
},
{
"id": 134,
"name": "swampland_mutated",
"category": "swamp",
"precipitation": "rain",
"depth": -0.1,
"dimension": "overworld",
"displayName": "Swamp Hills",
"color": 3145690,
"rainfall": 0.5,
"temperature": 0.800000011920929,
"parent": "swamp"
},
{
"id": 140,
"name": "ice_plains_spikes",
"category": "icy",
"precipitation": "snow",
"depth": 0.425,
"dimension": "overworld",
"displayName": "Ice Spikes",
"color": 11853020,
"rainfall": 1,
"temperature": 0,
"parent": "snowy_tundra"
},
{
"id": 149,
"name": "jungle_mutated",
"category": "jungle",
"precipitation": "rain",
"depth": 0.2,
"dimension": "overworld",
"displayName": "Modified Jungle",
"color": 8102705,
"rainfall": 0.8999999761581421,
"temperature": 0.949999988079071,
"parent": "jungle"
},
{
"id": 151,
"name": "jungle_edge_mutated",
"category": "jungle",
"precipitation": "rain",
"depth": 0.2,
"dimension": "overworld",
"displayName": "Modified Jungle Edge",
"color": 9089855,
"rainfall": 0.800000011920929,
"temperature": 0.949999988079071,
"parent": "jungle_edge"
},
{
"id": 155,
"name": "birch_forest_mutated",
"category": "forest",
"precipitation": "rain",
"depth": 0.2,
"dimension": "overworld",
"displayName": "Tall Birch Forest",
"color": 5807212,
"rainfall": 0.800000011920929,
"temperature": 0.699999988079071,
"parent": "birch_forest"
},
{
"id": 156,
"name": "birch_forest_hills_mutated",
"category": "forest",
"precipitation": "rain",
"depth": 0.55,
"dimension": "overworld",
"displayName": "Tall Birch Hills",
"color": 4687706,
"rainfall": 0.800000011920929,
"temperature": 0.699999988079071,
"parent": "birch_forest_hills"
},
{
"id": 157,
"name": "roofed_forest_mutated",
"category": "forest",
"precipitation": "rain",
"depth": 0.2,
"dimension": "overworld",
"displayName": "Dark Forest Hills",
"color": 6846786,
"rainfall": 0.800000011920929,
"temperature": 0.699999988079071,
"parent": "dark_forest"
},
{
"id": 158,
"name": "cold_taiga_mutated",
"category": "taiga",
"precipitation": "snow",
"depth": 0.3,
"dimension": "overworld",
"displayName": "Snowy Taiga Mountains",
"color": 5864818,
"rainfall": 0.4000000059604645,
"temperature": -0.5,
"parent": "snowy_taiga"
},
{
"id": 160,
"name": "redwood_taiga_mutated",
"category": "taiga",
"precipitation": "rain",
"depth": 0.2,
"dimension": "overworld",
"displayName": "Giant Spruce Taiga",
"color": 8490617,
"rainfall": 0.800000011920929,
"temperature": 0.25,
"parent": "giant_tree_taiga"
},
{
"id": 161,
"name": "redwood_taiga_hills_mutated",
"category": "taiga",
"precipitation": "rain",
"depth": 0.2,
"dimension": "overworld",
"displayName": "Giant Spruce Taiga Hills",
"color": 7173990,
"rainfall": 0.800000011920929,
"temperature": 0.30000001192092896,
"parent": "giant_tree_taiga_hills"
},
{
"id": 162,
"name": "extreme_hills_plus_trees_mutated",
"category": "extreme_hills",
"precipitation": "rain",
"depth": 1,
"dimension": "overworld",
"displayName": "Gravelly Mountains+",
"color": 7903352,
"rainfall": 0.30000001192092896,
"temperature": 0.20000000298023224,
"parent": "wooded_mountains"
},
{
"id": 163,
"name": "savanna_mutated",
"category": "savanna",
"precipitation": "none",
"depth": 0.3625,
"dimension": "overworld",
"displayName": "Shattered Savanna",
"color": 15063687,
"rainfall": 0.5,
"temperature": 1.100000023841858,
"parent": "savanna"
},
{
"id": 164,
"name": "savanna_plateau_mutated",
"category": "savanna",
"precipitation": "none",
"depth": 1.05,
"dimension": "overworld",
"displayName": "Shattered Savanna Plateau",
"color": 13616524,
"rainfall": 0.5,
"temperature": 1,
"parent": "savanna_plateau"
},
{
"id": 165,
"name": "mesa_bryce",
"category": "mesa",
"precipitation": "none",
"depth": 0.1,
"dimension": "overworld",
"displayName": "Eroded Badlands",
"color": 16739645,
"rainfall": 0,
"temperature": 2,
"parent": "badlands"
},
{
"id": 166,
"name": "mesa_plateau_stone_mutated",
"category": "mesa",
"precipitation": "none",
"depth": 0.45,
"dimension": "overworld",
"displayName": "Modified Wooded Badlands Plateau",
"color": 14204813,
"rainfall": 0,
"temperature": 2,
"parent": "wooded_badlands_plateau"
},
{
"id": 167,
"name": "mesa_plateau_mutated",
"category": "mesa",
"precipitation": "none",
"depth": 0.45,
"dimension": "overworld",
"displayName": "Modified Badlands Plateau",
"color": 15905933,
"rainfall": 0,
"temperature": 2,
"parent": "badlands_plateau"
},
{
"id": 178,
"name": "soulsand_valley",
"category": "nether",
"precipitation": "none",
"depth": 0.1,
"dimension": "nether",
"displayName": "Soul Sand Valley",
"color": 6174768,
"rainfall": 0,
"temperature": 2,
"climates": [
{
"temperature": 0,
"humidity": -0.5,
"altitude": 0,
"weirdness": 0,
"offset": 0
}
]
},
{
"id": 179,
"name": "crimson_forest",
"category": "nether",
"precipitation": "none",
"depth": 0.1,
"dimension": "nether",
"displayName": "Crimson Forest",
"color": 14485512,
"rainfall": 0,
"temperature": 2,
"climates": [
{
"temperature": 0.4,
"humidity": 0,
"altitude": 0,
"weirdness": 0,
"offset": 0
}
]
},
{
"id": 180,
"name": "warped_forest",
"category": "nether",
"precipitation": "none",
"depth": 0.1,
"dimension": "nether",
"displayName": "Warped Forest",
"color": 4821115,
"rainfall": 0,
"temperature": 2,
"climates": [
{
"temperature": 0,
"humidity": 0.5,
"altitude": 0,
"weirdness": 0,
"offset": 0.375
}
]
},
{
"id": 181,
"name": "basalt_deltas",
"category": "nether",
"precipitation": "none",
"depth": 0.1,
"dimension": "nether",
"displayName": "Basalt Deltas",
"color": 4208182,
"rainfall": 0,
"temperature": 2,
"climates": [
{
"temperature": -0.5,
"humidity": 0,
"altitude": 0,
"weirdness": 0,
"offset": 0.175
}
]
}
]
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,82 @@
[
{
"id": 0,
"sound": "note.harp",
"name": "harp"
},
{
"id": 1,
"sound": "note.bd",
"name": "basedrum"
},
{
"id": 2,
"sound": "note.snare",
"name": "snare"
},
{
"id": 3,
"sound": "note.hat",
"name": "hat"
},
{
"id": 4,
"sound": "note.bass",
"name": "bass"
},
{
"id": 5,
"sound": "note.flute",
"name": "flute"
},
{
"id": 6,
"sound": "note.bell",
"name": "bell"
},
{
"id": 7,
"sound": "note.guitar",
"name": "guitar"
},
{
"id": 8,
"sound": "note.chime",
"name": "chime"
},
{
"id": 9,
"sound": "note.xylophone",
"name": "xylophone"
},
{
"id": 10,
"sound": "note.iron_xylophone",
"name": "iron_xylophone"
},
{
"id": 11,
"sound": "note.cow_bell",
"name": "cow_bell"
},
{
"id": 12,
"sound": "note.didgeridoo",
"name": "didgeridoo"
},
{
"id": 13,
"sound": "note.bit",
"name": "bit"
},
{
"id": 14,
"sound": "note.banjo",
"name": "banjo"
},
{
"id": 15,
"sound": "note.pling",
"name": "pling"
}
]
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,6 @@
{
"version": 440,
"minecraftVersion": "1.17.0",
"majorVersion": "1.17",
"releaseType": "release"
}
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More