Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
69e9857bad | ||
|
|
d0aec490cc | ||
|
|
a01cb08672 | ||
|
|
326affe364 | ||
|
|
1b2552492b | ||
|
|
44ec148be4 | ||
|
|
78442c5c68 | ||
|
|
ac12f5c93b | ||
|
|
e58499ed09 | ||
|
|
f4c2762881 | ||
|
|
53d366ae53 | ||
|
|
56ac8e154e | ||
|
|
ac1b298989 | ||
|
|
1d8f70c7fd | ||
|
|
40b584dec4 | ||
|
|
eb83786f9a | ||
|
|
fa70ea36c6 | ||
|
|
7c978e1a7e | ||
|
|
44bfa2280c | ||
|
|
a54b066012 | ||
|
|
4b278b7ee3 | ||
|
|
0ed96837bd | ||
|
|
c911f95665 | ||
|
|
f645f3d78d | ||
|
|
1001c809e5 | ||
|
|
1e6ce06fb0 | ||
|
|
acb5fe8d90 | ||
|
|
d9bca7e517 | ||
|
|
b162d0d401 | ||
|
|
3273244c42 | ||
|
|
f579645b88 | ||
|
|
33754d7c71 | ||
|
|
8bd54a04f2 | ||
|
|
492dae8e83 | ||
|
|
d9543de951 | ||
|
|
f1f5374a5c | ||
|
|
0d6b6e1ab9 | ||
|
|
56c97a2b3f | ||
|
|
9063a0328d | ||
|
|
84b28d4c12 | ||
|
|
30cfc507ba | ||
|
|
d84618c116 | ||
|
|
5a55520126 | ||
|
|
8ac5e00c8e | ||
|
|
d46c678b89 | ||
|
|
0b714edcf5 | ||
|
|
45151add64 | ||
|
|
b90830881f | ||
|
|
94301a4a50 | ||
|
|
aa5d15a4dc | ||
|
|
d388e2c350 | ||
|
|
126ff5bca0 | ||
|
|
a347aedfc2 | ||
|
|
e324a95727 | ||
|
|
7f878f40a1 | ||
|
|
7baa9990cb | ||
|
|
5c5c391aa6 | ||
|
|
b966925212 | ||
|
|
efd7049859 | ||
|
|
3ef9446f50 | ||
|
|
68612969c9 | ||
|
|
b4712ff5c6 | ||
|
|
eab050e291 | ||
|
|
a8b090227b | ||
|
|
201e0dfa44 | ||
|
|
666f250b3e | ||
|
|
b2784574bd | ||
|
|
c0b1501f23 | ||
|
|
4de76d8023 | ||
|
|
f08684fb67 | ||
|
|
9e4f9409c5 | ||
|
|
4d3fbed957 | ||
|
|
b338888b58 | ||
|
|
4956c1d874 | ||
|
|
d4fe362d9f | ||
|
|
5d8ff870df | ||
|
|
58c9a60236 | ||
|
|
3e5b141ec7 | ||
|
|
efeb5ecda6 | ||
|
|
624e8ad4de | ||
|
|
764cfd8c4d | ||
|
|
d43ff7f83d | ||
|
|
a39999b344 | ||
|
|
8ee48e1a45 | ||
|
|
bf6affeb97 | ||
|
|
ede9a47ca2 | ||
|
|
2fb290572a | ||
|
|
021b8ddc1a | ||
|
|
05e8da2ff4 | ||
|
|
a8936d92a5 | ||
|
|
c56898e787 | ||
|
|
c483bf9553 | ||
|
|
b5ee17d1ae | ||
|
|
57cef25e27 | ||
|
|
148302c94d | ||
|
|
02c402251d | ||
|
|
c800c5d471 | ||
|
|
477f51cce7 | ||
|
|
60103ebf47 | ||
|
|
21eeebf0f7 | ||
|
|
bb6f6548d5 | ||
|
|
d6c07cf428 | ||
|
|
e98921755d | ||
|
|
60383fede1 | ||
|
|
3bfa70d384 | ||
|
|
60147fd4b6 | ||
|
|
fe51287cc6 | ||
|
|
a44c5f8606 | ||
|
|
d5979b2c3d | ||
|
|
4b95ea9ae2 | ||
|
|
a80fd29297 | ||
|
|
589f35d147 | ||
|
|
c7b5f6ba0f | ||
|
|
0600315e69 | ||
|
|
12005df68b | ||
|
|
600842a679 | ||
|
|
c93d907d46 | ||
|
|
be6d2fb461 | ||
|
|
b2df88203d | ||
|
|
2ed4498181 | ||
|
|
0316b0aa09 | ||
|
|
62d12386e1 | ||
|
|
00ea02a529 | ||
|
|
e5231dfdb6 | ||
|
|
3b1b436ecf | ||
|
|
47180bf650 | ||
|
|
325db98b99 | ||
|
|
ceae543352 | ||
|
|
aef9615476 | ||
|
|
5d3860b91e | ||
|
|
94d15258d8 | ||
|
|
ab03b36b4f | ||
|
|
da0c3c6f14 | ||
|
|
b01e482bc5 | ||
|
|
c560966177 | ||
|
|
66f86c5599 | ||
|
|
5a2bfcb1c1 | ||
|
|
b34f8d68ff | ||
|
|
c98472165b | ||
|
|
4888a07ebb | ||
|
|
a9bf30a609 | ||
|
|
35030601e1 | ||
|
|
66f32b4cd2 | ||
|
|
e6d08dccbb | ||
|
|
8a4f203621 | ||
|
|
69e230a79c | ||
|
|
1a5f4f4f8e | ||
|
|
d0844dd6b5 | ||
|
|
d8e36aa6fa | ||
|
|
6781e4c16d | ||
|
|
fccba0248a | ||
|
|
52b3c1ce17 | ||
|
|
3511cd2699 | ||
|
|
6dddccab7f | ||
|
|
9109d77f98 | ||
|
|
9eec1e8370 | ||
|
|
96481da0a4 | ||
|
|
159437c1ca | ||
|
|
3d34ad59e9 | ||
|
|
4878c6cc7b | ||
|
|
719a3bacd8 | ||
|
|
ce0c58e7c5 | ||
|
|
8a4e44760e | ||
|
|
e810a60848 | ||
|
|
0981339028 | ||
|
|
7746dd8ee9 | ||
|
|
378b78312e | ||
|
|
bd3139754d | ||
|
|
841d91b91d | ||
|
|
895ec53698 | ||
|
|
f6001c8b22 | ||
|
|
d87c362270 | ||
|
|
236e414242 | ||
|
|
5222390b65 | ||
|
|
dc04fdc7d0 | ||
|
|
8de34dfda1 | ||
|
|
997594d7c3 | ||
|
|
c83a777eac | ||
|
|
65fea61667 | ||
|
|
c2777f2ca9 | ||
|
|
dcb42f1970 | ||
|
|
7a2081f0a0 | ||
|
|
acc583f694 | ||
|
|
76409d07a3 | ||
|
|
bb89730781 | ||
|
|
9f6b1f6bcf | ||
|
|
1b8a9677c6 | ||
|
|
79ab13ca4b | ||
|
|
3cc414185a | ||
|
|
fe4a17dce2 | ||
|
|
891551f9dd | ||
|
|
8fb48c6ea9 | ||
|
|
ec25c28061 | ||
|
|
45287f47d2 | ||
|
|
4cc4b6af5b | ||
|
|
09e6760d61 | ||
|
|
ca74a3c3d4 | ||
|
|
fbc3693219 | ||
|
|
fb69fd93ec | ||
|
|
a3a3c3714e | ||
|
|
9294631c6c | ||
|
|
293f0b1eed | ||
|
|
272dcb4d36 | ||
|
|
abb0fa4d37 | ||
|
|
e5212624af | ||
|
|
35772aca03 | ||
|
|
819d89b26e | ||
|
|
958102e43c | ||
|
|
f749d3bfc5 | ||
|
|
d207cf52cf | ||
|
|
420ec5611c | ||
|
|
4bfaf9b7c5 | ||
|
|
48beb861e8 | ||
|
|
9fd2f517d9 | ||
|
|
1b5af63b75 | ||
|
|
dfb847eb3e | ||
|
|
322bd77f7f | ||
|
|
aefe850281 | ||
|
|
57f88f2176 | ||
|
|
8dd566c0da | ||
|
|
e386a45d96 | ||
|
|
8f771166a6 | ||
|
|
5d6a1a25ea | ||
|
|
ca33185f20 | ||
|
|
e921e9e9d2 | ||
|
|
7337068d86 | ||
|
|
9983062522 | ||
|
|
4fd5324064 | ||
|
|
df0e5e354b | ||
|
|
23b5a5d9b7 | ||
|
|
46770ed980 | ||
|
|
6179c5f6b6 | ||
|
|
f758a10575 | ||
|
|
41d1ab4c45 | ||
|
|
032fa26927 | ||
|
|
f7401a287e | ||
|
|
2d1e8ef4f6 | ||
|
|
2802cc36ce | ||
|
|
ea024fe808 | ||
|
|
a6102b7ec9 | ||
|
|
254b75e76b | ||
|
|
33cec6ecc9 | ||
|
|
fbf9bacd05 | ||
|
|
29baf8da9f | ||
|
|
43fea40d08 | ||
|
|
b107eba37c | ||
|
|
abc0f6aebb | ||
|
|
8c6172dcda | ||
|
|
57e78478cd | ||
|
|
f554e91354 | ||
|
|
e0cf1834f1 | ||
|
|
047c4d02ff | ||
|
|
c516cc5283 | ||
|
|
165075ea02 | ||
|
|
2eee7bde24 | ||
|
|
73d4e07b06 | ||
|
|
5dfd8fc499 | ||
|
|
1b36949316 | ||
|
|
5c455bd2c8 | ||
|
|
8d1a4b581b | ||
|
|
085b83cb71 | ||
|
|
65bf63d67e | ||
|
|
d8cc9cab48 | ||
|
|
f7d7a72b7c | ||
|
|
29b1b92113 | ||
|
|
bd83b9689e | ||
|
|
85529b965b |
@@ -0,0 +1,28 @@
|
|||||||
|
name: Go
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: ["master"]
|
||||||
|
pull_request:
|
||||||
|
branches: ["master"]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v4
|
||||||
|
with:
|
||||||
|
go-version: "1.26.2"
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: go build -v ./...
|
||||||
|
|
||||||
|
- name: Vet
|
||||||
|
run: go vet -v ./...
|
||||||
|
|
||||||
|
- name: Test
|
||||||
|
run: go test -v ./...
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
# This workflow will build a golang project
|
||||||
|
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go
|
||||||
|
|
||||||
|
name: Go
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ "master" ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ "master" ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout with LFS
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
lfs: true
|
||||||
|
|
||||||
|
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v4
|
||||||
|
with:
|
||||||
|
go-version: '1.26.2'
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: go build -v ./...
|
||||||
|
|
||||||
|
- name: Vet
|
||||||
|
run: go vet -v ./...
|
||||||
|
|
||||||
|
- name: Test
|
||||||
|
run: go test -v ./...
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
.devres/
|
.devres/
|
||||||
.idea/
|
.idea/
|
||||||
.failed_packets/
|
.failed_packets/
|
||||||
|
.vscode/proto_generator
|
||||||
|
proto_generator
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
[submodule "data/minecraft-data"]
|
|
||||||
path = data/minecraft-data
|
|
||||||
url = git@github.com:PrismarineJS/minecraft-data.git
|
|
||||||
@@ -1,202 +1,61 @@
|
|||||||
|
Copyright © 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||||
|
|
||||||
Apache License
|
Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed.
|
||||||
Version 2.0, January 2004
|
|
||||||
http://www.apache.org/licenses/
|
|
||||||
|
|
||||||
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,
|
“The Library” refers to a covered work governed by this License, other than an Application or a Combined Work as defined below.
|
||||||
and distribution as defined by Sections 1 through 9 of this document.
|
|
||||||
|
|
||||||
"Licensor" shall mean the copyright owner or entity authorized by
|
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.
|
||||||
the copyright owner that is granting the License.
|
|
||||||
|
|
||||||
"Legal Entity" shall mean the union of the acting entity and all
|
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”.
|
||||||
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.
|
|
||||||
|
|
||||||
"You" (or "Your") shall mean an individual or Legal Entity
|
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.
|
||||||
exercising permissions granted by this License.
|
|
||||||
|
|
||||||
"Source" form shall mean the preferred form for making modifications,
|
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.
|
||||||
including but not limited to software source code, documentation
|
1. Exception to Section 3 of the GNU GPL.
|
||||||
source, and configuration files.
|
|
||||||
|
|
||||||
"Object" form shall mean any form resulting from mechanical
|
You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL.
|
||||||
transformation or translation of a Source form, including but
|
2. Conveying Modified Versions.
|
||||||
not limited to compiled object code, generated documentation,
|
|
||||||
and conversions to other media types.
|
|
||||||
|
|
||||||
"Work" shall mean the work of authorship, whether in Source or
|
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:
|
||||||
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).
|
|
||||||
|
|
||||||
"Derivative Works" shall mean any work, whether in Source or Object
|
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
|
||||||
form, that is based on (or derived from) the Work and for which the
|
b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy.
|
||||||
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.
|
|
||||||
|
|
||||||
"Contribution" shall mean any work of authorship, including
|
3. Object Code Incorporating Material from Library Header Files.
|
||||||
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."
|
|
||||||
|
|
||||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
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:
|
||||||
on behalf of whom a Contribution has been received by Licensor and
|
|
||||||
subsequently incorporated within the Work.
|
|
||||||
|
|
||||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
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.
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
b) Accompany the object code with a copy of the GNU GPL and this license document.
|
||||||
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.
|
|
||||||
|
|
||||||
3. Grant of Patent License. Subject to the terms and conditions of
|
4. Combined Works.
|
||||||
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. Redistribution. You may reproduce and distribute copies of the
|
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:
|
||||||
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:
|
|
||||||
|
|
||||||
(a) You must give any other recipients of the Work or
|
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.
|
||||||
Derivative Works a copy of this License; and
|
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
|
5. Combined Libraries.
|
||||||
stating that You changed the files; and
|
|
||||||
|
|
||||||
(c) You must retain, in the Source form of any Derivative Works
|
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:
|
||||||
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
|
|
||||||
|
|
||||||
(d) If the Work includes a "NOTICE" text file as part of its
|
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.
|
||||||
distribution, then any Derivative Works that You distribute must
|
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.
|
||||||
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.
|
|
||||||
|
|
||||||
You may add Your own copyright statement to Your modifications and
|
6. Revised Versions of the GNU Lesser General Public License.
|
||||||
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.
|
|
||||||
|
|
||||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
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.
|
||||||
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.
|
|
||||||
|
|
||||||
6. Trademarks. This License does not grant permission to use the trade
|
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.
|
||||||
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.
|
|
||||||
|
|
||||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
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.
|
||||||
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.
|
|
||||||
@@ -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)
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
package client
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
|
|
||||||
"github.com/admin-else/strom/proto"
|
|
||||||
"github.com/admin-else/strom/proto_base"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Connect(addr string) (ret *proto.Conn, err error) {
|
|
||||||
ret = &proto.Conn{}
|
|
||||||
err = ret.SetVersion("1.21.8")
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ret.SetState(proto_base.Handshaking)
|
|
||||||
ret.CompressionThreshold = -1
|
|
||||||
ret.Actor = proto_base.Client
|
|
||||||
ret.Conn, err = net.Dial("tcp", addr)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ret.R = ret.Conn
|
|
||||||
ret.W = ret.Conn
|
|
||||||
return
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
package client
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
func LookupMinecraftSRV(host string, port uint16) (hostRet string, portRet uint16) {
|
|
||||||
hostRet = host // in case of error
|
|
||||||
portRet = port
|
|
||||||
if port != 25565 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
_, srvs, err := net.LookupSRV("minecraft", "tcp", host)
|
|
||||||
if err != nil || len(srvs) == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
target := strings.TrimSuffix(srvs[0].Target, ".")
|
|
||||||
hostRet = target
|
|
||||||
portRet = srvs[0].Port
|
|
||||||
return
|
|
||||||
}
|
|
||||||
@@ -1,172 +0,0 @@
|
|||||||
package modules
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/rand"
|
|
||||||
"crypto/rsa"
|
|
||||||
"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"
|
|
||||||
"github.com/admin-else/strom/proto_base"
|
|
||||||
"github.com/admin-else/strom/text"
|
|
||||||
|
|
||||||
"github.com/admin-else/strom/proto_generated/v1_21_8"
|
|
||||||
)
|
|
||||||
|
|
||||||
type LoginClient struct {
|
|
||||||
*proto.Conn
|
|
||||||
Account *api.Account
|
|
||||||
ServerHost string
|
|
||||||
ServerPort uint16
|
|
||||||
|
|
||||||
GivenAccount *v1_21_8.LoginToClientPacketSuccess
|
|
||||||
}
|
|
||||||
|
|
||||||
type KickedDuringLoginErr struct {
|
|
||||||
text.Component
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
s.SetState(proto_base.Login)
|
|
||||||
err = s.Send(&v1_21_8.LoginToServerPacketLoginStart{Username: s.Account.Name, PlayerUUID: s.Account.Uuid})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *LoginClient) OnCompress(compress *v1_21_8.LoginToClientPacketCompress) (err error) {
|
|
||||||
s.CompressionThreshold = compress.Threshold
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *LoginClient) OnEncrypt(packet *v1_21_8.LoginToClientPacketEncryptionBegin) (err error) {
|
|
||||||
sharedSecret := make([]byte, 16)
|
|
||||||
_, _ = rand.Read(sharedSecret) //never fails
|
|
||||||
|
|
||||||
if packet.ShouldAuthenticate {
|
|
||||||
if s.Account.Ygg == "" {
|
|
||||||
err = fmt.Errorf("the account has no token so we cant join online servers")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
serverId := crypto.AuthDigest([]byte(packet.ServerId), sharedSecret, packet.PublicKey)
|
|
||||||
err = s.Account.JoinServer(serverId)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pubAny, err := x509.ParsePKIXPublicKey(packet.PublicKey)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
pub, ok := pubAny.(*rsa.PublicKey)
|
|
||||||
if !ok {
|
|
||||||
err = fmt.Errorf("public key is not rsa")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
verifyTokenEnc, err := rsa.EncryptPKCS1v15(rand.Reader, pub, packet.VerifyToken)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
sharedSecretEnc, err := rsa.EncryptPKCS1v15(rand.Reader, pub, sharedSecret)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = s.Send(&v1_21_8.LoginToServerPacketEncryptionBegin{SharedSecret: sharedSecretEnc, VerifyToken: verifyTokenEnc})
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = s.SetSecret(sharedSecret)
|
|
||||||
return
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *LoginClient) OnDisconnect(packet *v1_21_8.LoginToClientPacketDisconnect) (err error) {
|
|
||||||
var reason text.Component
|
|
||||||
err = json.Unmarshal([]byte(packet.Reason), &reason)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = KickedDuringLoginErr{reason}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *LoginClient) OnSuccess(success *v1_21_8.LoginToClientPacketSuccess) (err error) {
|
|
||||||
s.GivenAccount = success
|
|
||||||
err = s.Send(&v1_21_8.LoginToServerPacketLoginAcknowledged{})
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
s.SetState(proto_base.Configuration)
|
|
||||||
err = event.HandlerDoneErr{}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func Login(c *proto.Conn, account *api.Account) (err error) {
|
|
||||||
err = c.Start(&LoginClient{
|
|
||||||
Conn: c,
|
|
||||||
Account: account,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
err = errors.Join(err, errors.New("failed to login"))
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func ConnectAndLogin(connectTo string, account *api.Account) (c *proto.Conn, err error) {
|
|
||||||
c, err = client.Connect(connectTo)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = c.Start(&LoginClient{
|
|
||||||
Conn: c,
|
|
||||||
Account: account,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
err = errors.Join(errors.New("failed to login"), err)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"flag"
|
||||||
|
"log/slog"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/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 {
|
||||||
|
slog.Info("account", "name", a.Name, "uuid", a.Uuid)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = json.NewEncoder(os.Stdout).Encode(data)
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package cmd_util
|
||||||
|
|
||||||
|
import "git.anygate.cloud/anygatecloud/strom/mc/api"
|
||||||
|
|
||||||
|
func Account(s string) (a *api.Account, err error) {
|
||||||
|
if len(s) > 16 {
|
||||||
|
return api.NewAccountFromYGG(s)
|
||||||
|
}
|
||||||
|
a = api.NewOfflineAccount(s)
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
package data
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"maps"
|
||||||
|
"os"
|
||||||
|
"slices"
|
||||||
|
|
||||||
|
mcdata "git.anygate.cloud/anygatecloud/strom/mc/data"
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/proto_generated"
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// strom data 1.21.11 blocks id 0 name => air
|
||||||
|
// {
|
||||||
|
// "id": 0,
|
||||||
|
// "name": "air",
|
||||||
|
// "displayName": "Air",
|
||||||
|
// "hardness": 0.0,
|
||||||
|
// "resistance": 0.0,
|
||||||
|
// "stackSize": 64,
|
||||||
|
// "diggable": false,
|
||||||
|
// "material": "default",
|
||||||
|
// "transparent": true,
|
||||||
|
// "emitLight": 0,
|
||||||
|
// "filterLight": 0,
|
||||||
|
// "defaultState": 0,
|
||||||
|
// "minStateId": 0,
|
||||||
|
// "maxStateId": 0,
|
||||||
|
// "states": [],
|
||||||
|
// "drops": [],
|
||||||
|
// "boundingBox": "empty"
|
||||||
|
// },
|
||||||
|
|
||||||
|
func Run(args []string) (err error) {
|
||||||
|
var dataValues any
|
||||||
|
switch len(args) {
|
||||||
|
case 0: // print available versions
|
||||||
|
dataValues = proto_generated.SupportedVersions
|
||||||
|
case 1: // print available data for version
|
||||||
|
dataValues = slices.Collect(maps.Keys(mcdata.Paths.Data[args[0]]))
|
||||||
|
case 2:
|
||||||
|
err = mcdata.LoadVersionedJson(args[0], args[1], &dataValues)
|
||||||
|
case 3:
|
||||||
|
err = mcdata.LoadVersionedJson(args[0], args[1], &dataValues)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var dataSlices []any
|
||||||
|
dataSlices, err = util.AssertTypeError[[]any](dataValues)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var result = make([]any, len(dataSlices))
|
||||||
|
for _, v := range dataSlices {
|
||||||
|
var dataMap map[string]any
|
||||||
|
dataMap, err = util.AssertTypeError[map[string]any](v)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var vToAppend any
|
||||||
|
vToAppend, err = util.MapGetError(dataMap, args[2])
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
result = append(result, vToAppend)
|
||||||
|
}
|
||||||
|
dataValues = result
|
||||||
|
case 4, 5:
|
||||||
|
err = mcdata.LoadVersionedJson(args[0], args[1], &dataValues)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var dataSlices []any
|
||||||
|
dataSlices, err = util.AssertTypeError[[]any](dataValues)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
found := false
|
||||||
|
for _, v := range dataSlices {
|
||||||
|
var dataMap map[string]any
|
||||||
|
dataMap, err = util.AssertTypeError[map[string]any](v)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if v2, ok := dataMap[args[2]]; ok {
|
||||||
|
if args[3] != fmt.Sprint(v2) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
dataValues = dataMap
|
||||||
|
found = true
|
||||||
|
if len(args) == 5 {
|
||||||
|
dataValues, err = util.MapGetError(dataMap, args[4])
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
err = fmt.Errorf("not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
err = fmt.Errorf("too many arguments")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
e := json.NewEncoder(os.Stdout)
|
||||||
|
e.SetIndent("", " ")
|
||||||
|
err = e.Encode(dataValues)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
package extract_mca
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/level/anvil"
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/nbt"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
cmd = flag.NewFlagSet("print-nbt", flag.ContinueOnError)
|
||||||
|
FileFlag = cmd.String("file", "", "The MCA file to extract")
|
||||||
|
IndexXFlag = cmd.Int("x", 0, "The x chunk coordinate")
|
||||||
|
IndexZFlag = cmd.Int("z", 0, "The z chunk coordinate")
|
||||||
|
ToFlag = cmd.String("to", "mca-chunk.nbt", "The output file")
|
||||||
|
)
|
||||||
|
|
||||||
|
func Run(args []string) (err error) {
|
||||||
|
err = cmd.Parse(args)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if *FileFlag == "" {
|
||||||
|
return fmt.Errorf("no file specified")
|
||||||
|
}
|
||||||
|
f, err := os.Open(*FileFlag)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
mca, err := anvil.ReadChunkHeader(f)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t, err := mca.TimeAt(f, int32(*IndexXFlag), int32(*IndexZFlag))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
slog.Info("Chunk last written", "time", t)
|
||||||
|
n, err := mca.ChunkAt(f, int32(*IndexXFlag), int32(*IndexZFlag))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = nbt.WriteUnstructuredFilePath(*ToFlag, n)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"maps"
|
||||||
|
"os"
|
||||||
|
"runtime/debug"
|
||||||
|
"slices"
|
||||||
|
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/cmd/strom/api"
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/cmd/strom/data"
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/cmd/strom/extract_mca"
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/cmd/strom/mca_to_tmcpr"
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/cmd/strom/messenger"
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/cmd/strom/offline_uuid"
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/cmd/strom/packet_info"
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/cmd/strom/packet_inspector"
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/cmd/strom/print_nbt"
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/cmd/strom/print_replay"
|
||||||
|
// "git.anygate.cloud/anygatecloud/strom/cmd/strom/raw_capture" // removed: use tcpflow instead for raw TCP capture
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/cmd/strom/raw_to_tmcpr"
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/cmd/strom/serve_tmcpr"
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/cmd/strom/serve_world"
|
||||||
|
"git.anygate.cloud/anygatecloud/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")
|
||||||
|
|
||||||
|
func ToolVersion() string {
|
||||||
|
info, ok := debug.ReadBuildInfo()
|
||||||
|
if !ok {
|
||||||
|
return "devel"
|
||||||
|
}
|
||||||
|
return info.Main.Version
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToolSum() string {
|
||||||
|
info, ok := debug.ReadBuildInfo()
|
||||||
|
if !ok {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return info.Main.Sum
|
||||||
|
}
|
||||||
|
|
||||||
|
func Version(args []string) error {
|
||||||
|
fmt.Println("strom cli ", ToolVersion(), " (", ToolSum(), ")")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var subcommands = map[string]func(args []string) error{
|
||||||
|
"offline-uuid": offline_uuid.Run,
|
||||||
|
"packet-info": packet_info.Run,
|
||||||
|
"print-nbt": print_nbt.Run,
|
||||||
|
"status": status.Run,
|
||||||
|
"packet-spy": packet_inspector.Run,
|
||||||
|
"api": api.Run,
|
||||||
|
"version": Version,
|
||||||
|
"extract-mca": extract_mca.Run,
|
||||||
|
"data": data.Run,
|
||||||
|
"print-replay": print_replay.Run,
|
||||||
|
// "raw-capture": raw_capture.Run, // removed: use tcpflow instead for raw TCP capture, then raw-to-tmcpr
|
||||||
|
"raw-to-tmcpr": raw_to_tmcpr.Run,
|
||||||
|
"serve-world": serve_world.Run,
|
||||||
|
"mca-to-tmcpr": mca_to_tmcpr.Run,
|
||||||
|
"serve-tmcpr": serve_tmcpr.Run,
|
||||||
|
"messenger": messenger.Run,
|
||||||
|
}
|
||||||
|
|
||||||
|
func Help(args []string) (err error) {
|
||||||
|
subcommandNames := slices.Collect(maps.Keys(subcommands))
|
||||||
|
slices.Sort(subcommandNames)
|
||||||
|
fmt.Println("Available subcommands", subcommandNames)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func mainE() (err error) {
|
||||||
|
subcommands["help"] = Help
|
||||||
|
args := os.Args
|
||||||
|
//args = []string{"TEST", "messenger", "-addr", "localhost"}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,212 @@
|
|||||||
|
package mca_to_tmcpr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/level"
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/level/anvil"
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/nbt"
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/proto/replay"
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/proto_generated/v1_21_11"
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
cmd = flag.NewFlagSet("mca-to-tmcpr", flag.ContinueOnError)
|
||||||
|
worldPath = cmd.String("world", "", "Path to world directory")
|
||||||
|
output = cmd.String("output", "", "Output .tmcpr file path")
|
||||||
|
radius = cmd.Int("radius", 14, "Chunk radius around spawn")
|
||||||
|
spawnX = cmd.Float64("spawn-x", 0.5, "Spawn X coordinate")
|
||||||
|
spawnY = cmd.Float64("spawn-y", 100, "Spawn Y coordinate")
|
||||||
|
spawnZ = cmd.Float64("spawn-z", 0.5, "Spawn Z coordinate")
|
||||||
|
)
|
||||||
|
|
||||||
|
var FullLight [][]uint8
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
light := make([]uint8, 2048)
|
||||||
|
for i := range light {
|
||||||
|
light[i] = 0xFF
|
||||||
|
}
|
||||||
|
sectionsPerChunk := 384 / 16
|
||||||
|
FullLight = make([][]uint8, sectionsPerChunk)
|
||||||
|
for i := range sectionsPerChunk {
|
||||||
|
FullLight[i] = light
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetChunkFromMca(x, z int32, version string) (b []byte, err error) {
|
||||||
|
xmca := util.FloorDiv(x, anvil.ChunksWidthRegionFile)
|
||||||
|
zmca := util.FloorDiv(z, anvil.ChunksWidthRegionFile)
|
||||||
|
x = x % anvil.ChunksWidthRegionFile
|
||||||
|
if x < 0 {
|
||||||
|
x += anvil.ChunksWidthRegionFile
|
||||||
|
}
|
||||||
|
z = z % anvil.ChunksWidthRegionFile
|
||||||
|
if z < 0 {
|
||||||
|
z += anvil.ChunksWidthRegionFile
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := os.Open(filepath.Join(*worldPath, "region", fmt.Sprintf("r.%v.%v.mca", xmca, zmca)))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
mca, err := anvil.ReadChunkHeader(f)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var n *nbt.Tag
|
||||||
|
n, err = mca.ChunkAt(f, x, z)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if n == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var chunk anvil.Chunk
|
||||||
|
err = nbt.Format.Decode(n.Value, &chunk)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var packetChunk *level.Chunk
|
||||||
|
packetChunk, err = chunk.ToStorage(version)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
buffer := bytes.NewBuffer(nil)
|
||||||
|
err = packetChunk.WriteChunkData(buffer)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
b = buffer.Bytes()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func Run(args []string) error {
|
||||||
|
slog.SetLogLoggerLevel(slog.LevelDebug)
|
||||||
|
|
||||||
|
err := cmd.Parse(args)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if *worldPath == "" {
|
||||||
|
slog.Error("no world path specified")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if *output == "" {
|
||||||
|
slog.Error("no output path specified")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
outFile, err := os.Create(*output)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("failed to create output file", "error", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer outFile.Close()
|
||||||
|
|
||||||
|
w := replay.NewWriter(outFile)
|
||||||
|
|
||||||
|
err = w.WritePacket(&v1_21_11.PlayToClientPacketLogin{
|
||||||
|
EntityId: 73,
|
||||||
|
IsHardcore: false,
|
||||||
|
WorldNames: []string{"minecraft:overworld", "minecraft:the_end", "minecraft:the_nether"},
|
||||||
|
MaxPlayers: 100,
|
||||||
|
ViewDistance: 10,
|
||||||
|
SimulationDistance: 10,
|
||||||
|
ReducedDebugInfo: false,
|
||||||
|
EnableRespawnScreen: true,
|
||||||
|
DoLimitedCrafting: false,
|
||||||
|
WorldState: v1_21_11.PlayToClientSpawnInfo{
|
||||||
|
Dimension: 0,
|
||||||
|
Name: "minecraft:overworld",
|
||||||
|
HashedSeed: -8365436745629239539,
|
||||||
|
Gamemode: "survival",
|
||||||
|
PreviousGamemode: 0xff,
|
||||||
|
IsDebug: false,
|
||||||
|
IsFlat: false,
|
||||||
|
Death: nil,
|
||||||
|
PortalCooldown: 0,
|
||||||
|
SeaLevel: 63,
|
||||||
|
},
|
||||||
|
EnforcesSecureChat: false,
|
||||||
|
}, 0)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("failed to write login packet", "error", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = w.WritePacket(&v1_21_11.PlayToClientPacketPosition{
|
||||||
|
TeleportId: 0,
|
||||||
|
X: *spawnX,
|
||||||
|
Y: *spawnY,
|
||||||
|
Z: *spawnZ,
|
||||||
|
Dx: 0,
|
||||||
|
Dy: 0,
|
||||||
|
Dz: 0,
|
||||||
|
Yaw: 0,
|
||||||
|
Pitch: 0,
|
||||||
|
Flags: v1_21_11.PlayToClientPositionUpdateRelatives{},
|
||||||
|
}, 0)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("failed to write position packet", "error", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
chunkX := int32(*spawnX) >> 4
|
||||||
|
chunkZ := int32(*spawnZ) >> 4
|
||||||
|
|
||||||
|
err = w.WritePacket(&v1_21_11.PlayToClientPacketUpdateViewPosition{
|
||||||
|
ChunkX: chunkX,
|
||||||
|
ChunkZ: chunkZ,
|
||||||
|
}, 0)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("failed to write view position packet", "error", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = w.WritePacket(&v1_21_11.PlayToClientPacketGameStateChange{
|
||||||
|
Reason: "level_chunks_load_start",
|
||||||
|
}, 0)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("failed to write game state packet", "error", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
radius32 := int32(*radius)
|
||||||
|
|
||||||
|
for x := -radius32; x < radius32; x++ {
|
||||||
|
for z := -radius32; z < radius32; z++ {
|
||||||
|
var chunkData []byte
|
||||||
|
chunkData, err = GetChunkFromMca(x, z, "1.21.11")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if chunkData == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
OLDpacket := &v1_21_11.PlayToClientPacketMapChunk{}
|
||||||
|
OLDpacket.X = x
|
||||||
|
OLDpacket.Z = z
|
||||||
|
OLDpacket.ChunkData = v1_21_11.ByteArray{Val: chunkData}
|
||||||
|
OLDpacket.BlockLight = FullLight
|
||||||
|
OLDpacket.BlockLightMask = []int64{(1 << 24) - 1}
|
||||||
|
err = w.WritePacket(OLDpacket, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
slog.Info("conversion complete", "output", *output)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,164 @@
|
|||||||
|
package messenger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/cmd/strom/cmd_util"
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/bot/chat"
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/bot/keepalive"
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/bot/world"
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/client"
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/data"
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/event"
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/proto"
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/proto_generated/v1_21_11"
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/proto_generated/v1_8"
|
||||||
|
)
|
||||||
|
|
||||||
|
func stror(a, b string) string {
|
||||||
|
if a != "" {
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
cmd = flag.NewFlagSet("status", flag.ContinueOnError)
|
||||||
|
connectToFlag = cmd.String("addr", "", "server address to connect to")
|
||||||
|
accFlag = cmd.String("acc", stror(os.Getenv("STROM_ACC"), "strom_messenger"), "the name to use if longer will be treated as ygg token")
|
||||||
|
versionFlag = cmd.String("version", "26.2", "minecraft version to use (e.g. 1.8, 1.21.11)")
|
||||||
|
)
|
||||||
|
|
||||||
|
type Messenger struct {
|
||||||
|
*proto.Conn
|
||||||
|
chat *chat.Module
|
||||||
|
world *world.Module
|
||||||
|
legacy bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Messenger) OnChat(e *v1_21_11.PlayToClientPacketPlayerChat) (err error) {
|
||||||
|
m.Log.Info("chat", "author", e.SenderUuid, "m", e.PlainMessage)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Messenger) OnChatUnsigned(e *v1_21_11.PlayToClientPacketProfilelessChat) (err error) {
|
||||||
|
m.Log.Info("chatP", "author", e.Name, "m", e.Message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Messenger) OnChatSystem(e *v1_21_11.PlayToClientPacketSystemChat) (err error) {
|
||||||
|
m.Log.Info("chatS", "m", e.Content)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Messenger) OnChatLegacy(e *v1_8.PlayToClientPacketChat) (err error) {
|
||||||
|
m.Log.Info("chat", "m", e.Message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Messenger) OnStdin(e event.Stdin) (err error) {
|
||||||
|
input := strings.TrimSpace(e.Val)
|
||||||
|
if strings.HasPrefix(input, ">say ") {
|
||||||
|
return m.sendChat(strings.TrimSpace(strings.TrimPrefix(input, ">say")))
|
||||||
|
}
|
||||||
|
if !m.legacy && strings.HasPrefix(input, ">getblock ") {
|
||||||
|
return m.handleGetBlock(strings.TrimSpace(strings.TrimPrefix(input, ">getblock")))
|
||||||
|
}
|
||||||
|
return m.sendChat(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Messenger) sendChat(message string) (err error) {
|
||||||
|
if m.legacy {
|
||||||
|
return m.Send(&v1_8.PlayToServerPacketChat{Message: message})
|
||||||
|
}
|
||||||
|
return m.chat.SendMessage(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Messenger) handleGetBlock(args string) (err error) {
|
||||||
|
fields := strings.Fields(args)
|
||||||
|
if len(fields) != 3 {
|
||||||
|
m.Log.Info("getblock", "error", "expected >getblock <x> <y> <z>")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
coords := make([]int64, 3)
|
||||||
|
for i, f := range fields {
|
||||||
|
coords[i], err = strconv.ParseInt(f, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
m.Log.Info("getblock", "error", fmt.Sprintf("bad coordinate %q", f))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stateId, err := m.world.GetBlock(int32(coords[0]), int32(coords[1]), int32(coords[2]))
|
||||||
|
if err != nil {
|
||||||
|
m.Log.Info("getblock", "error", err.Error())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
block, ok := data.LookupBlockByStateId(m.Version, stateId)
|
||||||
|
if !ok {
|
||||||
|
m.Log.Info("getblock", "stateId", stateId, "name", "unknown")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
m.Log.Info("getblock", "x", coords[0], "y", coords[1], "z", coords[2], "name", block.Name, "stateId", stateId)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Run(args []string) (err error) {
|
||||||
|
err = cmd.Parse(args)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
acc, err := cmd_util.Account(*accFlag)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c, err := client.Login(*connectToFlag, acc, client.WithVersion(*versionFlag))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer c.Close()
|
||||||
|
|
||||||
|
isLegacy := c.ProtocolVersion < 764
|
||||||
|
|
||||||
|
var chatMod *chat.Module
|
||||||
|
if !isLegacy {
|
||||||
|
chatMod, err = chat.Start(c, acc)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m := &Messenger{
|
||||||
|
Conn: c,
|
||||||
|
chat: chatMod,
|
||||||
|
legacy: isLegacy,
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isLegacy {
|
||||||
|
w := world.NewWorld(c.Version, -64, 384)
|
||||||
|
m.world = world.Start(c, w)
|
||||||
|
}
|
||||||
|
|
||||||
|
event.StartListingStdin(m.Loop)
|
||||||
|
m.Register(m.OnStdin)
|
||||||
|
|
||||||
|
if isLegacy {
|
||||||
|
m.Register(m.OnChatLegacy)
|
||||||
|
} else {
|
||||||
|
m.RegisterUntilLatest(m.OnChat, m.OnChatUnsigned, m.OnChatSystem)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isLegacy {
|
||||||
|
keepalive.Start(m.Conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
return m.StartConn()
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package offline_uuid
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/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
|
||||||
|
}
|
||||||
@@ -0,0 +1,169 @@
|
|||||||
|
package packet_info
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/data"
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/proto"
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/proto_base"
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/proto_generated"
|
||||||
|
)
|
||||||
|
|
||||||
|
var cmd = flag.NewFlagSet("packet-info", flag.ContinueOnError)
|
||||||
|
var versionFlag = cmd.String("version", "", "protocol version or minecraft version name")
|
||||||
|
var stateFlag = cmd.String("state", "", "handshaking, status, login, configuration, play")
|
||||||
|
var directionFlag = cmd.String("direction", "", "toserver, toclient")
|
||||||
|
var idFlag = cmd.Int("id", -1, "packet id")
|
||||||
|
var nameFlag = cmd.String("name", "", "packet name")
|
||||||
|
|
||||||
|
func Run(args []string) (err error) {
|
||||||
|
err = cmd.Parse(args)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var version int32
|
||||||
|
if *versionFlag != "" {
|
||||||
|
v, err := data.LookUpVersionByName(*versionFlag)
|
||||||
|
if err != nil {
|
||||||
|
v2, err := parseVersion(*versionFlag)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid version: %s", *versionFlag)
|
||||||
|
}
|
||||||
|
v, err = data.LookUpVersionByProtocolVersion(v2)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
version = v.Version
|
||||||
|
}
|
||||||
|
|
||||||
|
var state proto_base.State
|
||||||
|
if *stateFlag != "" {
|
||||||
|
state, err = parseState(*stateFlag)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var direction proto_base.Direction
|
||||||
|
if *directionFlag != "" {
|
||||||
|
direction, err = parseDirection(*directionFlag)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if *idFlag != -1 && *nameFlag != "" {
|
||||||
|
err = fmt.Errorf("cannot specify both id and name")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if *idFlag != -1 {
|
||||||
|
if *versionFlag == "" || *stateFlag == "" || *directionFlag == "" {
|
||||||
|
err = fmt.Errorf("version, state, and direction are required when looking up by id")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p, ok := proto.LookUpTypeByPacketInfo(direction, state, int32(*idFlag), version)
|
||||||
|
if !ok {
|
||||||
|
err = fmt.Errorf("packet not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
printPacketInfo(p)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if *nameFlag != "" {
|
||||||
|
if *versionFlag == "" {
|
||||||
|
err = fmt.Errorf("version is required when looking up by name")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if state != 0 || direction != 0 {
|
||||||
|
p, ok := proto.LookupPacketInfoByNameProtocolVersionStateAndDirection(*nameFlag, version, state, direction)
|
||||||
|
if !ok {
|
||||||
|
err = fmt.Errorf("packet not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
printPacketInfo(p)
|
||||||
|
} else {
|
||||||
|
var found bool
|
||||||
|
for _, p := range proto_generated.Packets {
|
||||||
|
if p.Name == *nameFlag && p.ProtocolVersion == version {
|
||||||
|
printPacketInfo(p)
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
err = fmt.Errorf("packet not found")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = fmt.Errorf("must specify either -id or -name")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseState(s string) (proto_base.State, error) {
|
||||||
|
switch s {
|
||||||
|
case "handshaking":
|
||||||
|
return proto_base.Handshaking, nil
|
||||||
|
case "status":
|
||||||
|
return proto_base.Status, nil
|
||||||
|
case "login":
|
||||||
|
return proto_base.Login, nil
|
||||||
|
case "configuration":
|
||||||
|
return proto_base.Configuration, nil
|
||||||
|
case "play":
|
||||||
|
return proto_base.Play, nil
|
||||||
|
default:
|
||||||
|
return 0, fmt.Errorf("unknown state: %s", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseDirection(s string) (proto_base.Direction, error) {
|
||||||
|
switch s {
|
||||||
|
case "toserver":
|
||||||
|
return proto_base.ToServer, nil
|
||||||
|
case "toclient":
|
||||||
|
return proto_base.ToClient, nil
|
||||||
|
default:
|
||||||
|
return 0, fmt.Errorf("unknown direction: %s", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseVersion(val string) (int32, error) {
|
||||||
|
v, err := strconv.ParseInt(val, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return int32(v), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func printPacketInfo(p proto_base.PacketInfo) {
|
||||||
|
v, _ := data.LookUpVersionByProtocolVersion(p.ProtocolVersion)
|
||||||
|
fmt.Printf("Name: %s\n", p.Name)
|
||||||
|
fmt.Printf("ID: %d\n", p.PacketId)
|
||||||
|
fmt.Printf("State: %s\n", stateToString(p.State))
|
||||||
|
fmt.Printf("Direction: %s\n", p.Direction)
|
||||||
|
fmt.Printf("Version: %d (%s)\n", p.ProtocolVersion, v.MinecraftVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
func stateToString(s proto_base.State) string {
|
||||||
|
switch s {
|
||||||
|
case proto_base.Handshaking:
|
||||||
|
return "handshaking"
|
||||||
|
case proto_base.Status:
|
||||||
|
return "status"
|
||||||
|
case proto_base.Login:
|
||||||
|
return "login"
|
||||||
|
case proto_base.Configuration:
|
||||||
|
return "configuration"
|
||||||
|
case proto_base.Play:
|
||||||
|
return "play"
|
||||||
|
default:
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,204 @@
|
|||||||
|
package packet_inspector
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"flag"
|
||||||
|
"log/slog"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/api"
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/client"
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/event"
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/nbt"
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/proto"
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/proto_base"
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/proto_generated/v1_21_11"
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/server"
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/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")
|
||||||
|
var TrueForward = Cmd.Bool("true-forward", false, "forward packets without modification -name and custom server status wont work")
|
||||||
|
var Version = Cmd.String("protocol-version", "", "protocol version to use; empty for auto")
|
||||||
|
|
||||||
|
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)
|
||||||
|
p.Servee.RegisterCriticalUntilLatest(p.OnFinishLogin)
|
||||||
|
p.Servee.RegisterCriticalUntilLatest(p.OnHandshake)
|
||||||
|
//p.Servee.RegisterCriticalUntilLatest(p.OnCompress)
|
||||||
|
p.Client.RegisterCriticalUntilLatest(p.OnCompress)
|
||||||
|
|
||||||
|
errChan := make(chan error)
|
||||||
|
go func() {
|
||||||
|
errChan <- p.Client.StartConn()
|
||||||
|
}()
|
||||||
|
go func() {
|
||||||
|
errChan <- p.Servee.StartConn()
|
||||||
|
}()
|
||||||
|
err = <-errChan
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type PacketLogEvent struct {
|
||||||
|
Time time.Time
|
||||||
|
Packet proto_base.EncodeDecodeAble
|
||||||
|
Info PacketInfoNoTypeJson
|
||||||
|
ClientAddr string
|
||||||
|
}
|
||||||
|
|
||||||
|
type PacketInfoNoTypeJson struct {
|
||||||
|
Type proto_base.EncodeDecodeAble `json:"-"`
|
||||||
|
Name string
|
||||||
|
Direction proto_base.Direction
|
||||||
|
State proto_base.State
|
||||||
|
PacketId int32
|
||||||
|
ProtocolVersion int32
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
jsonBytes, err := json.Marshal(PacketLogEvent{
|
||||||
|
Time: time.Now(),
|
||||||
|
Packet: packet,
|
||||||
|
ClientAddr: p.Client.RemoteAddr().String(),
|
||||||
|
Info: PacketInfoNoTypeJson(packetInfo),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// :pray:
|
||||||
|
_, _ = os.Stdout.Write(jsonBytes)
|
||||||
|
_, _ = os.Stdout.Write([]byte("\n"))
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Proxy) OnFinishLogin(packet *v1_21_11.LoginToServerPacketLoginAcknowledged) (err error) {
|
||||||
|
err = p.Client.Send(packet)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.Servee.SetState(proto_base.Configuration)
|
||||||
|
p.Client.SetState(proto_base.Configuration)
|
||||||
|
err = event.DontForwardErr
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Proxy) OnHandshake(packet *v1_21_11.HandshakingToServerPacketSetProtocol) (err error) {
|
||||||
|
err = p.Client.Send(packet)
|
||||||
|
state := proto_base.State(packet.NextState)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.Servee.SetState(state)
|
||||||
|
p.Client.SetState(state)
|
||||||
|
err = event.DontForwardErr
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Proxy) OnCompress(packet *v1_21_11.LoginToClientPacketCompress) (err error) {
|
||||||
|
err = p.Servee.Send(packet)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.Servee.SetCompressionThreshold(packet.Threshold)
|
||||||
|
p.Client.SetCompressionThreshold(packet.Threshold)
|
||||||
|
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.Info("Starting packet inspector", "listen", *ListenAddr)
|
||||||
|
err = server.StartServerWithOnConn(*ListenAddr, func(serveeConn *proto.Conn) (err error) {
|
||||||
|
p := &Proxy{Servee: serveeConn, Client: nil}
|
||||||
|
var acc = api.NewOfflineAccount(*OfflineNameFlag)
|
||||||
|
if !*TrueForward {
|
||||||
|
if *Token != "" {
|
||||||
|
acc, err = api.NewAccountFromYGG(*Token)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_, err = server.ServeLogin(serveeConn, server.WithOtherAccount(acc), server.WithStatus(StatusResponse), server.WithoutOnlineMode(), server.WithoutEncryption())
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*TargetAddr, _, _, err = client.DoDNSChecked(nil, *TargetAddr, nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c, err := client.Connect(nil, *TargetAddr, *Version)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer c.Close()
|
||||||
|
if !*TrueForward {
|
||||||
|
err = client.LoginRaw(c, acc)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p.Client = c
|
||||||
|
p.Client.AlwaysDecode = true
|
||||||
|
p.Servee.AlwaysDecode = true
|
||||||
|
err = p.Start()
|
||||||
|
return
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
package print_nbt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/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
|
||||||
|
}
|
||||||
|
if *FileFlag == "" {
|
||||||
|
return fmt.Errorf("no file specified")
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package failed_packets_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
generated "git.anygate.cloud/anygatecloud/strom/mc/proto_generated/%v"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Error: %v
|
||||||
|
func TestFailedPacket%10X(t *testing.T) {
|
||||||
|
p := &generated.%v{}
|
||||||
|
b := bytes.NewReader(%#v)
|
||||||
|
err := p.Decode(b)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("expected decode error but got nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,215 @@
|
|||||||
|
package print_replay
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/sha256"
|
||||||
|
_ "embed"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"go/format"
|
||||||
|
"io"
|
||||||
|
"log/slog"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/proto"
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/proto_base"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
cmd = flag.NewFlagSet("print-replay", flag.ContinueOnError)
|
||||||
|
FileFlag = cmd.String("file", "", "The tmcpr file to print")
|
||||||
|
ProtocolVersion = cmd.Int("protocol", -1, "The protocol version")
|
||||||
|
CreateTestsPath = cmd.String("create-tests", "", "")
|
||||||
|
StateFlag = cmd.String("state", "login", "Initial state: handshake, login, config, status, play")
|
||||||
|
DirectionFlag = cmd.String("direction", "clientbound", "Packet direction: clientbound (ToClient) or serverbound (ToServer)")
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed failed_packet.go.tmpl
|
||||||
|
var TestSrcF string
|
||||||
|
|
||||||
|
func SaveUnCodeAbleAsTest(packet *proto.UnCodablePacket) (err error) {
|
||||||
|
if packet.Info.Type == nil {
|
||||||
|
slog.Warn("skipping test for unknown packet id", "packet_id", packet.Info.PacketId, "state", packet.Info.State, "error", packet.Err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
hUntrimmed := sha256.Sum256(packet.Data)
|
||||||
|
h := hUntrimmed[:8]
|
||||||
|
if err = os.MkdirAll(*CreateTestsPath, 0755); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
typeStr := reflect.TypeOf(packet.Info.Type).String()
|
||||||
|
goPackage := typeStr[1:strings.LastIndex(typeStr, ".")]
|
||||||
|
typeName := reflect.TypeOf(packet.Info.Type).Elem().Name()
|
||||||
|
|
||||||
|
src := fmt.Sprintf(TestSrcF, goPackage, packet.Err, h, typeName, packet.Data)
|
||||||
|
formatted, formatErr := format.Source([]byte(src))
|
||||||
|
if formatErr != nil {
|
||||||
|
formatted = []byte(src)
|
||||||
|
}
|
||||||
|
|
||||||
|
filePath := fmt.Sprintf(*CreateTestsPath+"/%v_%10x_test.go", len(packet.Data), h)
|
||||||
|
if err = os.WriteFile(filePath, formatted, 0644); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
slog.Info("Packet failed to parse", "error", packet.Err, "saved", filePath, "data", fmt.Sprintf("%q", packet.Data))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var state = proto_base.Login
|
||||||
|
var direction = proto_base.ToClient
|
||||||
|
|
||||||
|
func UnpackPacket(r io.Reader) (packet proto_base.EncodeDecodeAble, i proto_base.PacketInfo, err error) {
|
||||||
|
var timestamp, length uint32
|
||||||
|
err = binary.Read(r, binary.BigEndian, ×tamp)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = binary.Read(r, binary.BigEndian, &length)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the packet data into a byte slice
|
||||||
|
packetData := make([]byte, length)
|
||||||
|
_, err = io.ReadFull(r, packetData)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use bytes.Reader instead of bytes.Buffer
|
||||||
|
b := bytes.NewReader(packetData)
|
||||||
|
|
||||||
|
packetId, err := proto_base.DecodeVarInt(b)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate position after reading the packet ID
|
||||||
|
// indexafterid represents how many bytes we've read so far
|
||||||
|
indexafterid := int(b.Size()) - b.Len()
|
||||||
|
|
||||||
|
var ok bool
|
||||||
|
i, ok = proto.LookUpTypeByPacketInfo(direction, state, packetId, int32(*ProtocolVersion))
|
||||||
|
if !ok {
|
||||||
|
packet = &proto.UnCodablePacket{Err: proto.BadPacketIdErr, Data: packetData, Info: i}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
packet = reflect.New(reflect.TypeOf(i.Type).Elem()).Interface().(proto_base.EncodeDecodeAble)
|
||||||
|
err = packet.Decode(b)
|
||||||
|
if err != nil {
|
||||||
|
packet = &proto.UnCodablePacket{Err: err, Data: packetData[indexafterid:], Info: i}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if b.Len() != 0 {
|
||||||
|
packet = &proto.UnCodablePacket{Err: proto.PacketNotFullyDecodedErr, Data: packetData[indexafterid:], Info: i}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
trackState(packet, i)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func trackState(packet proto_base.EncodeDecodeAble, i proto_base.PacketInfo) {
|
||||||
|
switch i.Name {
|
||||||
|
case "set_protocol":
|
||||||
|
v := reflect.ValueOf(packet).Elem()
|
||||||
|
nsField := v.FieldByName("NextState")
|
||||||
|
if nsField.IsValid() && nsField.Kind() == reflect.Int32 {
|
||||||
|
switch nsField.Int() {
|
||||||
|
case 1:
|
||||||
|
state = proto_base.Status
|
||||||
|
case 2:
|
||||||
|
state = proto_base.Login
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "login_start":
|
||||||
|
if int32(*ProtocolVersion) <= 47 {
|
||||||
|
state = proto_base.Play
|
||||||
|
}
|
||||||
|
case "success":
|
||||||
|
if int32(*ProtocolVersion) >= 764 {
|
||||||
|
state = proto_base.Configuration
|
||||||
|
} else {
|
||||||
|
state = proto_base.Play
|
||||||
|
}
|
||||||
|
case "finish_configuration":
|
||||||
|
state = proto_base.Play
|
||||||
|
case "login_acknowledged":
|
||||||
|
state = proto_base.Configuration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Run(args []string) (err error) {
|
||||||
|
err = cmd.Parse(args)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if *ProtocolVersion == -1 {
|
||||||
|
return fmt.Errorf("no protocol version specified")
|
||||||
|
}
|
||||||
|
if *FileFlag == "" {
|
||||||
|
return fmt.Errorf("no file specified")
|
||||||
|
}
|
||||||
|
switch *StateFlag {
|
||||||
|
case "handshake":
|
||||||
|
state = proto_base.Handshaking
|
||||||
|
case "login":
|
||||||
|
state = proto_base.Login
|
||||||
|
case "config":
|
||||||
|
state = proto_base.Configuration
|
||||||
|
case "play":
|
||||||
|
state = proto_base.Play
|
||||||
|
case "status":
|
||||||
|
state = proto_base.Status
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unknown state: %s", *StateFlag)
|
||||||
|
}
|
||||||
|
switch *DirectionFlag {
|
||||||
|
case "clientbound", "to-client":
|
||||||
|
direction = proto_base.ToClient
|
||||||
|
case "serverbound", "to-server":
|
||||||
|
direction = proto_base.ToServer
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unknown direction: %s", *DirectionFlag)
|
||||||
|
}
|
||||||
|
f, err := os.Open(*FileFlag)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
var totalPackets, failedPackets int
|
||||||
|
for {
|
||||||
|
var packet proto_base.EncodeDecodeAble
|
||||||
|
var info proto_base.PacketInfo
|
||||||
|
packet, info, err = UnpackPacket(f)
|
||||||
|
unCodablePacket, ok := packet.(*proto.UnCodablePacket)
|
||||||
|
if ok {
|
||||||
|
slog.Error("failed to decode packet", "error", err, "packet_name", info.Name)
|
||||||
|
if *CreateTestsPath != "" {
|
||||||
|
err = SaveUnCodeAbleAsTest(unCodablePacket)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("failed to save uncodable packet as test", "error", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
failedPackets++
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Packet: %#v\n", packet)
|
||||||
|
if errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
totalPackets++
|
||||||
|
}
|
||||||
|
if totalPackets > 0 {
|
||||||
|
fmt.Printf("Total packets: %d, failed packets: %d, %%failed: %.2f%%\n", totalPackets, failedPackets, float64(failedPackets)/float64(totalPackets)*100)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("Total packets: %d, failed packets: %d\n", totalPackets, failedPackets)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,18 @@
|
|||||||
|
package failed_packets_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
generated "git.anygate.cloud/anygatecloud/strom/mc/proto_generated/v26_2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Error: unexpected EOF
|
||||||
|
func TestFailedPacket0F8B37C94207F7BF(t *testing.T) {
|
||||||
|
p := &generated.PlayToClientPacketUpdateTime{}
|
||||||
|
b := bytes.NewReader([]byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1c, 0x42, 0x0})
|
||||||
|
err := p.Decode(b)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("expected decode error but got nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package failed_packets_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
generated "git.anygate.cloud/anygatecloud/strom/mc/proto_generated/v26_2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Error: unexpected EOF
|
||||||
|
func TestFailedPacket6C9AD835672C5C51(t *testing.T) {
|
||||||
|
p := &generated.PlayToClientPacketUpdateTime{}
|
||||||
|
b := bytes.NewReader([]byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1b, 0xde, 0x0})
|
||||||
|
err := p.Decode(b)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("expected decode error but got nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package failed_packets_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
generated "git.anygate.cloud/anygatecloud/strom/mc/proto_generated/v26_2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Error: unexpected EOF
|
||||||
|
func TestFailedPacketA5D61E1C0F23E7FA(t *testing.T) {
|
||||||
|
p := &generated.PlayToClientPacketUpdateTime{}
|
||||||
|
b := bytes.NewReader([]byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1c, 0x56, 0x0})
|
||||||
|
err := p.Decode(b)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("expected decode error but got nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package failed_packets_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
generated "git.anygate.cloud/anygatecloud/strom/mc/proto_generated/v26_2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Error: unexpected EOF
|
||||||
|
func TestFailedPacketCAA58159889F7E8A(t *testing.T) {
|
||||||
|
p := &generated.PlayToClientPacketUpdateTime{}
|
||||||
|
b := bytes.NewReader([]byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1c, 0x2e, 0x0})
|
||||||
|
err := p.Decode(b)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("expected decode error but got nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package failed_packets_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
generated "git.anygate.cloud/anygatecloud/strom/mc/proto_generated/v26_2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Error: unexpected EOF
|
||||||
|
func TestFailedPacketD18FC4C0B2DF2972(t *testing.T) {
|
||||||
|
p := &generated.PlayToClientPacketUpdateTime{}
|
||||||
|
b := bytes.NewReader([]byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1b, 0xf2, 0x0})
|
||||||
|
err := p.Decode(b)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("expected decode error but got nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package failed_packets_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
generated "git.anygate.cloud/anygatecloud/strom/mc/proto_generated/v26_2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Error: unexpected EOF
|
||||||
|
func TestFailedPacketE81E82016DB04F28(t *testing.T) {
|
||||||
|
p := &generated.PlayToClientPacketUpdateTime{}
|
||||||
|
b := bytes.NewReader([]byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1c, 0x6, 0x0})
|
||||||
|
err := p.Decode(b)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("expected decode error but got nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package failed_packets_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
generated "git.anygate.cloud/anygatecloud/strom/mc/proto_generated/v26_2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Error: unexpected EOF
|
||||||
|
func TestFailedPacketFE935CF2E584C649(t *testing.T) {
|
||||||
|
p := &generated.PlayToClientPacketUpdateTime{}
|
||||||
|
b := bytes.NewReader([]byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1c, 0x1a, 0x0})
|
||||||
|
err := p.Decode(b)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("expected decode error but got nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package failed_packets_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
generated "git.anygate.cloud/anygatecloud/strom/mc/proto_generated/v26_2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Decode regression: recipe_book_add (26.2) - SlotDisplay type fix
|
||||||
|
func TestRegressionPacket25D845B5D0023D22(t *testing.T) {
|
||||||
|
p := &generated.PlayToClientPacketRecipeBookAdd{}
|
||||||
|
b := bytes.NewReader([]byte{0x1, 0xbe, 0x2, 0x1, 0x2, 0x2, 0x4, 0x6, 0x10, 0x6d, 0x69, 0x6e, 0x65, 0x63, 0x72, 0x61, 0x66, 0x74, 0x3a, 0x70, 0x6c, 0x61, 0x6e, 0x6b, 0x73, 0x6, 0x10, 0x6d, 0x69, 0x6e, 0x65, 0x63, 0x72, 0x61, 0x66, 0x74, 0x3a, 0x70, 0x6c, 0x61, 0x6e, 0x6b, 0x73, 0x6, 0x10, 0x6d, 0x69, 0x6e, 0x65, 0x63, 0x72, 0x61, 0x66, 0x74, 0x3a, 0x70, 0x6c, 0x61, 0x6e, 0x6b, 0x73, 0x6, 0x10, 0x6d, 0x69, 0x6e, 0x65, 0x63, 0x72, 0x61, 0x66, 0x74, 0x3a, 0x70, 0x6c, 0x61, 0x6e, 0x6b, 0x73, 0x5, 0xe8, 0x2, 0x1, 0x0, 0x0, 0x4, 0xe8, 0x2, 0x0, 0x3, 0x1, 0x4, 0x0, 0x10, 0x6d, 0x69, 0x6e, 0x65, 0x63, 0x72, 0x61, 0x66, 0x74, 0x3a, 0x70, 0x6c, 0x61, 0x6e, 0x6b, 0x73, 0x0, 0x10, 0x6d, 0x69, 0x6e, 0x65, 0x63, 0x72, 0x61, 0x66, 0x74, 0x3a, 0x70, 0x6c, 0x61, 0x6e, 0x6b, 0x73, 0x0, 0x10, 0x6d, 0x69, 0x6e, 0x65, 0x63, 0x72, 0x61, 0x66, 0x74, 0x3a, 0x70, 0x6c, 0x61, 0x6e, 0x6b, 0x73, 0x0, 0x10, 0x6d, 0x69, 0x6e, 0x65, 0x63, 0x72, 0x61, 0x66, 0x74, 0x3a, 0x70, 0x6c, 0x61, 0x6e, 0x6b, 0x73, 0x2, 0x1})
|
||||||
|
err := p.Decode(b)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("regression: decode failed: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package failed_packets_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
generated "git.anygate.cloud/anygatecloud/strom/mc/proto_generated/v26_2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Decode regression: declare_commands (26.2) - bitfield + extraNodeData fix
|
||||||
|
func TestRegressionPacket78C731784E6E834A(t *testing.T) {
|
||||||
|
p := &generated.PlayToClientPacketDeclareCommands{}
|
||||||
|
b := bytes.NewReader([]byte{0x1a, 0x0, 0xa, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0x1, 0x1, 0xb, 0x2, 0x6d, 0x65, 0x5, 0x1, 0xc, 0x4, 0x68, 0x65, 0x6c, 0x70, 0x5, 0x1, 0xd, 0x4, 0x6c, 0x69, 0x73, 0x74, 0x1, 0x1, 0xe, 0x3, 0x6d, 0x73, 0x67, 0x9, 0x0, 0x4, 0x4, 0x74, 0x65, 0x6c, 0x6c, 0x9, 0x0, 0x4, 0x1, 0x77, 0x1, 0x2, 0xf, 0x10, 0x6, 0x72, 0x61, 0x6e, 0x64, 0x6f, 0x6d, 0x1, 0x1, 0x11, 0x7, 0x74, 0x65, 0x61, 0x6d, 0x6d, 0x73, 0x67, 0x9, 0x0, 0x8, 0x2, 0x74, 0x6d, 0x1, 0x1, 0x12, 0x7, 0x74, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x6, 0x0, 0x6, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x14, 0x6, 0x0, 0x7, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5, 0x2, 0x5, 0x0, 0x5, 0x75, 0x75, 0x69, 0x64, 0x73, 0x2, 0x1, 0x13, 0x7, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x6, 0x2, 0x1, 0x1, 0x14, 0x5, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x1, 0x1, 0x15, 0x4, 0x72, 0x6f, 0x6c, 0x6c, 0x6, 0x0, 0x7, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x14, 0x16, 0x2, 0x16, 0x17, 0x9, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x18, 0x14, 0x6d, 0x69, 0x6e, 0x65, 0x63, 0x72, 0x61, 0x66, 0x74, 0x3a, 0x61, 0x73, 0x6b, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x6, 0x0, 0x7, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x14, 0x6, 0x0, 0x5, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x27, 0x6, 0x0, 0x5, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x27, 0x1, 0x1, 0x18, 0x3, 0x61, 0x64, 0x64, 0x1, 0x1, 0x19, 0x3, 0x73, 0x65, 0x74, 0x6, 0x0, 0x5, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3, 0x0, 0x6, 0x0, 0x5, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3, 0x0, 0x0})
|
||||||
|
err := p.Decode(b)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("regression: decode failed: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package failed_packets_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
generated "git.anygate.cloud/anygatecloud/strom/mc/proto_generated/v1_8"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Decode regression: spawn_entity_living (1.8) - entityMetadata mergeCompareTo
|
||||||
|
func TestRegressionPacket2B76AF8B937B1E8F(t *testing.T) {
|
||||||
|
p := &generated.PlayToClientPacketSpawnEntityLiving{}
|
||||||
|
b := bytes.NewReader([]byte{0x4c, 0x65, 0xff, 0xff, 0xed, 0x66, 0x0, 0x0, 0x7, 0xe0, 0x0, 0x0, 0x29, 0xe6, 0x91, 0x0, 0x91, 0x0, 0x0, 0xfd, 0x8d, 0x0, 0x0, 0x0, 0x0, 0x21, 0x1, 0x2c, 0x82, 0x0, 0x12, 0x4, 0x3, 0x0, 0x4, 0x0, 0x66, 0x41, 0x20, 0x0, 0x0, 0x47, 0x0, 0x0, 0x0, 0x0, 0x8, 0x0, 0x9, 0x0, 0xc, 0x0, 0xf, 0x0, 0x7f})
|
||||||
|
err := p.Decode(b)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("regression: decode failed: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package failed_packets_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
generated "git.anygate.cloud/anygatecloud/strom/mc/proto_generated/v1_8"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Decode regression: entity_equipment (1.8) - nbt TAG_End fix
|
||||||
|
func TestRegressionPacket0C20C77EA68496BD(t *testing.T) {
|
||||||
|
p := &generated.PlayToClientPacketEntityEquipment{}
|
||||||
|
b := bytes.NewReader([]byte{0x89, 0x16, 0x0, 0x0, 0x1, 0x5, 0x1, 0x0, 0x0, 0x0})
|
||||||
|
err := p.Decode(b)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("regression: decode failed: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package failed_packets_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
generated "git.anygate.cloud/anygatecloud/strom/mc/proto_generated/v1_8"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Decode regression: entity_metadata (1.8) - entityMetadataLoop mergeCompareTo
|
||||||
|
func TestRegressionPacketDF2DF88561529208(t *testing.T) {
|
||||||
|
p := &generated.PlayToClientPacketEntityMetadata{}
|
||||||
|
b := bytes.NewReader([]byte{0x69, 0x0, 0x0, 0x21, 0x1, 0x2c, 0x82, 0x0, 0x3, 0x0, 0x4, 0x0, 0x66, 0x40, 0x80, 0x0, 0x0, 0x47, 0x0, 0x0, 0x0, 0x0, 0x8, 0x0, 0x9, 0x0, 0xc, 0x0, 0xf, 0x0, 0x7f})
|
||||||
|
err := p.Decode(b)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("regression: decode failed: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package failed_packets_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
generated "git.anygate.cloud/anygatecloud/strom/mc/proto_generated/v26_2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Decode regression: entity_equipment (26.2) - topBitSetTerminatedArray
|
||||||
|
func TestRegressionPacket1D0089BBE86A1648(t *testing.T) {
|
||||||
|
p := &generated.PlayToClientPacketEntityEquipment{}
|
||||||
|
b := bytes.NewReader([]byte{0x81, 0x2, 0x0, 0x1, 0x9a, 0x7, 0x0, 0x0})
|
||||||
|
err := p.Decode(b)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("regression: decode failed: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,256 @@
|
|||||||
|
package raw_to_tmcpr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"compress/zlib"
|
||||||
|
"errors"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/proto/replay"
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/proto_base"
|
||||||
|
)
|
||||||
|
|
||||||
|
var Cmd = flag.NewFlagSet("raw-to-tmcpr", flag.ContinueOnError)
|
||||||
|
|
||||||
|
var ClientboundIn = Cmd.String("clientbound", "", "raw tcpflow file for clientbound (server->client) direction")
|
||||||
|
var ServerboundIn = Cmd.String("serverbound", "", "raw tcpflow file for serverbound (client->server) direction")
|
||||||
|
var ClientboundOut = Cmd.String("clientbound-out", "clientbound.tmcpr", "output TMCPR file for clientbound")
|
||||||
|
var ServerboundOut = Cmd.String("serverbound-out", "serverbound.tmcpr", "output TMCPR file for serverbound")
|
||||||
|
var ClientboundState = Cmd.String("clientbound-state", "login", "initial state for clientbound: handshake, login, config, status, play")
|
||||||
|
var ServerboundState = Cmd.String("serverbound-state", "handshake", "initial state for serverbound: handshake, login, config, status, play")
|
||||||
|
var UseConfig = Cmd.Bool("config", false, "use Configuration state transitions (for protocol >= 764, Minecraft 1.20.2+)")
|
||||||
|
|
||||||
|
type captureReader struct {
|
||||||
|
r io.Reader
|
||||||
|
out *replay.Writer
|
||||||
|
state proto_base.State
|
||||||
|
threshold int32
|
||||||
|
startTime time.Time
|
||||||
|
haveStartTime bool
|
||||||
|
direction proto_base.Direction
|
||||||
|
pendingThreshold int32
|
||||||
|
useConfig bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cr *captureReader) setStartTime() {
|
||||||
|
if !cr.haveStartTime {
|
||||||
|
cr.startTime = time.Now()
|
||||||
|
cr.haveStartTime = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cr *captureReader) timestamp() uint32 {
|
||||||
|
return uint32(time.Since(cr.startTime).Milliseconds())
|
||||||
|
}
|
||||||
|
|
||||||
|
func readPacketBytes(r io.Reader, threshold int32) ([]byte, error) {
|
||||||
|
packetLen, err := proto_base.DecodeVarInt(r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("read packet length: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rawBytes := make([]byte, packetLen)
|
||||||
|
_, err = io.ReadFull(r, rawBytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("read packet data: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if threshold >= 0 {
|
||||||
|
buf := bytes.NewReader(rawBytes)
|
||||||
|
uncompressedLen, err := proto_base.DecodeVarInt(buf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("read uncompressed length: %w", err)
|
||||||
|
}
|
||||||
|
if uncompressedLen == 0 {
|
||||||
|
return io.ReadAll(buf)
|
||||||
|
} else {
|
||||||
|
zReader, err := zlib.NewReader(buf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("zlib reader: %w", err)
|
||||||
|
}
|
||||||
|
defer zReader.Close()
|
||||||
|
decompressed, err := io.ReadAll(zReader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("decompress: %w", err)
|
||||||
|
}
|
||||||
|
return decompressed, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rawBytes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cr *captureReader) trackState(packetBytes []byte) {
|
||||||
|
buf := bytes.NewReader(packetBytes)
|
||||||
|
packetId, err := proto_base.DecodeVarInt(buf)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch cr.state {
|
||||||
|
case proto_base.Handshaking:
|
||||||
|
if cr.direction == proto_base.ToServer && packetId == 0 {
|
||||||
|
_, err = proto_base.DecodeVarInt(buf) // protocol version
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, err = proto_base.DecodeString(buf) // server address
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
portBytes := make([]byte, 2)
|
||||||
|
_, err = io.ReadFull(buf, portBytes)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
nextState, err := proto_base.DecodeVarInt(buf)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if nextState == 2 {
|
||||||
|
cr.state = proto_base.Login
|
||||||
|
} else if nextState == 1 {
|
||||||
|
cr.state = proto_base.Status
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case proto_base.Login:
|
||||||
|
if cr.direction == proto_base.ToClient {
|
||||||
|
if packetId == 0x03 {
|
||||||
|
threshold, err := proto_base.DecodeVarInt(buf)
|
||||||
|
if err == nil {
|
||||||
|
cr.threshold = threshold
|
||||||
|
}
|
||||||
|
} else if packetId == 0x02 {
|
||||||
|
if cr.useConfig {
|
||||||
|
cr.state = proto_base.Configuration
|
||||||
|
} else {
|
||||||
|
cr.state = proto_base.Play
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if cr.direction == proto_base.ToServer && packetId == 0 {
|
||||||
|
if cr.pendingThreshold >= 0 {
|
||||||
|
cr.threshold = cr.pendingThreshold
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case proto_base.Configuration:
|
||||||
|
if cr.direction == proto_base.ToClient && packetId == 0x02 {
|
||||||
|
cr.state = proto_base.Play
|
||||||
|
}
|
||||||
|
case proto_base.Status:
|
||||||
|
case proto_base.Play:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cr *captureReader) process() (count int, err error) {
|
||||||
|
for {
|
||||||
|
packetBytes, err := readPacketBytes(cr.r, cr.threshold)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF) {
|
||||||
|
return count, nil
|
||||||
|
}
|
||||||
|
return count, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cr.setStartTime()
|
||||||
|
cr.trackState(packetBytes)
|
||||||
|
|
||||||
|
err = cr.out.WriteRaw(packetBytes, cr.timestamp())
|
||||||
|
if err != nil {
|
||||||
|
return count, fmt.Errorf("write TMCPR: %w", err)
|
||||||
|
}
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Run(args []string) (err error) {
|
||||||
|
err = Cmd.Parse(args)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if *ClientboundIn == "" && *ServerboundIn == "" {
|
||||||
|
return fmt.Errorf("at least one of -clientbound or -serverbound must be specified")
|
||||||
|
}
|
||||||
|
|
||||||
|
parseState := func(name string) (proto_base.State, error) {
|
||||||
|
switch name {
|
||||||
|
case "handshake":
|
||||||
|
return proto_base.Handshaking, nil
|
||||||
|
case "login":
|
||||||
|
return proto_base.Login, nil
|
||||||
|
case "config":
|
||||||
|
return proto_base.Configuration, nil
|
||||||
|
case "play":
|
||||||
|
return proto_base.Play, nil
|
||||||
|
case "status":
|
||||||
|
return proto_base.Status, nil
|
||||||
|
default:
|
||||||
|
return 0, fmt.Errorf("unknown state: %s (valid: handshake, login, config, status, play)", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var compressionThreshold int32 = -1
|
||||||
|
|
||||||
|
if *ClientboundIn != "" {
|
||||||
|
state, err := parseState(*ClientboundState)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
compressionThreshold, err = processFile(*ClientboundIn, *ClientboundOut, state, proto_base.ToClient, -1, *UseConfig)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("clientbound: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if *ServerboundIn != "" {
|
||||||
|
state, err := parseState(*ServerboundState)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = processFile(*ServerboundIn, *ServerboundOut, state, proto_base.ToServer, compressionThreshold, *UseConfig)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("serverbound: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func processFile(inPath, outPath string, initial proto_base.State, dir proto_base.Direction, initialThreshold int32, useConfig bool) (finalThreshold int32, err error) {
|
||||||
|
in, err := os.Open(inPath)
|
||||||
|
if err != nil {
|
||||||
|
return initialThreshold, fmt.Errorf("open input: %w", err)
|
||||||
|
}
|
||||||
|
defer in.Close()
|
||||||
|
|
||||||
|
out, err := os.Create(outPath)
|
||||||
|
if err != nil {
|
||||||
|
return initialThreshold, fmt.Errorf("create output: %w", err)
|
||||||
|
}
|
||||||
|
defer out.Close()
|
||||||
|
|
||||||
|
cr := &captureReader{
|
||||||
|
r: in,
|
||||||
|
out: replay.NewWriter(out),
|
||||||
|
state: initial,
|
||||||
|
threshold: -1,
|
||||||
|
direction: dir,
|
||||||
|
pendingThreshold: initialThreshold,
|
||||||
|
useConfig: useConfig,
|
||||||
|
}
|
||||||
|
if dir == proto_base.ToClient {
|
||||||
|
cr.threshold = initialThreshold
|
||||||
|
}
|
||||||
|
|
||||||
|
count, err := cr.process()
|
||||||
|
if err != nil {
|
||||||
|
return cr.threshold, err
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("%s: %d packets written to %s (threshold=%d)\n", inPath, count, outPath, cr.threshold)
|
||||||
|
return cr.threshold, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
package serve_tmcpr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"flag"
|
||||||
|
"io"
|
||||||
|
"log/slog"
|
||||||
|
"math/rand/v2"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/event"
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/proto"
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/proto/replay"
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/proto_generated/v1_21_8"
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/server"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Server struct {
|
||||||
|
*proto.Conn
|
||||||
|
*event.Timer
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
cmd = flag.NewFlagSet("serve-tmcpr", flag.ContinueOnError)
|
||||||
|
tmcprFile = cmd.String("file", "/tmp/world.tmcpr", "Path to .tmcpr replay file")
|
||||||
|
bindAddr = cmd.String("bind", ":25566", "Address to listen on")
|
||||||
|
)
|
||||||
|
|
||||||
|
func Limbo(c *proto.Conn, tmcprPath string) (err error) {
|
||||||
|
s := &Server{Conn: c}
|
||||||
|
|
||||||
|
_, err = server.ServeLogin(s.Conn, server.WithoutOnlineMode())
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = server.ServeConfig(s.Conn)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := os.Open(tmcprPath)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
r := replay.NewReader(f)
|
||||||
|
r.Version = c.ProtocolVersion
|
||||||
|
|
||||||
|
start := time.Now()
|
||||||
|
|
||||||
|
for {
|
||||||
|
var packetData []byte
|
||||||
|
var timestamp uint32
|
||||||
|
packetData, timestamp, _, err = r.ReadRaw()
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, io.EOF) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
time.Sleep(time.Duration(timestamp)*time.Millisecond - time.Since(start))
|
||||||
|
err = s.SendRaw(packetData)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s.Timer = &event.Timer{}
|
||||||
|
s.Timer.Every(time.Second*15, s.SendKeepAlive)
|
||||||
|
s.Timer.Start(c.Loop)
|
||||||
|
|
||||||
|
return s.StartConn()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) SendKeepAlive() error {
|
||||||
|
return s.Send(&v1_21_8.PlayToClientPacketKeepAlive{KeepAliveId: rand.Int64()})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Run(args []string) error {
|
||||||
|
slog.SetLogLoggerLevel(slog.LevelDebug)
|
||||||
|
|
||||||
|
err := cmd.Parse(args)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if *tmcprFile == "" {
|
||||||
|
slog.Error("no file specified")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return server.StartServerWithOnConn(*bindAddr, func(c *proto.Conn) error {
|
||||||
|
return Limbo(c, *tmcprFile)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,180 @@
|
|||||||
|
package serve_world
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"math/rand/v2"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/event"
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/level"
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/level/anvil"
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/nbt"
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/proto"
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/proto_generated/v1_21_8"
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/server"
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Server struct {
|
||||||
|
*proto.Conn
|
||||||
|
*event.Timer
|
||||||
|
|
||||||
|
reconnectCalled bool
|
||||||
|
}
|
||||||
|
|
||||||
|
var FullLight [][]uint8
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
light := make([]uint8, 2048)
|
||||||
|
for i := range light {
|
||||||
|
light[i] = 0xFF
|
||||||
|
}
|
||||||
|
|
||||||
|
sectionsPerChunk := 384 / 16
|
||||||
|
FullLight = make([][]uint8, sectionsPerChunk)
|
||||||
|
for i := range sectionsPerChunk {
|
||||||
|
FullLight[i] = light
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetChunkFromMca(x, z int32, version string) (b []byte, err error) {
|
||||||
|
xmca := util.FloorDiv(x, anvil.ChunksWidthRegionFile)
|
||||||
|
zmca := util.FloorDiv(z, anvil.ChunksWidthRegionFile)
|
||||||
|
x = x % anvil.ChunksWidthRegionFile
|
||||||
|
if x < 0 {
|
||||||
|
x += anvil.ChunksWidthRegionFile
|
||||||
|
}
|
||||||
|
z = z % anvil.ChunksWidthRegionFile
|
||||||
|
if z < 0 {
|
||||||
|
z += anvil.ChunksWidthRegionFile
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := os.Open(fmt.Sprintf("./.devres/anvil_testdata/securechattestworld/region/r.%v.%v.mca", xmca, zmca))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
mca, err := anvil.ReadChunkHeader(f)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var n *nbt.Tag
|
||||||
|
n, err = mca.ChunkAt(f, x, z)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if n == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var chunk anvil.Chunk
|
||||||
|
err = nbt.Format.Decode(n.Value, &chunk)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var packetChunk *level.Chunk
|
||||||
|
packetChunk, err = chunk.ToStorage(version)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
buffer := bytes.NewBuffer(nil)
|
||||||
|
err = packetChunk.WriteChunkData(buffer)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
b = buffer.Bytes()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func Limbo(c *proto.Conn) (err error) {
|
||||||
|
s := &Server{
|
||||||
|
Conn: c,
|
||||||
|
}
|
||||||
|
_, err = server.ServeLogin(s.Conn, server.WithoutOnlineMode())
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = server.ServeConfig(s.Conn)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
login := &v1_21_8.PlayToClientPacketLogin{EntityId: 73, IsHardcore: false, WorldNames: []string{"minecraft:overworld", "minecraft:the_end", "minecraft:the_nether"}, MaxPlayers: 100, ViewDistance: 10, SimulationDistance: 10, ReducedDebugInfo: false, EnableRespawnScreen: true, DoLimitedCrafting: false, WorldState: v1_21_8.PlayToClientSpawnInfo{Dimension: 0, Name: "minecraft:overworld", HashedSeed: -8365436745629239539, Gamemode: "survival", PreviousGamemode: 0xff, IsDebug: false, IsFlat: false, Death: nil, PortalCooldown: 0, SeaLevel: 63}, EnforcesSecureChat: false}
|
||||||
|
err = s.Send(login)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = s.Send(&v1_21_8.PlayToClientPacketPosition{
|
||||||
|
TeleportId: 0,
|
||||||
|
X: 0.5,
|
||||||
|
Y: 100,
|
||||||
|
Z: 0.5,
|
||||||
|
Dx: 0,
|
||||||
|
Dy: 0,
|
||||||
|
Dz: 0,
|
||||||
|
Yaw: 0,
|
||||||
|
Pitch: 0,
|
||||||
|
Flags: v1_21_8.PlayToClientPositionUpdateRelatives{},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = s.Send(&v1_21_8.PlayToClientPacketUpdateViewPosition{
|
||||||
|
ChunkX: 0,
|
||||||
|
ChunkZ: 0,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = s.Send(&v1_21_8.PlayToClientPacketGameStateChange{
|
||||||
|
Reason: "level_chunks_load_start",
|
||||||
|
})
|
||||||
|
|
||||||
|
radius := int32(10)
|
||||||
|
|
||||||
|
for x := -radius; x < radius; x++ {
|
||||||
|
for z := -radius; z < radius; z++ {
|
||||||
|
var chunkData []byte
|
||||||
|
chunkData, err = GetChunkFromMca(x, z, c.Version)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if chunkData == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
OLDpacket := &v1_21_8.PlayToClientPacketMapChunk{}
|
||||||
|
OLDpacket.X = x
|
||||||
|
OLDpacket.Z = z
|
||||||
|
OLDpacket.ChunkData = v1_21_8.ByteArray{Val: chunkData}
|
||||||
|
OLDpacket.BlockLight = FullLight
|
||||||
|
OLDpacket.BlockLightMask = []int64{(1 << 24) - 1}
|
||||||
|
err = s.Send(OLDpacket)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s.Timer = &event.Timer{}
|
||||||
|
s.Timer.Every(time.Second*15, s.SendKeepAlive)
|
||||||
|
s.Timer.Start(c.Loop)
|
||||||
|
|
||||||
|
return s.StartConn()
|
||||||
|
}
|
||||||
|
func (s *Server) SendKeepAlive() (err error) {
|
||||||
|
err = s.Send(&v1_21_8.PlayToClientPacketKeepAlive{KeepAliveId: rand.Int64()})
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func Run(args []string) error {
|
||||||
|
slog.SetLogLoggerLevel(slog.LevelDebug)
|
||||||
|
return server.StartServerWithOnConn(":25566", Limbo)
|
||||||
|
}
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
package status
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/client"
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/event"
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/proto"
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/proto_base"
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/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(nil, 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.DoDNSChecked(nil, *addrFlag, nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
slog.Debug("resolved address to ", "addr", *addrFlag)
|
||||||
|
|
||||||
|
status, err := StatusRaw(*addrFlag)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Println(status.Status)
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
package data
|
|
||||||
|
|
||||||
// [
|
|
||||||
//
|
|
||||||
// {
|
|
||||||
// "id": 0,
|
|
||||||
// "name": "badlands",
|
|
||||||
// "category": "mesa",
|
|
||||||
// "temperature": 2.0,
|
|
||||||
// "has_precipitation": false,
|
|
||||||
// "dimension": "overworld",
|
|
||||||
// "displayName": "Badlands",
|
|
||||||
// "color": 7254527
|
|
||||||
// },
|
|
||||||
|
|
||||||
type Biome struct {
|
|
||||||
Id int
|
|
||||||
Name string
|
|
||||||
Category string
|
|
||||||
Temperature float64
|
|
||||||
HasPrecipitation bool
|
|
||||||
Dimension string
|
|
||||||
DisplayName string
|
|
||||||
Color int
|
|
||||||
}
|
|
||||||
|
|
||||||
var BiomesCache = make(map[string][]Biome)
|
|
||||||
|
|
||||||
func BiomesForVersion(v string) (ret []Biome) {
|
|
||||||
var ok bool
|
|
||||||
if ret, ok = BiomesCache[v]; ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var b []Biome
|
|
||||||
must(LoadVersionedJson(v, "biomes", &b))
|
|
||||||
BiomesCache[v] = b
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
func LookupBiomeById(version string, id int32) (biome Biome, ok bool) {
|
|
||||||
for _, b := range BiomesForVersion(version) {
|
|
||||||
if b.Id == int(id) {
|
|
||||||
biome = b
|
|
||||||
ok = true
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
package data
|
|
||||||
|
|
||||||
// {
|
|
||||||
// "id": 0,
|
|
||||||
// "name": "air",
|
|
||||||
// "displayName": "Air",
|
|
||||||
// "hardness": 0.0,
|
|
||||||
// "resistance": 0.0,
|
|
||||||
// "stackSize": 64,
|
|
||||||
// "diggable": false,
|
|
||||||
// "material": "default",
|
|
||||||
// "transparent": true,
|
|
||||||
// "emitLight": 0,
|
|
||||||
// "filterLight": 0,
|
|
||||||
// "defaultState": 0,
|
|
||||||
// "minStateId": 0,
|
|
||||||
// "maxStateId": 0,
|
|
||||||
// "states": [],
|
|
||||||
// "drops": [],
|
|
||||||
// "boundingBox": "empty"
|
|
||||||
// },
|
|
||||||
|
|
||||||
type Block struct {
|
|
||||||
Id int
|
|
||||||
Name string
|
|
||||||
DisplayName string
|
|
||||||
Hardness float64
|
|
||||||
Resistance float64
|
|
||||||
StackSize int
|
|
||||||
Diggable bool
|
|
||||||
Material string
|
|
||||||
Transparent bool
|
|
||||||
EmitLight int
|
|
||||||
FilterLight int
|
|
||||||
DefaultState int32
|
|
||||||
MinStateId int32
|
|
||||||
MaxStateId int32
|
|
||||||
States []struct {
|
|
||||||
Name string
|
|
||||||
Type string
|
|
||||||
NumValues int
|
|
||||||
}
|
|
||||||
Drops []int
|
|
||||||
BoundingBox string
|
|
||||||
}
|
|
||||||
|
|
||||||
var BlocksCache = make(map[string][]Block)
|
|
||||||
|
|
||||||
func BlocksForVersion(v string) (ret []Block) {
|
|
||||||
var ok bool
|
|
||||||
if ret, ok = BlocksCache[v]; ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var b []Block
|
|
||||||
must(LoadVersionedJson(v, "blocks", &b))
|
|
||||||
BlocksCache[v] = b
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
func LookupBlockByStateId(version string, stateId int32) (block Block, ok bool) {
|
|
||||||
for _, b := range BlocksForVersion(version) {
|
|
||||||
if b.MinStateId <= stateId && stateId <= b.MaxStateId {
|
|
||||||
block = b
|
|
||||||
ok = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
package data_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/admin-else/strom/data"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestLookupBlockByStateId(t *testing.T) {
|
|
||||||
b, ok := data.LookupBlockByStateId("1.21.8", 0)
|
|
||||||
if !ok {
|
|
||||||
t.Fatal("Block not found")
|
|
||||||
}
|
|
||||||
fmt.Println(b)
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
package data
|
|
||||||
|
|
||||||
import (
|
|
||||||
"embed"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:embed minecraft-data/data
|
|
||||||
var MinecraftData embed.FS
|
|
||||||
|
|
||||||
func LoadJson(path string, data any) (err error) {
|
|
||||||
b, err := MinecraftData.ReadFile(path)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = json.Unmarshal(b, data)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func LoadVersionedJson(version, dataName string, data any) (err error) {
|
|
||||||
b, err := MinecraftData.ReadFile("minecraft-data/data/" + Paths.Data[version][dataName] + "/" + dataName + ".json")
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = json.Unmarshal(b, data)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var UnknownMinecraftVersionError = errors.New("unknown minecraft version")
|
|
||||||
|
|
||||||
func must(err error) {
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Submodule data/minecraft-data deleted from 9443ed0dec
@@ -1,47 +0,0 @@
|
|||||||
package data
|
|
||||||
|
|
||||||
// {
|
|
||||||
// "minecraftVersion": "1.21.10-rc1",
|
|
||||||
// "version": 1073742098,
|
|
||||||
// "dataVersion": 4555,
|
|
||||||
// "usesNetty": true,
|
|
||||||
// "majorVersion": "1.21",
|
|
||||||
// "releaseType": "snapshot"
|
|
||||||
// },
|
|
||||||
|
|
||||||
type ProtocolVersion struct {
|
|
||||||
MinecraftVersion string
|
|
||||||
Version int
|
|
||||||
DataVersion int
|
|
||||||
UsesNetty bool
|
|
||||||
MajorVersion string
|
|
||||||
ReleaseType string
|
|
||||||
}
|
|
||||||
|
|
||||||
var ProtocolVersions []ProtocolVersion
|
|
||||||
|
|
||||||
func LookUpProtocolVersionByName(name string) (ret ProtocolVersion, err error) {
|
|
||||||
for _, v := range ProtocolVersions {
|
|
||||||
if v.MinecraftVersion == name {
|
|
||||||
ret = v
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
err = UnknownMinecraftVersionError
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func LookUpProtocolVersionByVersion(version int) (ret ProtocolVersion, err error) {
|
|
||||||
for _, v := range ProtocolVersions {
|
|
||||||
if v.Version == version {
|
|
||||||
ret = v
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
err = UnknownMinecraftVersionError
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
must(LoadJson("minecraft-data/data/pc/common/protocolVersions.json", &ProtocolVersions))
|
|
||||||
}
|
|
||||||
@@ -1,118 +0,0 @@
|
|||||||
// Package event is the event handler
|
|
||||||
package event
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Loop struct {
|
|
||||||
HandlerFunctions map[reflect.Type][]reflect.Value
|
|
||||||
Handlers []any
|
|
||||||
HandlerIndex int
|
|
||||||
}
|
|
||||||
|
|
||||||
type Unhandled struct {
|
|
||||||
Val any
|
|
||||||
}
|
|
||||||
|
|
||||||
type Anything struct {
|
|
||||||
Val any
|
|
||||||
}
|
|
||||||
|
|
||||||
type (
|
|
||||||
OnStart struct{}
|
|
||||||
Tick struct{}
|
|
||||||
)
|
|
||||||
|
|
||||||
// HandlerDoneErr will stop the event loop and return the error if provided.
|
|
||||||
type HandlerDoneErr struct {
|
|
||||||
Return error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e HandlerDoneErr) Error() string {
|
|
||||||
return "HandlerDoneErr"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e HandlerDoneErr) Unwrap() error {
|
|
||||||
return e.Return
|
|
||||||
}
|
|
||||||
|
|
||||||
// ErrDontForward will stop the event from being forwarded to other handlers, including anything handlers.
|
|
||||||
var ErrDontForward = errors.New("dont forward")
|
|
||||||
|
|
||||||
// FindHandlers initializes and populates the HandlerFunctions map by reflecting over methods of registered handlers.
|
|
||||||
func (l *Loop) FindHandlers() {
|
|
||||||
l.HandlerFunctions = make(map[reflect.Type][]reflect.Value)
|
|
||||||
for _, inst := range l.Handlers {
|
|
||||||
t := reflect.TypeOf(inst)
|
|
||||||
v := reflect.ValueOf(inst)
|
|
||||||
for i := 0; i < t.NumMethod(); i++ {
|
|
||||||
method := t.Method(i)
|
|
||||||
if strings.HasPrefix(method.Name, "On") {
|
|
||||||
if method.Type.NumIn() != 2 || method.Type.NumOut() != 1 || method.Type.Out(0) != reflect.TypeFor[error]() {
|
|
||||||
fmt.Println("Invalid event handler signature:", t.String(), ".", method.Name)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
eventType := method.Type.In(1)
|
|
||||||
l.HandlerFunctions[eventType] = append(l.HandlerFunctions[eventType], v.Method(i))
|
|
||||||
//fmt.Println("Registered Handler:", t.String(), ".", method.Name, "(", eventType.String(), ")")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// FireFound triggers all handlers registered for the specified event type and returns if handlers were found or an error occurred.
|
|
||||||
func (l *Loop) FireFound(event any) (found bool, err error) {
|
|
||||||
var handlers []reflect.Value
|
|
||||||
if handlers, found = l.HandlerFunctions[reflect.TypeOf(event)]; found {
|
|
||||||
for _, handler := range handlers {
|
|
||||||
errV := handler.Call([]reflect.Value{reflect.ValueOf(event)})[0]
|
|
||||||
if !errV.IsNil() {
|
|
||||||
err = errV.Interface().(error)
|
|
||||||
err = errors.Join(fmt.Errorf("event handler failed at: %s(%T)", handler.Type().In(0), event), err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fire dispatches the provided event to all appropriate handlers and invokes fallback handlers if the event is unhandled.
|
|
||||||
func (l *Loop) Fire(event any) (err error) {
|
|
||||||
found, err := l.FireFound(event)
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, ErrDontForward) {
|
|
||||||
err = nil
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !found {
|
|
||||||
_, err = l.FireFound(Unhandled{event})
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_, err = l.FireFound(Anything{event})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *Loop) Start() (err error) {
|
|
||||||
_ = *l // exit early on nil connection
|
|
||||||
l.FindHandlers()
|
|
||||||
err = l.Fire(OnStart{})
|
|
||||||
for err == nil {
|
|
||||||
err = l.Fire(Tick{})
|
|
||||||
if err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if errors.Is(err, HandlerDoneErr{}) {
|
|
||||||
err = errors.Unwrap(err)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
module github.com/admin-else/strom
|
module git.anygate.cloud/anygatecloud/strom
|
||||||
|
|
||||||
go 1.24
|
go 1.26.2
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/go-viper/mapstructure/v2 v2.4.0
|
github.com/go-viper/mapstructure/v2 v2.4.0 // FIXME: this should be replaced with my version
|
||||||
github.com/google/go-cmp v0.7.0
|
github.com/google/go-cmp v0.7.0
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
)
|
)
|
||||||
|
|||||||
-149
@@ -1,149 +0,0 @@
|
|||||||
package level
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"math"
|
|
||||||
|
|
||||||
"github.com/admin-else/strom/proto_base"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
BlocksPerChunkSection = 4096
|
|
||||||
BiomesPerChunkSection = 64
|
|
||||||
)
|
|
||||||
|
|
||||||
func UnpackArrayPalette(r io.Reader, bitsPerEntry uint8, numberOfEntries int) (data []int32, err error) {
|
|
||||||
palletLen, err := proto_base.DecodeVarInt(r)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var pallet []int32 // remember we can't use make because this is user-controlled data
|
|
||||||
for range palletLen {
|
|
||||||
entry, err := proto_base.DecodeVarInt(r)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
pallet = append(pallet, entry)
|
|
||||||
}
|
|
||||||
data, err = UnpackLongData(r, bitsPerEntry, numberOfEntries)
|
|
||||||
for i, b := range data {
|
|
||||||
data[i] = pallet[b] // maybe check bounds?
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func UnpackLongData(r io.Reader, bitsPerEntry uint8, numberOfEntries int) (data []int32, err error) {
|
|
||||||
entriesPerLong := int(64 / bitsPerEntry)
|
|
||||||
numberOfLongs := int(math.Ceil(float64(numberOfEntries) / float64(entriesPerLong)))
|
|
||||||
var dataL []uint64
|
|
||||||
for range numberOfLongs {
|
|
||||||
var entry uint64
|
|
||||||
err = binary.Read(r, binary.BigEndian, &entry)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
dataL = append(dataL, entry)
|
|
||||||
}
|
|
||||||
|
|
||||||
data = LongsToData(dataL, int32(numberOfEntries), int32(bitsPerEntry))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func UnpackSingleValuePalette(r io.Reader, numberOfEntries int) (data []int32, err error) {
|
|
||||||
entry, err := proto_base.DecodeVarInt(r)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
data = make([]int32, numberOfEntries)
|
|
||||||
for i := range data {
|
|
||||||
data[i] = int32(entry)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func LongsToData(data []uint64, n, bpe int32) (ret []int32) {
|
|
||||||
mask := (uint64(1) << uint32(bpe)) - 1
|
|
||||||
elementsPerLong := 64 / bpe
|
|
||||||
|
|
||||||
ret = make([]int32, n)
|
|
||||||
for i := range n {
|
|
||||||
ret[i] = int32((data[i/elementsPerLong] >> ((i % elementsPerLong) * bpe)) & mask)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func UnpackBlockData(r io.Reader) (blocks [BlocksPerChunkSection]int32, err error) {
|
|
||||||
var bitsPerEntry uint8
|
|
||||||
err = binary.Read(r, binary.BigEndian, &bitsPerEntry)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var blocksSlice []int32
|
|
||||||
fmt.Println("blocks bpe", bitsPerEntry)
|
|
||||||
switch bitsPerEntry {
|
|
||||||
case 0:
|
|
||||||
blocksSlice, err = UnpackSingleValuePalette(r, BlocksPerChunkSection)
|
|
||||||
case 1, 2, 3, 4:
|
|
||||||
blocksSlice, err = UnpackArrayPalette(r, bitsPerEntry, BlocksPerChunkSection)
|
|
||||||
case 5, 6, 7, 8:
|
|
||||||
err = errors.New("unimplemented")
|
|
||||||
default:
|
|
||||||
blocksSlice, err = UnpackLongData(r, bitsPerEntry, BlocksPerChunkSection)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
blocks = [BlocksPerChunkSection]int32(blocksSlice)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func UnpackBiomeData(r io.Reader) (biomes [BiomesPerChunkSection]int32, err error) {
|
|
||||||
// net.minecraft.world.chunk.PaletteProvider#forBiomes
|
|
||||||
var bitsPerEntry uint8
|
|
||||||
err = binary.Read(r, binary.BigEndian, &bitsPerEntry)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var biomesSlice []int32
|
|
||||||
fmt.Println("biome bpe", bitsPerEntry)
|
|
||||||
switch bitsPerEntry {
|
|
||||||
case 0:
|
|
||||||
biomesSlice, err = UnpackSingleValuePalette(r, BiomesPerChunkSection)
|
|
||||||
case 1, 2, 3:
|
|
||||||
biomesSlice, err = UnpackArrayPalette(r, bitsPerEntry, BiomesPerChunkSection)
|
|
||||||
default:
|
|
||||||
biomesSlice, err = UnpackLongData(r, bitsPerEntry, BiomesPerChunkSection)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
biomes = [BiomesPerChunkSection]int32(biomesSlice)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
type ChunkSection struct {
|
|
||||||
BlockData [BlocksPerChunkSection]int32
|
|
||||||
BiomeData [BiomesPerChunkSection]int32
|
|
||||||
}
|
|
||||||
|
|
||||||
func UnpackSection(r io.Reader) (s ChunkSection, err error) {
|
|
||||||
var blockCount int16
|
|
||||||
err = binary.Read(r, binary.BigEndian, &blockCount)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
s.BlockData, err = UnpackBlockData(r)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
s.BiomeData, err = UnpackBiomeData(r)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
@@ -9,32 +9,40 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/admin-else/strom/crypto"
|
|
||||||
"github.com/google/uuid"
|
"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 {
|
type Account struct {
|
||||||
|
*Servers
|
||||||
Name string
|
Name string
|
||||||
Uuid uuid.UUID
|
Uuid uuid.UUID
|
||||||
Ygg string
|
Ygg string
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
|
||||||
BaseUrlApi = "https://api.minecraftservices.com"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (a *Account) doMcApiRequest(method string, url string, from any, to any) (err error) {
|
func (a *Account) doMcApiRequest(method string, url string, from any, to any) (err error) {
|
||||||
var b []byte
|
var b []byte
|
||||||
if method != "GET" {
|
if method != "GET" {
|
||||||
b, err = json.Marshal(from)
|
b, err = json.Marshal(from)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
r, err := http.NewRequest(method, url, bytes.NewReader(b))
|
r, err := http.NewRequest(method, url, bytes.NewReader(b))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
r.Header.Set("Content-Type", "application/json")
|
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)
|
resp, err := http.DefaultClient.Do(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return
|
||||||
}
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
if !(resp.StatusCode >= 200 && resp.StatusCode <= 299) {
|
if !(resp.StatusCode >= 200 && resp.StatusCode <= 299) {
|
||||||
return fmt.Errorf("bad status code %v", resp.StatusCode)
|
return fmt.Errorf("bad status code %v", resp.StatusCode)
|
||||||
}
|
}
|
||||||
b, err = io.ReadAll(resp.Body)
|
b, err = io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return
|
||||||
}
|
}
|
||||||
err = json.Unmarshal(b, to)
|
err = json.Unmarshal(b, to)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return
|
||||||
}
|
}
|
||||||
return nil
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
type PlayerKeys struct {
|
type PlayerKeys struct {
|
||||||
@@ -73,7 +82,7 @@ type PlayerKeys struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *Account) FetchKeys() (keys PlayerKeys, err error) {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,10 +99,12 @@ func (a *Account) JoinServer(serverId string) (err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
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 {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
if resp.StatusCode != 204 {
|
if resp.StatusCode != 204 {
|
||||||
body, err = io.ReadAll(resp.Body)
|
body, err = io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -112,17 +123,10 @@ type NameChangResp struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *Account) NameChangeInfo() (r NameChangResp, err error) {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewOfflineAccount(name string) *Account {
|
|
||||||
return &Account{
|
|
||||||
Name: name,
|
|
||||||
Uuid: crypto.FromOfflinePlayer(name),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// PlayerProfile represents the player's profile information
|
// PlayerProfile represents the player's profile information
|
||||||
type PlayerProfile struct {
|
type PlayerProfile struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
@@ -143,7 +147,7 @@ type PlayerProfile struct {
|
|||||||
|
|
||||||
// QueryProfile queries the player's profile
|
// QueryProfile queries the player's profile
|
||||||
func (a *Account) QueryProfile() (profile PlayerProfile, err error) {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,7 +185,7 @@ type PlayerAttributes struct {
|
|||||||
|
|
||||||
// QueryAttributes queries the player's attributes
|
// QueryAttributes queries the player's attributes
|
||||||
func (a *Account) QueryAttributes() (attrs PlayerAttributes, err error) {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,7 +198,7 @@ type ModifyAttributesRequest struct {
|
|||||||
|
|
||||||
// ModifyAttributes modifies the player's attributes
|
// ModifyAttributes modifies the player's attributes
|
||||||
func (a *Account) ModifyAttributes(req ModifyAttributesRequest) (attrs PlayerAttributes, err error) {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -205,24 +209,23 @@ type BlockedUsers struct {
|
|||||||
|
|
||||||
// GetBlocklist gets the list of blocked users
|
// GetBlocklist gets the list of blocked users
|
||||||
func (a *Account) GetBlocklist() (blocklist BlockedUsers, err error) {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckNameAvailability checks if a name is available
|
|
||||||
type NameAvailability struct {
|
type NameAvailability struct {
|
||||||
Status string `json:"status"` // DUPLICATE, AVAILABLE, NOT_ALLOWED
|
Status string `json:"status"` // DUPLICATE, AVAILABLE, NOT_ALLOWED
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckNameAvailability checks if a name is available
|
// CheckNameAvailability checks if a name is available
|
||||||
func (a *Account) CheckNameAvailability(name string) (avail NameAvailability, err error) {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// ChangeName changes the player's name
|
// ChangeName changes the player's name
|
||||||
func (a *Account) ChangeName(newName string) (profile PlayerProfile, err error) {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -234,19 +237,19 @@ type ChangeSkinRequest struct {
|
|||||||
|
|
||||||
// ChangeSkin changes the player's skin
|
// ChangeSkin changes the player's skin
|
||||||
func (a *Account) ChangeSkin(req ChangeSkinRequest) (profile PlayerProfile, err error) {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResetSkin resets the player's skin to default
|
// ResetSkin resets the player's skin to default
|
||||||
func (a *Account) ResetSkin() (profile PlayerProfile, err error) {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// HideCape hides the player's active cape
|
// HideCape hides the player's active cape
|
||||||
func (a *Account) HideCape() (profile PlayerProfile, err error) {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -258,29 +261,58 @@ type ShowCapeRequest struct {
|
|||||||
// ShowCape shows a specific cape
|
// ShowCape shows a specific cape
|
||||||
func (a *Account) ShowCape(capeID string) (profile PlayerProfile, err error) {
|
func (a *Account) ShowCape(capeID string) (profile PlayerProfile, err error) {
|
||||||
req := ShowCapeRequest{CapeID: capeID}
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckGiftCodeValidity checks if a gift code is valid
|
// CheckGiftCodeValidity checks if a gift code is valid
|
||||||
func (a *Account) CheckGiftCodeValidity(giftCode string) (valid bool, err error) {
|
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)
|
r, err := http.NewRequest("GET", url, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
r.Header.Set("Authorization", "Bearer "+a.Ygg)
|
r.Header.Set("Authorization", "Bearer "+a.Ygg)
|
||||||
|
|
||||||
resp, err := http.DefaultClient.Do(r)
|
resp, err := http.DefaultClient.Do(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
// Returns HTTP 200 or 204 if valid, 404 if invalid
|
// Returns HTTP 200 or 204 if valid, 404 if invalid
|
||||||
if resp.StatusCode == 200 || resp.StatusCode == 204 {
|
if resp.StatusCode == 200 || resp.StatusCode == 204 {
|
||||||
return true, nil
|
valid = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAccountFromYGG creates an Account by authenticating the YGG token against
|
||||||
|
// Mojang's API. It makes an HTTP call to fetch the player profile and returns
|
||||||
|
// an Account with name and UUID populated, or an error if authentication fails.
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAccount creates an Account from explicit name, UUID, and YGG token without
|
||||||
|
// making any HTTP calls. The account uses Mojang's default API servers.
|
||||||
|
func NewAccount(name string, uuid uuid.UUID, ygg string) (a *Account) {
|
||||||
|
return &Account{
|
||||||
|
Name: name,
|
||||||
|
Uuid: uuid,
|
||||||
|
Ygg: ygg,
|
||||||
|
Servers: DefaultServers,
|
||||||
}
|
}
|
||||||
return false, nil
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/crypto"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewOfflineAccount creates an offline-mode Account with a UUID derived from the
|
||||||
|
// player name. No HTTP calls are made; the UUID is computed locally.
|
||||||
|
func NewOfflineAccount(name string) *Account {
|
||||||
|
return &Account{
|
||||||
|
Name: name,
|
||||||
|
Uuid: crypto.FromOfflinePlayer(name),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,143 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BlockedServers fetches the list of SHA-1 hashes of blocked server IPs from
|
||||||
|
// Mojang's session server. The returned strings are hex-encoded hashes.
|
||||||
|
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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasJoined checks with Mojang's session server whether a player has joined a
|
||||||
|
// given server. name is the player name, server is the server hash, and ip is
|
||||||
|
// the optional player IP address. Returns the player's profile on success.
|
||||||
|
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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// MojangPublicKeys fetches Mojang's public keys for profile property signing and
|
||||||
|
// player certificate verification from the session server.
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// Profile fetches a player's profile by UUID from Mojang's session server.
|
||||||
|
// If unsigned is false, signed profile properties are included in the response.
|
||||||
|
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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NameToId resolves a Minecraft player name to a UUID and canonical name by
|
||||||
|
// making an HTTP GET to Mojang's API.
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// NameToIdBulk resolves multiple Minecraft player names to UUIDs and canonical
|
||||||
|
// names in a single HTTP POST to Mojang's API.
|
||||||
|
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
|
||||||
|
}
|
||||||
@@ -0,0 +1,215 @@
|
|||||||
|
package chat
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/binary"
|
||||||
|
"encoding/pem"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/api"
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/proto"
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/proto_generated/v1_21_11"
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/proto_generated/v1_8"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Module struct {
|
||||||
|
*proto.Conn
|
||||||
|
|
||||||
|
Account *api.Account
|
||||||
|
|
||||||
|
// Chat signing state
|
||||||
|
canSign bool
|
||||||
|
privKey *rsa.PrivateKey
|
||||||
|
sessionUUID uuid.UUID
|
||||||
|
msgIndex int32
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start creates a chat module for the connection. If acc is an online account,
|
||||||
|
// it fetches the profile key pair from Mojang and enables signed chat.
|
||||||
|
func Start(c *proto.Conn, acc *api.Account) (*Module, error) {
|
||||||
|
m := &Module{Conn: c, Account: acc}
|
||||||
|
if acc != nil && acc.Ygg != "" {
|
||||||
|
if err := m.initSigning(acc); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Module) initSigning(acc *api.Account) (err error) {
|
||||||
|
if m.Conn.ProtocolVersion < 764 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
keys, err := acc.FetchKeys()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("fetch profile keys: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
priv, err := parsePrivateKey(keys.KeyPair.PrivateKey)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("parse private key: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
m.privKey = priv
|
||||||
|
m.sessionUUID = uuid.New()
|
||||||
|
|
||||||
|
pubDer, err := x509.MarshalPKIXPublicKey(&priv.PublicKey)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("marshal public key: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sigBytes, err := base64.StdEncoding.DecodeString(keys.PublicKeySignatureV2)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("decode key signature: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = m.Conn.Send(&v1_21_11.PlayToServerPacketChatSessionUpdate{
|
||||||
|
SessionUUID: m.sessionUUID,
|
||||||
|
ExpireTime: keys.ExpiresAt.UnixMilli(),
|
||||||
|
PublicKey: v1_21_11.ByteArray{Val: pubDer},
|
||||||
|
Signature: v1_21_11.ByteArray{Val: sigBytes},
|
||||||
|
}); err != nil {
|
||||||
|
return fmt.Errorf("send chat session update: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
m.canSign = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendMessage sends a chat message. If signing is enabled, the message is signed;
|
||||||
|
// otherwise it is sent unsigned. For pre-1.19 protocol versions, uses the legacy
|
||||||
|
// chat packet format (plain string).
|
||||||
|
func (m *Module) SendMessage(message string) (err error) {
|
||||||
|
if m.Conn.ProtocolVersion < 764 {
|
||||||
|
return m.Conn.Send(&v1_8.PlayToServerPacketChat{Message: message})
|
||||||
|
}
|
||||||
|
if m.canSign {
|
||||||
|
return m.sendSigned(message)
|
||||||
|
}
|
||||||
|
return m.Conn.Send(&v1_21_11.PlayToServerPacketChatMessage{Message: message})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Module) sendSigned(message string) (err error) {
|
||||||
|
now := time.Now()
|
||||||
|
|
||||||
|
saltBytes := make([]byte, 8)
|
||||||
|
if _, err = rand.Read(saltBytes); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
salt := int64(binary.BigEndian.Uint64(saltBytes))
|
||||||
|
|
||||||
|
timestamp := now.UnixMilli()
|
||||||
|
|
||||||
|
payload, err := m.buildSignaturePayload(message, salt, timestamp)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
hashed := sha256.Sum256(payload)
|
||||||
|
sigBytes, err := rsa.SignPKCS1v15(rand.Reader, m.privKey, crypto.SHA256, hashed[:])
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(sigBytes) != 256 {
|
||||||
|
return fmt.Errorf("unexpected signature length: %d", len(sigBytes))
|
||||||
|
}
|
||||||
|
var signature [256]byte
|
||||||
|
copy(signature[:], sigBytes)
|
||||||
|
|
||||||
|
m.msgIndex++
|
||||||
|
|
||||||
|
return m.Conn.Send(&v1_21_11.PlayToServerPacketChatMessage{
|
||||||
|
Message: message,
|
||||||
|
Timestamp: timestamp,
|
||||||
|
Salt: salt,
|
||||||
|
Signature: &signature,
|
||||||
|
Offset: 0,
|
||||||
|
Acknowledged: [3]byte{},
|
||||||
|
Checksum: 0,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Module) buildSignaturePayload(content string, salt, timestampMillis int64) ([]byte, error) {
|
||||||
|
buf := make([]byte, 0, 64+len(content))
|
||||||
|
w := func(b []byte) { buf = append(buf, b...) }
|
||||||
|
|
||||||
|
// Header
|
||||||
|
w(int32Bytes(1))
|
||||||
|
|
||||||
|
// SignedMessageLink: sender UUID, session UUID, index
|
||||||
|
w(uuidBytes(m.Account.Uuid))
|
||||||
|
w(uuidBytes(m.sessionUUID))
|
||||||
|
w(int32Bytes(m.msgIndex))
|
||||||
|
|
||||||
|
// SignedMessageBody: salt, timestamp seconds, content
|
||||||
|
w(int64Bytes(uint64(salt)))
|
||||||
|
w(int64Bytes(uint64(timestampMillis / 1000)))
|
||||||
|
contentBytes := []byte(content)
|
||||||
|
w(int32Bytes(int32(len(contentBytes))))
|
||||||
|
w(contentBytes)
|
||||||
|
|
||||||
|
// LastSeenMessages: empty
|
||||||
|
w(int32Bytes(0))
|
||||||
|
|
||||||
|
return buf, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parsePrivateKey(s string) (*rsa.PrivateKey, error) {
|
||||||
|
block, _ := pem.Decode([]byte(s))
|
||||||
|
if block != nil {
|
||||||
|
k, err := x509.ParsePKCS8PrivateKey(block.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
k, err = x509.ParsePKCS1PrivateKey(block.Bytes)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if key, ok := k.(*rsa.PrivateKey); ok {
|
||||||
|
return key, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("not an RSA private key")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Some implementations return raw base64 DER instead of PEM
|
||||||
|
raw, err := base64.StdEncoding.DecodeString(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
k, err := x509.ParsePKCS8PrivateKey(raw)
|
||||||
|
if err != nil {
|
||||||
|
k, err = x509.ParsePKCS1PrivateKey(raw)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
key, ok := k.(*rsa.PrivateKey)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("not an RSA private key")
|
||||||
|
}
|
||||||
|
return key, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func int32Bytes(v int32) []byte {
|
||||||
|
b := make([]byte, 4)
|
||||||
|
binary.BigEndian.PutUint32(b, uint32(v))
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func int64Bytes(v uint64) []byte {
|
||||||
|
b := make([]byte, 8)
|
||||||
|
binary.BigEndian.PutUint64(b, v)
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func uuidBytes(u uuid.UUID) []byte {
|
||||||
|
b := make([]byte, 16)
|
||||||
|
copy(b, u[:])
|
||||||
|
return b
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package keepalive
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/proto"
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/proto_generated/v1_21_8"
|
||||||
|
)
|
||||||
|
|
||||||
|
type mod struct {
|
||||||
|
*proto.Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mod) onKeepAlive(p *v1_21_8.PlayToClientPacketKeepAlive) (err error) {
|
||||||
|
return m.Send(&v1_21_8.PlayToServerPacketKeepAlive{KeepAliveId: p.KeepAliveId})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start begins responding to keep-alive packets on the connection.
|
||||||
|
func Start(c *proto.Conn) {
|
||||||
|
m := &mod{Conn: c}
|
||||||
|
m.RegisterUntilLatest(m.onKeepAlive)
|
||||||
|
}
|
||||||
@@ -0,0 +1,178 @@
|
|||||||
|
package world
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/level"
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/proto"
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/proto_generated/v1_21_11"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ChunkNotLoadedErr = errors.New("chunk not loaded")
|
||||||
|
OutOfBoundsErr = errors.New("coordinates out of bounds")
|
||||||
|
)
|
||||||
|
|
||||||
|
// ChunkPos identifies a chunk column.
|
||||||
|
type ChunkPos struct {
|
||||||
|
X, Z int32
|
||||||
|
}
|
||||||
|
|
||||||
|
// World stores a shareable in-memory view of the Minecraft world.
|
||||||
|
// Multiple bots can subscribe to the same World and update it from their
|
||||||
|
// respective connections.
|
||||||
|
type World struct {
|
||||||
|
mu sync.RWMutex
|
||||||
|
chunks map[ChunkPos]*level.Chunk
|
||||||
|
version string
|
||||||
|
minY int
|
||||||
|
height int
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWorld creates a new World for the given protocol version and vertical
|
||||||
|
// bounds. For a 1.21 overworld use minY=-64 and height=384.
|
||||||
|
func NewWorld(version string, minY, height int) *World {
|
||||||
|
return &World{
|
||||||
|
chunks: make(map[ChunkPos]*level.Chunk),
|
||||||
|
version: version,
|
||||||
|
minY: minY,
|
||||||
|
height: height,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBlock returns the global block state ID at the given world coordinates.
|
||||||
|
func (w *World) GetBlock(x, y, z int32) (stateId int32, err error) {
|
||||||
|
w.mu.RLock()
|
||||||
|
defer w.mu.RUnlock()
|
||||||
|
return w.getBlockLocked(x, y, z)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *World) getBlockLocked(x, y, z int32) (stateId int32, err error) {
|
||||||
|
chunkX := floorDiv(x, level.ChunkWidth)
|
||||||
|
chunkZ := floorDiv(z, level.ChunkWidth)
|
||||||
|
chunk, ok := w.chunks[ChunkPos{chunkX, chunkZ}]
|
||||||
|
if !ok {
|
||||||
|
err = ChunkNotLoadedErr
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sectionIndex := (int(y) - w.minY) / level.ChunkWidth
|
||||||
|
if sectionIndex < 0 || sectionIndex >= len(chunk.Sections) {
|
||||||
|
err = OutOfBoundsErr
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
lx, ly, lz := blockToLocal(x, y, z)
|
||||||
|
index := ly*level.ChunkWidth*level.ChunkWidth + lz*level.ChunkWidth + lx
|
||||||
|
return chunk.Sections[sectionIndex].Blocks.Get(index)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetBlock updates the global block state ID at the given world coordinates.
|
||||||
|
func (w *World) SetBlock(x, y, z int32, stateId int32) (err error) {
|
||||||
|
w.mu.Lock()
|
||||||
|
defer w.mu.Unlock()
|
||||||
|
|
||||||
|
chunkX := floorDiv(x, level.ChunkWidth)
|
||||||
|
chunkZ := floorDiv(z, level.ChunkWidth)
|
||||||
|
chunk, ok := w.chunks[ChunkPos{chunkX, chunkZ}]
|
||||||
|
if !ok {
|
||||||
|
return ChunkNotLoadedErr
|
||||||
|
}
|
||||||
|
|
||||||
|
sectionIndex := (int(y) - w.minY) / level.ChunkWidth
|
||||||
|
if sectionIndex < 0 || sectionIndex >= len(chunk.Sections) {
|
||||||
|
return OutOfBoundsErr
|
||||||
|
}
|
||||||
|
|
||||||
|
lx, ly, lz := blockToLocal(x, y, z)
|
||||||
|
index := ly*level.ChunkWidth*level.ChunkWidth + lz*level.ChunkWidth + lx
|
||||||
|
return chunk.Sections[sectionIndex].Blocks.Set(index, stateId)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Module subscribes to chunk and block-change packets and updates a shared
|
||||||
|
// World. Multiple Modules can point to the same World.
|
||||||
|
type Module struct {
|
||||||
|
*proto.Conn
|
||||||
|
world *World
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBlock returns the block state ID at the given world coordinates via the
|
||||||
|
// shared World.
|
||||||
|
func (m *Module) GetBlock(x, y, z int32) (int32, error) {
|
||||||
|
return m.world.GetBlock(x, y, z)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start registers packet handlers on c that update the shared World w.
|
||||||
|
func Start(c *proto.Conn, w *World) *Module {
|
||||||
|
m := &Module{Conn: c, world: w}
|
||||||
|
m.RegisterUntilLatest(m.onMapChunk)
|
||||||
|
m.RegisterUntilLatest(m.onBlockChange)
|
||||||
|
m.RegisterUntilLatest(m.onMultiBlockChange)
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Module) onMapChunk(p *v1_21_11.PlayToClientPacketMapChunk) (err error) {
|
||||||
|
chunk, err := level.ReadChunkFromChunkPacketData(
|
||||||
|
bytes.NewReader(p.ChunkData.Val),
|
||||||
|
m.world.version,
|
||||||
|
m.world.height,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
m.world.mu.Lock()
|
||||||
|
m.world.chunks[ChunkPos{p.X, p.Z}] = chunk
|
||||||
|
m.world.mu.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Module) onBlockChange(p *v1_21_11.PlayToClientPacketBlockChange) (err error) {
|
||||||
|
return m.world.SetBlock(p.Location.X, int32(p.Location.Y), p.Location.Z, p.Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Module) onMultiBlockChange(p *v1_21_11.PlayToClientPacketMultiBlockChange) (err error) {
|
||||||
|
chunkX := p.ChunkCoordinates.X
|
||||||
|
chunkZ := p.ChunkCoordinates.Z
|
||||||
|
sectionY := p.ChunkCoordinates.Y
|
||||||
|
|
||||||
|
for _, record := range p.Records {
|
||||||
|
packedPos := record & 0xFFF
|
||||||
|
stateId := record >> 12
|
||||||
|
|
||||||
|
localX := (packedPos >> 8) & 0xF
|
||||||
|
localY := (packedPos >> 4) & 0xF
|
||||||
|
localZ := packedPos & 0xF
|
||||||
|
|
||||||
|
x := chunkX*level.ChunkWidth + int32(localX)
|
||||||
|
y := sectionY*level.ChunkWidth + int32(localY)
|
||||||
|
z := chunkZ*level.ChunkWidth + int32(localZ)
|
||||||
|
|
||||||
|
if err = m.world.SetBlock(x, y, z, stateId); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func floorDiv(a, b int32) int32 {
|
||||||
|
return (a - ((a%b + b) % b)) / b
|
||||||
|
}
|
||||||
|
|
||||||
|
func blockToLocal(x, y, z int32) (lx, ly, lz int32) {
|
||||||
|
lx = x % level.ChunkWidth
|
||||||
|
if lx < 0 {
|
||||||
|
lx += level.ChunkWidth
|
||||||
|
}
|
||||||
|
ly = y % level.ChunkWidth
|
||||||
|
if ly < 0 {
|
||||||
|
ly += level.ChunkWidth
|
||||||
|
}
|
||||||
|
lz = z % level.ChunkWidth
|
||||||
|
if lz < 0 {
|
||||||
|
lz += level.ChunkWidth
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/data"
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/proto"
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/proto_base"
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/server"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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
|
||||||
|
// version must be a vaild minecraft version eg 1.21.11 if left empty it will be detected with a status-request.
|
||||||
|
func Connect(ctx context.Context, addr string, version string) (ret *proto.Conn, err error) {
|
||||||
|
var protocolVersion int32
|
||||||
|
if version == "" {
|
||||||
|
var s server.StatusResponse
|
||||||
|
s, err = Status(ctx, addr)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if s.Version.Protocol == 0 {
|
||||||
|
err = errors.New("server didnt return protocol version")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
protocolVersion = s.Version.Protocol
|
||||||
|
} else {
|
||||||
|
var v data.VersionInfo
|
||||||
|
v, err = data.LookUpVersionByName(version)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
protocolVersion = v.Version
|
||||||
|
}
|
||||||
|
ret, err = ConnectVersionLess(ctx, addr)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = ret.SetProtocolVersion(protocolVersion)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 ConnectVersionLess(ctx context.Context, addr string) (ret *proto.Conn, err error) {
|
||||||
|
if ctx == nil {
|
||||||
|
ctx = context.Background()
|
||||||
|
}
|
||||||
|
ret = proto.NewConnCtx(ctx)
|
||||||
|
ret.Actor = proto_base.Client
|
||||||
|
ret.Conn, err = (&net.Dialer{}).DialContext(ctx, "tcp", addr)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ret.R = ret.Conn
|
||||||
|
ret.W = ret.Conn
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConnectWithConn wraps an existing net.Conn into a proto.Conn with the given
|
||||||
|
// Minecraft version. The version must be non-empty.
|
||||||
|
func ConnectWithConn(ctx context.Context, conn net.Conn, version string) (ret *proto.Conn, err error) {
|
||||||
|
if ctx == nil {
|
||||||
|
ctx = context.Background()
|
||||||
|
}
|
||||||
|
ret = proto.NewConnCtx(ctx)
|
||||||
|
ret.Actor = proto_base.Client
|
||||||
|
ret.Conn = conn
|
||||||
|
ret.R = conn
|
||||||
|
ret.W = conn
|
||||||
|
v, err := data.LookUpVersionByName(version)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ret.ProtocolVersion = v.Version
|
||||||
|
ret.Version = version
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -1,21 +1,25 @@
|
|||||||
package modules
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/admin-else/strom/event"
|
"git.anygate.cloud/anygatecloud/strom/mc/event"
|
||||||
"github.com/admin-else/strom/proto"
|
"git.anygate.cloud/anygatecloud/strom/mc/proto"
|
||||||
"github.com/admin-else/strom/proto_base"
|
"git.anygate.cloud/anygatecloud/strom/mc/proto_base"
|
||||||
"github.com/admin-else/strom/proto_generated/v1_21_8"
|
"git.anygate.cloud/anygatecloud/strom/mc/proto_generated/v1_21_8"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ConfigIgnorer struct {
|
type ConfigIgnorer struct {
|
||||||
*proto.Conn
|
*proto.Conn
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ConfigIgnorer) Default(event any) (err error) {
|
func (c *ConfigIgnorer) OnStart() (err error) {
|
||||||
fmt.Printf("%#v\n", event)
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ConfigIgnorer) Default(e event.Anything) (err error) {
|
||||||
|
c.Log.Debug("Config ignorer", "packet", fmt.Sprintf("%#v", e))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -23,7 +27,7 @@ func (c *ConfigIgnorer) OnKnownPacks(packet *v1_21_8.ConfigurationToClientPacket
|
|||||||
return c.Send(&v1_21_8.ConfigurationToServerPacketCommonSelectKnownPacks{Packs: packet.Packs})
|
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{})
|
err = c.Send(&v1_21_8.ConfigurationToServerPacketFinishConfiguration{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@@ -43,8 +47,16 @@ func (c *ConfigIgnorer) OnKeepAlive(packet *v1_21_8.ConfigurationToClientPacketK
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IgnoreConfig acknowledges configuration packets on the connection and transitions it to the Play state once configuration is complete.
|
||||||
func IgnoreConfig(c *proto.Conn) (err error) {
|
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 {
|
if err != nil {
|
||||||
err = errors.Join(err, errors.New("failed to ignore config"))
|
err = errors.Join(err, errors.New("failed to ignore config"))
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DNSCheckFunc func(addr string) error
|
||||||
|
|
||||||
|
// 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(ctx context.Context, in string, f DNSCheckFunc) (connectTo string, host string, portN uint16, err error) {
|
||||||
|
if f == nil {
|
||||||
|
f = func(addr string) error { return nil }
|
||||||
|
}
|
||||||
|
if ctx == nil {
|
||||||
|
ctx = context.Background()
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://mcsrc.dev/1/26.1-pre-1/net/minecraft/client/multiplayer/resolver/ServerAddress#L34
|
||||||
|
host, port, err := net.SplitHostPort(in)
|
||||||
|
if err != nil {
|
||||||
|
if addrErr, ok := errors.AsType[*net.AddrError](err); ok && addrErr.Err == "missing port in address" {
|
||||||
|
err = nil
|
||||||
|
host = in
|
||||||
|
port = "25565"
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
porti, err := strconv.Atoi(port)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = f(host)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
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(ctx, 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
|
||||||
|
}
|
||||||
@@ -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 := DoDNSChecked(nil, c.Input, nil)
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/proto"
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/proto_base"
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/proto_generated/v1_21_8"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MakeHandshakePacket creates a handshake packet using the connection's remote address as the server host.
|
||||||
|
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
|
||||||
|
}
|
||||||
@@ -0,0 +1,337 @@
|
|||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/api"
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/crypto"
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/event"
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/proto"
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/proto_base"
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/proto_generated/v1_16_5"
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/proto_generated/v1_21_8"
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/proto_generated/v1_8"
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/proto_generated/v26_2"
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/text"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LoginClient struct {
|
||||||
|
*proto.Conn
|
||||||
|
Account *api.Account
|
||||||
|
ServerHost string
|
||||||
|
ServerPort uint16
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k KickedDuringLoginErr) Error() string {
|
||||||
|
return k.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *LoginClient) OnDefault(event event.Unhandled) (err error) {
|
||||||
|
if _, ok := event.Val.(proto_base.EncodeDecodeAble); !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
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(packet *v1_8.LoginToClientPacketCompress) (err error) {
|
||||||
|
s.SetCompressionThreshold(packet.Threshold)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *LoginClient) OnEncryptV1_8(packet *v1_8.LoginToClientPacketEncryptionBegin) (err error) {
|
||||||
|
return s.doEncrypt(packet.ServerId, packet.PublicKey, packet.VerifyToken, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *LoginClient) OnEncrypt(packet *v1_21_8.LoginToClientPacketEncryptionBegin) (err error) {
|
||||||
|
return s.doEncrypt(packet.ServerId, packet.PublicKey, packet.VerifyToken, packet.ShouldAuthenticate)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *LoginClient) doEncrypt(serverId string, publicKey []byte, verifyToken []byte, shouldAuthenticate bool) (err error) {
|
||||||
|
sharedSecret := make([]byte, 16)
|
||||||
|
_, _ = rand.Read(sharedSecret) //never fails
|
||||||
|
|
||||||
|
if shouldAuthenticate {
|
||||||
|
if s.Account.Ygg == "" {
|
||||||
|
err = NoTokenErr
|
||||||
|
return
|
||||||
|
}
|
||||||
|
hash := crypto.AuthDigest([]byte(serverId), sharedSecret, publicKey)
|
||||||
|
err = s.Account.JoinServer(hash)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pubAny, err := x509.ParsePKIXPublicKey(publicKey)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pub, ok := pubAny.(*rsa.PublicKey)
|
||||||
|
if !ok {
|
||||||
|
err = BadPublicKeyTypeErr
|
||||||
|
return
|
||||||
|
}
|
||||||
|
verifyTokenEnc, err := rsa.EncryptPKCS1v15(rand.Reader, pub, verifyToken)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sharedSecretEnc, err := rsa.EncryptPKCS1v15(rand.Reader, pub, sharedSecret)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = s.Send(&v1_21_8.LoginToServerPacketEncryptionBegin{SharedSecret: sharedSecretEnc, VerifyToken: verifyTokenEnc})
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = s.SetSecret(sharedSecret)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *LoginClient) OnDisconnect(packet *v1_8.LoginToClientPacketDisconnect) (err error) {
|
||||||
|
var reason text.Component
|
||||||
|
err = json.Unmarshal([]byte(packet.Reason), &reason)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = KickedDuringLoginErr{reason}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *LoginClient) OnSuccessV1_8(success *v1_8.LoginToClientPacketSuccess) (err error) {
|
||||||
|
s.GivenAccount = &v1_21_8.LoginToClientPacketSuccess{
|
||||||
|
Uuid: uuid.MustParse(success.Uuid),
|
||||||
|
Username: success.Username,
|
||||||
|
}
|
||||||
|
s.SetState(proto_base.Play)
|
||||||
|
err = event.HandlerDoneErr{}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *LoginClient) OnSuccess(success *v1_21_8.LoginToClientPacketSuccess) (err error) {
|
||||||
|
s.GivenAccount = success
|
||||||
|
if s.ProtocolVersion >= 764 {
|
||||||
|
err = s.Send(&v1_21_8.LoginToServerPacketLoginAcknowledged{})
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.SetState(proto_base.Configuration)
|
||||||
|
} else {
|
||||||
|
s.SetState(proto_base.Play)
|
||||||
|
}
|
||||||
|
err = event.HandlerDoneErr{}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *LoginClient) OnSuccess26_2(success *v26_2.LoginToClientPacketSuccess) (err error) {
|
||||||
|
return s.OnSuccess(&v1_21_8.LoginToClientPacketSuccess{
|
||||||
|
Uuid: success.Uuid,
|
||||||
|
Username: success.Username,
|
||||||
|
Properties: success.Properties,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *LoginClient) OnSuccess1_16_5(success *v1_16_5.LoginToClientPacketSuccess) (err error) {
|
||||||
|
return s.OnSuccess(&v1_21_8.LoginToClientPacketSuccess{
|
||||||
|
Uuid: success.Uuid,
|
||||||
|
Username: success.Username,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *LoginClient) OnStart() (err error) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoginRaw performs the login handshake on an already-established connection without DNS resolution or configuration handling.
|
||||||
|
// The handshake address is derived from the connection's RemoteAddr.
|
||||||
|
func LoginRaw(c *proto.Conn, account *api.Account) (err error) {
|
||||||
|
return LoginRawAddr(c, account, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoginRawAddr performs the login handshake on an already-established connection using the given address for the handshake packet.
|
||||||
|
// If hostAddr is empty, the handshake address is derived from the connection's RemoteAddr.
|
||||||
|
func LoginRawAddr(c *proto.Conn, account *api.Account, hostAddr string) (err error) {
|
||||||
|
lc := &LoginClient{
|
||||||
|
Conn: c,
|
||||||
|
Account: account,
|
||||||
|
}
|
||||||
|
lc.RegisterCritical(lc.OnDefault)
|
||||||
|
lc.RegisterCritical(lc.OnClose)
|
||||||
|
lc.RegisterUntil("26.2", lc.OnCompress, lc.OnDisconnect, lc.OnEncrypt)
|
||||||
|
lc.RegisterUntil("26.1", lc.OnSuccess)
|
||||||
|
lc.RegisterUntilLatest(lc.OnSuccess26_2)
|
||||||
|
lc.RegisterUntil("1.14.4", lc.OnEncryptV1_8, lc.OnSuccessV1_8)
|
||||||
|
lc.RegisterUntil("1.16.5", lc.OnSuccess1_16_5)
|
||||||
|
|
||||||
|
var p proto_base.EncodeDecodeAble
|
||||||
|
if hostAddr != "" {
|
||||||
|
p, err = MakeHandshakePacketAddr(lc.Conn, proto_base.Login, hostAddr)
|
||||||
|
} else {
|
||||||
|
p, err = MakeHandshakePacket(lc.Conn, proto_base.Login)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = lc.Send(p)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
lc.SetState(proto_base.Login)
|
||||||
|
if lc.Conn.ProtocolVersion < 759 {
|
||||||
|
err = lc.Send(&v1_8.LoginToServerPacketLoginStart{Username: lc.Account.Name})
|
||||||
|
} else {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithoutDns returns a login setting that disables SRV DNS resolution during login.
|
||||||
|
func WithoutDns() func(ls *loginSettings) {
|
||||||
|
return func(ls *loginSettings) {
|
||||||
|
ls.NoDns = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithContext returns a login setting that sets the context used for the connection dial and DNS resolution.
|
||||||
|
func WithContext(ctx context.Context) func(ls *loginSettings) {
|
||||||
|
return func(ls *loginSettings) {
|
||||||
|
ls.Ctx = ctx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithVersion returns a login setting that forces a specific Minecraft version string for the connection.
|
||||||
|
func WithVersion(version string) func(ls *loginSettings) {
|
||||||
|
return func(ls *loginSettings) {
|
||||||
|
ls.Version = version
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithIgnoreConfig() func(ls *loginSettings) {
|
||||||
|
return func(ls *loginSettings) {
|
||||||
|
ls.IgnoreConfig = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DialFunc is a context-aware dial function used by Login to establish a TCP connection.
|
||||||
|
type DialFunc func(ctx context.Context, network, addr string) (net.Conn, error)
|
||||||
|
|
||||||
|
// WithConn returns a login setting that uses an already-established net.Conn instead of dialling.
|
||||||
|
// WithVersion must also be set; auto-detection is not possible with a pre-established connection.
|
||||||
|
func WithConn(conn net.Conn) func(ls *loginSettings) {
|
||||||
|
return func(ls *loginSettings) {
|
||||||
|
ls.Conn = conn
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithDialer returns a login setting that uses the given dial function instead of net.Dialer.
|
||||||
|
// Auto version detection via status ping is still supported when no explicit version is set.
|
||||||
|
func WithDialer(d DialFunc) func(ls *loginSettings) {
|
||||||
|
return func(ls *loginSettings) {
|
||||||
|
ls.Dialer = d
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrWithConnRequiresVersion = errors.New("WithVersion is required when using WithConn")
|
||||||
|
ErrWithConnNoStatus = errors.New("auto version detection is not supported with WithConn")
|
||||||
|
)
|
||||||
|
|
||||||
|
type loginSettings struct {
|
||||||
|
NoDns bool
|
||||||
|
Ctx context.Context
|
||||||
|
Version string
|
||||||
|
IgnoreConfig bool
|
||||||
|
Conn net.Conn
|
||||||
|
Dialer DialFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
// Login resolves the address with SRV DNS, connects, performs the login handshake, and handles the configuration phase, returning an authenticated connection ready for play.
|
||||||
|
func Login(connectTo string, account *api.Account, settings ...func(ls *loginSettings)) (c *proto.Conn, err error) {
|
||||||
|
ls := &loginSettings{}
|
||||||
|
|
||||||
|
for _, setting := range settings {
|
||||||
|
setting(ls)
|
||||||
|
}
|
||||||
|
|
||||||
|
hostAddr := connectTo
|
||||||
|
|
||||||
|
if ls.Conn != nil {
|
||||||
|
if ls.Version == "" {
|
||||||
|
return nil, ErrWithConnRequiresVersion
|
||||||
|
}
|
||||||
|
c, err = ConnectWithConn(ls.Ctx, ls.Conn, ls.Version)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else if ls.Dialer != nil {
|
||||||
|
if ls.Version == "" {
|
||||||
|
return nil, ErrWithConnRequiresVersion
|
||||||
|
}
|
||||||
|
if !ls.NoDns {
|
||||||
|
connectTo, _, _, err = DoDNSChecked(ls.Ctx, connectTo, nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var conn net.Conn
|
||||||
|
conn, err = ls.Dialer(ls.Ctx, "tcp", connectTo)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c, err = ConnectWithConn(ls.Ctx, conn, ls.Version)
|
||||||
|
if err != nil {
|
||||||
|
conn.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if !ls.NoDns {
|
||||||
|
connectTo, _, _, err = DoDNSChecked(ls.Ctx, connectTo, nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c, err = Connect(ls.Ctx, connectTo, ls.Version)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = LoginRawAddr(c, account, hostAddr)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !ls.IgnoreConfig && c.ProtocolVersion >= 764 {
|
||||||
|
err = IgnoreConfig(c)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
package client_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/data"
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/proto"
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/proto_base"
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/proto_generated/v1_21_8"
|
||||||
|
)
|
||||||
|
|
||||||
|
func assertClientSendConvertible(t *testing.T, srcType reflect.Type, name string, state proto_base.State, direction proto_base.Direction, targetVersion int32) {
|
||||||
|
t.Helper()
|
||||||
|
targetInfo, ok := proto.LookupPacketInfoByNameProtocolVersionStateAndDirection(name, targetVersion, state, direction)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("packet %s not found for version %d", name, targetVersion)
|
||||||
|
}
|
||||||
|
targetType := reflect.TypeOf(targetInfo.Type)
|
||||||
|
if !proto.SmartConvertibleTo(srcType, targetType) {
|
||||||
|
t.Fatalf("%s is not smart-convertible from v1_21_8 to version %d", name, targetVersion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertClientReceiveConvertible(t *testing.T, dstType reflect.Type, name string, state proto_base.State, direction proto_base.Direction, targetVersion int32) {
|
||||||
|
t.Helper()
|
||||||
|
targetInfo, ok := proto.LookupPacketInfoByNameProtocolVersionStateAndDirection(name, targetVersion, state, direction)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("packet %s not found for version %d", name, targetVersion)
|
||||||
|
}
|
||||||
|
targetType := reflect.TypeOf(targetInfo.Type)
|
||||||
|
if !proto.SmartConvertibleTo(targetType, dstType) {
|
||||||
|
t.Fatalf("%s is not smart-convertible from version %d to v1_21_8", name, targetVersion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoginClientSendAssumptions(t *testing.T) {
|
||||||
|
v26_1Version := data.MustLookupProtocolVersion("26.1")
|
||||||
|
|
||||||
|
// Packets sent by LoginClient as v1_21_8.
|
||||||
|
assertClientSendConvertible(t, reflect.TypeFor[*v1_21_8.HandshakingToServerPacketSetProtocol](), "set_protocol", proto_base.Handshaking, proto_base.ToServer, v26_1Version)
|
||||||
|
assertClientSendConvertible(t, reflect.TypeFor[*v1_21_8.LoginToServerPacketLoginStart](), "login_start", proto_base.Login, proto_base.ToServer, v26_1Version)
|
||||||
|
assertClientSendConvertible(t, reflect.TypeFor[*v1_21_8.LoginToServerPacketLoginAcknowledged](), "login_acknowledged", proto_base.Login, proto_base.ToServer, v26_1Version)
|
||||||
|
assertClientSendConvertible(t, reflect.TypeFor[*v1_21_8.LoginToServerPacketEncryptionBegin](), "encryption_begin", proto_base.Login, proto_base.ToServer, v26_1Version)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoginClientReceiveAssumptions(t *testing.T) {
|
||||||
|
v26_1Version := data.MustLookupProtocolVersion("26.1")
|
||||||
|
|
||||||
|
// Packets received by LoginClient via RegisterCriticalUntilLatest.
|
||||||
|
// login_success is excluded because 26.2 is not convertible.
|
||||||
|
assertClientReceiveConvertible(t, reflect.TypeFor[*v1_21_8.LoginToClientPacketCompress](), "compress", proto_base.Login, proto_base.ToClient, v26_1Version)
|
||||||
|
assertClientReceiveConvertible(t, reflect.TypeFor[*v1_21_8.LoginToClientPacketEncryptionBegin](), "encryption_begin", proto_base.Login, proto_base.ToClient, v26_1Version)
|
||||||
|
assertClientReceiveConvertible(t, reflect.TypeFor[*v1_21_8.LoginToClientPacketDisconnect](), "disconnect", proto_base.Login, proto_base.ToClient, v26_1Version)
|
||||||
|
}
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/event"
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/proto"
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/proto_base"
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/proto_generated/v1_21_8"
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/proto_generated/v1_8"
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/server"
|
||||||
|
)
|
||||||
|
|
||||||
|
type StatusClient struct {
|
||||||
|
*proto.Conn
|
||||||
|
Status string
|
||||||
|
PingSendTime, PingReceiveTime time.Time
|
||||||
|
DoPingRoundTripTime bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StatusClient) OnStatus(p *v1_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_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) {
|
||||||
|
return StatusRawWithVersion(ctx, addr, "1.21.8")
|
||||||
|
}
|
||||||
|
|
||||||
|
// StatusRawWithVersion is like StatusRaw but uses the given Minecraft version for the handshake.
|
||||||
|
func StatusRawWithVersion(ctx context.Context, addr, version string) (s *StatusClient, err error) {
|
||||||
|
c, err := ConnectVersionLess(ctx, addr)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer c.Close()
|
||||||
|
err = c.SetVersion(version)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s = &StatusClient{
|
||||||
|
Conn: c,
|
||||||
|
}
|
||||||
|
s.RegisterUntil("26.2", s.OnStatus, 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(ctx context.Context, addr string) (status server.StatusResponse, err error) {
|
||||||
|
s, err := StatusRaw(ctx, addr)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer s.Close()
|
||||||
|
err = json.Unmarshal([]byte(s.Status), &status)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Status resolves the status of the minecraft server at the given address.
|
||||||
|
func Status(ctx context.Context, addr string) (status server.StatusResponse, err error) {
|
||||||
|
addr, _, _, err = DoDNSChecked(ctx, addr, nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return StatusNoDns(ctx, addr)
|
||||||
|
}
|
||||||
@@ -28,6 +28,7 @@ package crypto
|
|||||||
import (
|
import (
|
||||||
"crypto/cipher"
|
"crypto/cipher"
|
||||||
"crypto/subtle"
|
"crypto/subtle"
|
||||||
|
"sync"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -37,12 +38,15 @@ type CFB8 struct {
|
|||||||
ivPos int
|
ivPos int
|
||||||
iv []byte
|
iv []byte
|
||||||
de bool
|
de bool
|
||||||
|
mutex sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewCFB8Decrypt creates a new CFB8 stream cipher in decrypt mode.
|
||||||
func NewCFB8Decrypt(c cipher.Block, iv []byte) *CFB8 {
|
func NewCFB8Decrypt(c cipher.Block, iv []byte) *CFB8 {
|
||||||
return newCFB8(c, iv, true)
|
return newCFB8(c, iv, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewCFB8Encrypt creates a new CFB8 stream cipher in encrypt mode.
|
||||||
func NewCFB8Encrypt(c cipher.Block, iv []byte) *CFB8 {
|
func NewCFB8Encrypt(c cipher.Block, iv []byte) *CFB8 {
|
||||||
return newCFB8(c, iv, false)
|
return newCFB8(c, iv, false)
|
||||||
}
|
}
|
||||||
@@ -59,6 +63,8 @@ func newCFB8(c cipher.Block, iv []byte, de bool) *CFB8 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (cf *CFB8) XORKeyStream(dst, src []byte) {
|
func (cf *CFB8) XORKeyStream(dst, src []byte) {
|
||||||
|
cf.mutex.Lock()
|
||||||
|
defer cf.mutex.Unlock()
|
||||||
if len(src) == 0 {
|
if len(src) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -6,9 +6,9 @@ import (
|
|||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
// UUIDv3 Variation of google.uuid.NewHash
|
// uuidV3 Variation of google.uuid.NewHash
|
||||||
// We need this because we want a 14-byte namespace and Google forces 16
|
// We need this because we want a 14-byte namespace and Google forces 16
|
||||||
func UUIDv3(space []byte, data []byte) uuid.UUID {
|
func uuidV3(space []byte, data []byte) uuid.UUID {
|
||||||
h := md5.New()
|
h := md5.New()
|
||||||
h.Reset()
|
h.Reset()
|
||||||
h.Write(space) //nolint:errcheck
|
h.Write(space) //nolint:errcheck
|
||||||
@@ -21,6 +21,7 @@ func UUIDv3(space []byte, data []byte) uuid.UUID {
|
|||||||
return u
|
return u
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FromOfflinePlayer generates a UUID for an offline-mode (cracked) player from their display name.
|
||||||
func FromOfflinePlayer(displayName string) uuid.UUID {
|
func FromOfflinePlayer(displayName string) uuid.UUID {
|
||||||
return UUIDv3([]byte("OfflinePlayer:"), []byte(displayName))
|
return uuidV3([]byte("OfflinePlayer:"), []byte(displayName))
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
package data
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
// [
|
||||||
|
//
|
||||||
|
// {
|
||||||
|
// "id": 0,
|
||||||
|
// "name": "badlands",
|
||||||
|
// "category": "mesa",
|
||||||
|
// "temperature": 2.0,
|
||||||
|
// "has_precipitation": false,
|
||||||
|
// "dimension": "overworld",
|
||||||
|
// "displayName": "Badlands",
|
||||||
|
// "color": 7254527
|
||||||
|
// },
|
||||||
|
|
||||||
|
type Biome struct {
|
||||||
|
Id int32
|
||||||
|
Name string
|
||||||
|
Category string
|
||||||
|
Temperature float64
|
||||||
|
HasPrecipitation bool
|
||||||
|
Dimension string
|
||||||
|
DisplayName string
|
||||||
|
Color int
|
||||||
|
}
|
||||||
|
|
||||||
|
var BiomesCache = make(map[string][]*Biome)
|
||||||
|
|
||||||
|
// BiomesForVersion returns the biome definitions for the given Minecraft version, caching them after the first load.
|
||||||
|
func BiomesForVersion(v string) (ret []*Biome) {
|
||||||
|
var ok bool
|
||||||
|
if ret, ok = BiomesCache[v]; ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var b []*Biome
|
||||||
|
must(LoadVersionedJson(v, "biomes", &b))
|
||||||
|
BiomesCache[v] = b
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// LookupBiomeById returns the biome with the given numeric ID in the given version.
|
||||||
|
func LookupBiomeById(version string, id int32) (biome *Biome, ok bool) {
|
||||||
|
for _, b := range BiomesForVersion(version) {
|
||||||
|
if b.Id == id {
|
||||||
|
biome = b
|
||||||
|
ok = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var UnknownBiomeNameErr = errors.New("unknown biome Name")
|
||||||
|
|
||||||
|
// LookupBiomeByName returns the biome with the given name in the given version.
|
||||||
|
func LookupBiomeByName(version string, name string) (biome *Biome, ok bool) {
|
||||||
|
for _, b := range BiomesForVersion(version) {
|
||||||
|
if b.Name == name {
|
||||||
|
biome = b
|
||||||
|
ok = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@ package data_test
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/admin-else/strom/data"
|
"git.anygate.cloud/anygatecloud/strom/mc/data"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestLookupBiomeById(t *testing.T) {
|
func TestLookupBiomeById(t *testing.T) {
|
||||||
@@ -0,0 +1,231 @@
|
|||||||
|
package data
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"slices"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// {
|
||||||
|
// "id": 0,
|
||||||
|
// "name": "air",
|
||||||
|
// "displayName": "Air",
|
||||||
|
// "hardness": 0.0,
|
||||||
|
// "resistance": 0.0,
|
||||||
|
// "stackSize": 64,
|
||||||
|
// "diggable": false,
|
||||||
|
// "material": "default",
|
||||||
|
// "transparent": true,
|
||||||
|
// "emitLight": 0,
|
||||||
|
// "filterLight": 0,
|
||||||
|
// "defaultState": 0,
|
||||||
|
// "minStateId": 0,
|
||||||
|
// "maxStateId": 0,
|
||||||
|
// "states": [],
|
||||||
|
// "drops": [],
|
||||||
|
// "boundingBox": "empty"
|
||||||
|
// },
|
||||||
|
|
||||||
|
type Block struct {
|
||||||
|
Id int
|
||||||
|
Name string
|
||||||
|
DisplayName string
|
||||||
|
Hardness float64
|
||||||
|
Resistance float64
|
||||||
|
StackSize int
|
||||||
|
Diggable bool
|
||||||
|
Material string
|
||||||
|
Transparent bool
|
||||||
|
EmitLight int
|
||||||
|
FilterLight int
|
||||||
|
DefaultState int32
|
||||||
|
MinStateId int32
|
||||||
|
MaxStateId int32
|
||||||
|
States []BlockState
|
||||||
|
Drops []int
|
||||||
|
BoundingBox string
|
||||||
|
}
|
||||||
|
|
||||||
|
type BlockState struct {
|
||||||
|
Name string
|
||||||
|
Type string
|
||||||
|
MinValue int32 // this is generated on load and used for state value calculations
|
||||||
|
NumValues int32 `json:"num_values"`
|
||||||
|
Values []string
|
||||||
|
}
|
||||||
|
|
||||||
|
var BlocksCache = make(map[string][]*Block)
|
||||||
|
var BlocksCacheIdMap = make(map[string]map[int32]*Block)
|
||||||
|
|
||||||
|
// BlocksForVersion returns the block definitions for the given Minecraft version, caching them after the first load.
|
||||||
|
func BlocksForVersion(v string) (ret []*Block) {
|
||||||
|
var ok bool
|
||||||
|
if ret, ok = BlocksCache[v]; ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var b []*Block
|
||||||
|
must(LoadVersionedJson(v, "blocks", &b))
|
||||||
|
BlocksCache[v] = b
|
||||||
|
for _, block := range b {
|
||||||
|
for i, state := range block.States {
|
||||||
|
if state.Type != "int" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
values := make([]int32, state.NumValues)
|
||||||
|
for j, stateValue := range state.Values {
|
||||||
|
n := util.MustT(strconv.Atoi(stateValue))
|
||||||
|
values[j] = int32(n)
|
||||||
|
}
|
||||||
|
block.States[i].MinValue = slices.Min(values)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// LookupBlockByStateId returns the block for the given global palette state ID in the given version.
|
||||||
|
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")
|
||||||
|
|
||||||
|
// LookupBlockByName returns the block with the given name in the given version.
|
||||||
|
func LookupBlockByName(version string, name string) (block *Block, ok bool) {
|
||||||
|
for _, b := range BlocksForVersion(version) {
|
||||||
|
if b.Name == name {
|
||||||
|
block = b
|
||||||
|
ok = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// StateIdFromBlocKAndStateMap returns the global palette state ID for the given block name and state property map in the given version.
|
||||||
|
func StateIdFromBlocKAndStateMap(version string, name string, stateMap map[string]string) (stateId int32, err error) {
|
||||||
|
b, ok := LookupBlockByName(version, name)
|
||||||
|
if !ok {
|
||||||
|
err = BlockNotFoundErr
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if stateMap == nil {
|
||||||
|
stateId = b.DefaultState
|
||||||
|
} else {
|
||||||
|
stateId, err = b.IdFromStateMap(stateMap)
|
||||||
|
}
|
||||||
|
if b.MinStateId > stateId || b.MaxStateId < stateId {
|
||||||
|
err = BlockStateOutOfRangeErr
|
||||||
|
}
|
||||||
|
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")
|
||||||
|
|
||||||
|
// FromBlockState converts a global palette state ID back into a block definition and a map of state properties.
|
||||||
|
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) - s.MinValue, 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
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package data_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.anygate.cloud/anygatecloud/strom/mc/data"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLookupBlockByStateId(t *testing.T) {
|
||||||
|
b, ok := data.LookupBlockByStateId("1.21.8", 0)
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("Block not found")
|
||||||
|
}
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBlockStateOutOfRangeBug(t *testing.T) {
|
||||||
|
id, err := data.StateIdFromBlocKAndStateMap("1.21.11", "wildflowers", map[string]string{"facing": "east", "flower_amount": "4"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Logf("State id: %d", id)
|
||||||
|
}
|
||||||
Executable
+27
@@ -0,0 +1,27 @@
|
|||||||
|
#!/usr/bin/sh
|
||||||
|
set -xeuo pipefail
|
||||||
|
|
||||||
|
# Copy minecraft-data from the local AnyGate checkout instead of cloning upstream.
|
||||||
|
# This is used when upstream does not yet have the target version or when we want
|
||||||
|
# to test local changes before pushing.
|
||||||
|
|
||||||
|
LOCAL_MINECRAFT_DATA="${LOCAL_MINECRAFT_DATA:-$HOME/src/anygate/minecraft-data}"
|
||||||
|
|
||||||
|
cd "$(dirname "$0")"
|
||||||
|
|
||||||
|
rm -rf minecraft-data/
|
||||||
|
mkdir -p minecraft-data/pc
|
||||||
|
|
||||||
|
cp "$LOCAL_MINECRAFT_DATA/data/dataPaths.json" minecraft-data/
|
||||||
|
|
||||||
|
copy-mc() {
|
||||||
|
cp -r "$LOCAL_MINECRAFT_DATA/data/pc/$1" minecraft-data/pc
|
||||||
|
}
|
||||||
|
|
||||||
|
copy-mc common
|
||||||
|
|
||||||
|
copy-mc 1.21.8
|
||||||
|
copy-mc 1.21.9
|
||||||
|
copy-mc 1.21.11
|
||||||
|
copy-mc 26.1
|
||||||
|
copy-mc 26.2
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package data
|
||||||
|
|
||||||
|
import (
|
||||||
|
"embed"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:generate ./download_minecraft_data.sh
|
||||||
|
|
||||||
|
//go:embed minecraft-data/pc minecraft-data/dataPaths.json
|
||||||
|
var MinecraftData embed.FS
|
||||||
|
|
||||||
|
// LoadJson reads a JSON file from the embedded minecraft-data FS and unmarshals it into data.
|
||||||
|
func LoadJson(path string, data any) (err error) {
|
||||||
|
b, err := MinecraftData.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = json.Unmarshal(b, data)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadVersionedJson loads a version-specific JSON data file from the embedded minecraft-data FS and unmarshals it into data.
|
||||||
|
func LoadVersionedJson(version, dataName string, data any) (err error) {
|
||||||
|
b, err := MinecraftData.ReadFile("minecraft-data/" + Paths.Data[version][dataName] + "/" + dataName + ".json")
|
||||||
|
if err != nil {
|
||||||
|
// If you get this error, you need to probably need to update the download script
|
||||||
|
return fmt.Errorf("minecraft-data/%s/%s/%s.json: %w", version, Paths.Data[version][dataName], dataName, err)
|
||||||
|
}
|
||||||
|
err = json.Unmarshal(b, data)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var UnknownMinecraftVersionError = errors.New("unknown minecraft version")
|
||||||
|
|
||||||
|
func must(err error) {
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,5 +9,5 @@ var Paths struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
must(LoadJson("minecraft-data/data/dataPaths.json", &Paths))
|
must(LoadJson("minecraft-data/dataPaths.json", &Paths))
|
||||||
}
|
}
|
||||||
Executable
+36
@@ -0,0 +1,36 @@
|
|||||||
|
#!/usr/bin/sh
|
||||||
|
set -xeuo pipefail
|
||||||
|
|
||||||
|
# Go get / install does not clone git submodules so we have to do it like this
|
||||||
|
|
||||||
|
cd "$(dirname "$0")"
|
||||||
|
|
||||||
|
rm -rf minecraft-data/
|
||||||
|
git clone --depth 1 git@github.com:admin-else/minecraft-data.git
|
||||||
|
mv minecraft-data minecraft-data-untrimmed
|
||||||
|
|
||||||
|
mkdir minecraft-data
|
||||||
|
mkdir minecraft-data/pc
|
||||||
|
|
||||||
|
mv minecraft-data-untrimmed/data/dataPaths.json minecraft-data
|
||||||
|
|
||||||
|
copy-mc() {
|
||||||
|
mv minecraft-data-untrimmed/data/pc/$1 minecraft-data/pc
|
||||||
|
}
|
||||||
|
|
||||||
|
copy-mc common
|
||||||
|
|
||||||
|
# Legacy versions
|
||||||
|
copy-mc 1.8
|
||||||
|
copy-mc 1.12.2
|
||||||
|
copy-mc 1.14.4
|
||||||
|
copy-mc 1.16.5
|
||||||
|
|
||||||
|
# Modern versions
|
||||||
|
copy-mc 1.21.8
|
||||||
|
copy-mc 1.21.9
|
||||||
|
copy-mc 1.21.11
|
||||||
|
copy-mc 26.1
|
||||||
|
copy-mc 26.2
|
||||||
|
|
||||||
|
rm -rf minecraft-data-untrimmed
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
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{}
|
||||||
|
|
||||||
|
// ItemsForVersion returns the item definitions for the given Minecraft version, caching them after the first load.
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// LookupItemById returns the item with the given numeric ID in the given version.
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// LookupItemByName returns the item with the given name in the given version.
|
||||||
|
func LookupItemByName(version string, name string) (item *Item, ok bool) {
|
||||||
|
for _, i := range ItemsForVersion(version) {
|
||||||
|
if i.Name == name {
|
||||||
|
item = i
|
||||||
|
ok = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
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,5 @@
|
|||||||
|
{
|
||||||
|
"minecraftVersion": "1.12.2",
|
||||||
|
"version":340,
|
||||||
|
"majorVersion": "1.12"
|
||||||
|
}
|
||||||
@@ -0,0 +1,953 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": 0,
|
||||||
|
"name": "ocean",
|
||||||
|
"category": "ocean",
|
||||||
|
"temperature": 0.5,
|
||||||
|
"precipitation": "rain",
|
||||||
|
"depth": -1,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Ocean",
|
||||||
|
"color": 112,
|
||||||
|
"rainfall": 0.5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": "plains",
|
||||||
|
"category": "plains",
|
||||||
|
"temperature": 0.8,
|
||||||
|
"precipitation": "rain",
|
||||||
|
"depth": 0.125,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Plains",
|
||||||
|
"color": 9286496,
|
||||||
|
"rainfall": 0.4,
|
||||||
|
"child": 129
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"name": "desert",
|
||||||
|
"category": "desert",
|
||||||
|
"temperature": 2,
|
||||||
|
"precipitation": "none",
|
||||||
|
"depth": 0.125,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Desert",
|
||||||
|
"color": 16421912,
|
||||||
|
"rainfall": 0,
|
||||||
|
"child": 130
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"name": "mountains",
|
||||||
|
"category": "extreme_hills",
|
||||||
|
"temperature": 0.2,
|
||||||
|
"precipitation": "rain",
|
||||||
|
"depth": 1,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Mountains",
|
||||||
|
"color": 6316128,
|
||||||
|
"rainfall": 0.3,
|
||||||
|
"child": 131
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 4,
|
||||||
|
"name": "forest",
|
||||||
|
"category": "forest",
|
||||||
|
"temperature": 0.7,
|
||||||
|
"precipitation": "rain",
|
||||||
|
"depth": 0.1,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Forest",
|
||||||
|
"color": 353825,
|
||||||
|
"rainfall": 0.8,
|
||||||
|
"child": 132
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 5,
|
||||||
|
"name": "taiga",
|
||||||
|
"category": "taiga",
|
||||||
|
"temperature": 0.25,
|
||||||
|
"precipitation": "rain",
|
||||||
|
"depth": 0.2,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Taiga",
|
||||||
|
"color": 747097,
|
||||||
|
"rainfall": 0.8,
|
||||||
|
"child": 133
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 6,
|
||||||
|
"name": "swamp",
|
||||||
|
"category": "swamp",
|
||||||
|
"temperature": 0.8,
|
||||||
|
"precipitation": "rain",
|
||||||
|
"depth": -0.2,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Swamp",
|
||||||
|
"color": 522674,
|
||||||
|
"rainfall": 0.9,
|
||||||
|
"child": 134
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 7,
|
||||||
|
"name": "river",
|
||||||
|
"category": "river",
|
||||||
|
"temperature": 0.5,
|
||||||
|
"precipitation": "rain",
|
||||||
|
"depth": -0.5,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "River",
|
||||||
|
"color": 255,
|
||||||
|
"rainfall": 0.5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 8,
|
||||||
|
"name": "nether_wastes",
|
||||||
|
"category": "nether",
|
||||||
|
"temperature": 2,
|
||||||
|
"precipitation": "none",
|
||||||
|
"depth": 0.1,
|
||||||
|
"climates": [
|
||||||
|
{
|
||||||
|
"temperature": 0,
|
||||||
|
"humidity": 0,
|
||||||
|
"altitude": 0,
|
||||||
|
"weirdness": 0,
|
||||||
|
"offset": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"dimension": "nether",
|
||||||
|
"displayName": "Nether Wastes",
|
||||||
|
"color": 12532539,
|
||||||
|
"rainfall": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 9,
|
||||||
|
"name": "the_end",
|
||||||
|
"category": "the_end",
|
||||||
|
"temperature": 0.5,
|
||||||
|
"precipitation": "none",
|
||||||
|
"depth": 0.1,
|
||||||
|
"dimension": "end",
|
||||||
|
"displayName": "The End",
|
||||||
|
"color": 8421631,
|
||||||
|
"rainfall": 0.5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 10,
|
||||||
|
"name": "frozen_ocean",
|
||||||
|
"category": "ocean",
|
||||||
|
"temperature": 0,
|
||||||
|
"precipitation": "snow",
|
||||||
|
"depth": -1,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Frozen Ocean",
|
||||||
|
"color": 7368918,
|
||||||
|
"rainfall": 0.5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 11,
|
||||||
|
"name": "frozen_river",
|
||||||
|
"category": "river",
|
||||||
|
"temperature": 0,
|
||||||
|
"precipitation": "snow",
|
||||||
|
"depth": -0.5,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Frozen River",
|
||||||
|
"color": 10526975,
|
||||||
|
"rainfall": 0.5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 12,
|
||||||
|
"name": "snowy_tundra",
|
||||||
|
"category": "icy",
|
||||||
|
"temperature": 0,
|
||||||
|
"precipitation": "snow",
|
||||||
|
"depth": 0.125,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Snowy Tundra",
|
||||||
|
"color": 16777215,
|
||||||
|
"rainfall": 0.5,
|
||||||
|
"child": 140
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 13,
|
||||||
|
"name": "snowy_mountains",
|
||||||
|
"category": "icy",
|
||||||
|
"temperature": 0,
|
||||||
|
"precipitation": "snow",
|
||||||
|
"depth": 0.45,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Snowy Mountains",
|
||||||
|
"color": 10526880,
|
||||||
|
"rainfall": 0.5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 14,
|
||||||
|
"name": "mushroom_fields",
|
||||||
|
"category": "mushroom",
|
||||||
|
"temperature": 0.9,
|
||||||
|
"precipitation": "rain",
|
||||||
|
"depth": 0.2,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Mushroom Fields",
|
||||||
|
"color": 16711935,
|
||||||
|
"rainfall": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 15,
|
||||||
|
"name": "mushroom_field_shore",
|
||||||
|
"category": "mushroom",
|
||||||
|
"temperature": 0.9,
|
||||||
|
"precipitation": "rain",
|
||||||
|
"depth": 0,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Mushroom Fields Shore",
|
||||||
|
"color": 10486015,
|
||||||
|
"rainfall": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 16,
|
||||||
|
"name": "beach",
|
||||||
|
"category": "beach",
|
||||||
|
"temperature": 0.8,
|
||||||
|
"precipitation": "rain",
|
||||||
|
"depth": 0,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Beach",
|
||||||
|
"color": 16440917,
|
||||||
|
"rainfall": 0.4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 17,
|
||||||
|
"name": "desert_hills",
|
||||||
|
"category": "desert",
|
||||||
|
"temperature": 2,
|
||||||
|
"precipitation": "none",
|
||||||
|
"depth": 0.45,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Desert Hills",
|
||||||
|
"color": 13786898,
|
||||||
|
"rainfall": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 18,
|
||||||
|
"name": "wooded_hills",
|
||||||
|
"category": "forest",
|
||||||
|
"temperature": 0.7,
|
||||||
|
"precipitation": "rain",
|
||||||
|
"depth": 0.45,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Wooded Hills",
|
||||||
|
"color": 2250012,
|
||||||
|
"rainfall": 0.8
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 19,
|
||||||
|
"name": "taiga_hills",
|
||||||
|
"category": "taiga",
|
||||||
|
"temperature": 0.25,
|
||||||
|
"precipitation": "rain",
|
||||||
|
"depth": 0.45,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Taiga Hills",
|
||||||
|
"color": 1456435,
|
||||||
|
"rainfall": 0.8
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 20,
|
||||||
|
"name": "mountain_edge",
|
||||||
|
"category": "extreme_hills",
|
||||||
|
"temperature": 0.2,
|
||||||
|
"precipitation": "rain",
|
||||||
|
"depth": 0.8,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Mountain Edge",
|
||||||
|
"color": 7501978,
|
||||||
|
"rainfall": 0.3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 21,
|
||||||
|
"name": "jungle",
|
||||||
|
"category": "jungle",
|
||||||
|
"temperature": 0.95,
|
||||||
|
"precipitation": "rain",
|
||||||
|
"depth": 0.1,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Jungle",
|
||||||
|
"color": 5470985,
|
||||||
|
"rainfall": 0.9,
|
||||||
|
"child": 149
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 22,
|
||||||
|
"name": "jungle_hills",
|
||||||
|
"category": "jungle",
|
||||||
|
"temperature": 0.95,
|
||||||
|
"precipitation": "rain",
|
||||||
|
"depth": 0.45,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Jungle Hills",
|
||||||
|
"color": 2900485,
|
||||||
|
"rainfall": 0.9
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 23,
|
||||||
|
"name": "jungle_edge",
|
||||||
|
"category": "jungle",
|
||||||
|
"temperature": 0.95,
|
||||||
|
"precipitation": "rain",
|
||||||
|
"depth": 0.1,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Jungle Edge",
|
||||||
|
"color": 6458135,
|
||||||
|
"rainfall": 0.8,
|
||||||
|
"child": 151
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 24,
|
||||||
|
"name": "deep_ocean",
|
||||||
|
"category": "ocean",
|
||||||
|
"temperature": 0.5,
|
||||||
|
"precipitation": "rain",
|
||||||
|
"depth": -1.8,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Deep Ocean",
|
||||||
|
"color": 48,
|
||||||
|
"rainfall": 0.5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 25,
|
||||||
|
"name": "stone_shore",
|
||||||
|
"category": "none",
|
||||||
|
"temperature": 0.2,
|
||||||
|
"precipitation": "rain",
|
||||||
|
"depth": 0.1,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Stone Shore",
|
||||||
|
"color": 10658436,
|
||||||
|
"rainfall": 0.3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 26,
|
||||||
|
"name": "snowy_beach",
|
||||||
|
"category": "beach",
|
||||||
|
"temperature": 0.05,
|
||||||
|
"precipitation": "snow",
|
||||||
|
"depth": 0,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Snowy Beach",
|
||||||
|
"color": 16445632,
|
||||||
|
"rainfall": 0.3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 27,
|
||||||
|
"name": "birch_forest",
|
||||||
|
"category": "forest",
|
||||||
|
"temperature": 0.6,
|
||||||
|
"precipitation": "rain",
|
||||||
|
"depth": 0.1,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Birch Forest",
|
||||||
|
"color": 3175492,
|
||||||
|
"rainfall": 0.6,
|
||||||
|
"child": 155
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 28,
|
||||||
|
"name": "birch_forest_hills",
|
||||||
|
"category": "forest",
|
||||||
|
"temperature": 0.6,
|
||||||
|
"precipitation": "rain",
|
||||||
|
"depth": 0.45,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Birch Forest Hills",
|
||||||
|
"color": 2055986,
|
||||||
|
"rainfall": 0.6,
|
||||||
|
"child": 156
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 29,
|
||||||
|
"name": "dark_forest",
|
||||||
|
"category": "forest",
|
||||||
|
"temperature": 0.7,
|
||||||
|
"precipitation": "rain",
|
||||||
|
"depth": 0.1,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Dark Forest",
|
||||||
|
"color": 4215066,
|
||||||
|
"rainfall": 0.8,
|
||||||
|
"child": 157
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 30,
|
||||||
|
"name": "snowy_taiga",
|
||||||
|
"category": "taiga",
|
||||||
|
"temperature": -0.5,
|
||||||
|
"precipitation": "snow",
|
||||||
|
"depth": 0.2,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Snowy Taiga",
|
||||||
|
"color": 3233098,
|
||||||
|
"rainfall": 0.4,
|
||||||
|
"child": 158
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 31,
|
||||||
|
"name": "snowy_taiga_hills",
|
||||||
|
"category": "taiga",
|
||||||
|
"temperature": -0.5,
|
||||||
|
"precipitation": "snow",
|
||||||
|
"depth": 0.45,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Snowy Taiga Hills",
|
||||||
|
"color": 2375478,
|
||||||
|
"rainfall": 0.4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 32,
|
||||||
|
"name": "giant_tree_taiga",
|
||||||
|
"category": "taiga",
|
||||||
|
"temperature": 0.3,
|
||||||
|
"precipitation": "rain",
|
||||||
|
"depth": 0.2,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Giant Tree Taiga",
|
||||||
|
"color": 5858897,
|
||||||
|
"rainfall": 0.8,
|
||||||
|
"child": 160
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 33,
|
||||||
|
"name": "giant_tree_taiga_hills",
|
||||||
|
"category": "taiga",
|
||||||
|
"temperature": 0.3,
|
||||||
|
"precipitation": "rain",
|
||||||
|
"depth": 0.45,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Giant Tree Taiga Hills",
|
||||||
|
"color": 4542270,
|
||||||
|
"rainfall": 0.8,
|
||||||
|
"child": 161
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 34,
|
||||||
|
"name": "wooded_mountains",
|
||||||
|
"category": "extreme_hills",
|
||||||
|
"temperature": 0.2,
|
||||||
|
"precipitation": "rain",
|
||||||
|
"depth": 1,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Wooded Mountains",
|
||||||
|
"color": 5271632,
|
||||||
|
"rainfall": 0.3,
|
||||||
|
"child": 162
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 35,
|
||||||
|
"name": "savanna",
|
||||||
|
"category": "savanna",
|
||||||
|
"temperature": 1.2,
|
||||||
|
"precipitation": "none",
|
||||||
|
"depth": 0.125,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Savanna",
|
||||||
|
"color": 12431967,
|
||||||
|
"rainfall": 0,
|
||||||
|
"child": 163
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 36,
|
||||||
|
"name": "savanna_plateau",
|
||||||
|
"category": "savanna",
|
||||||
|
"temperature": 1,
|
||||||
|
"precipitation": "none",
|
||||||
|
"depth": 1.5,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Savanna Plateau",
|
||||||
|
"color": 10984804,
|
||||||
|
"rainfall": 0,
|
||||||
|
"child": 164
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 37,
|
||||||
|
"name": "badlands",
|
||||||
|
"category": "mesa",
|
||||||
|
"temperature": 2,
|
||||||
|
"precipitation": "none",
|
||||||
|
"depth": 0.1,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Badlands",
|
||||||
|
"color": 14238997,
|
||||||
|
"rainfall": 0,
|
||||||
|
"child": 165
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 38,
|
||||||
|
"name": "wooded_badlands_plateau",
|
||||||
|
"category": "mesa",
|
||||||
|
"temperature": 2,
|
||||||
|
"precipitation": "none",
|
||||||
|
"depth": 1.5,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Wooded Badlands Plateau",
|
||||||
|
"color": 11573093,
|
||||||
|
"rainfall": 0,
|
||||||
|
"child": 166
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 39,
|
||||||
|
"name": "badlands_plateau",
|
||||||
|
"category": "mesa",
|
||||||
|
"temperature": 2,
|
||||||
|
"precipitation": "none",
|
||||||
|
"depth": 1.5,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Badlands Plateau",
|
||||||
|
"color": 13274213,
|
||||||
|
"rainfall": 0,
|
||||||
|
"child": 167
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 40,
|
||||||
|
"name": "small_end_islands",
|
||||||
|
"category": "the_end",
|
||||||
|
"temperature": 0.5,
|
||||||
|
"precipitation": "none",
|
||||||
|
"depth": 0.1,
|
||||||
|
"dimension": "end",
|
||||||
|
"displayName": "Small End Islands",
|
||||||
|
"color": 42,
|
||||||
|
"rainfall": 0.5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 41,
|
||||||
|
"name": "end_midlands",
|
||||||
|
"category": "the_end",
|
||||||
|
"temperature": 0.5,
|
||||||
|
"precipitation": "none",
|
||||||
|
"depth": 0.1,
|
||||||
|
"dimension": "end",
|
||||||
|
"displayName": "End Midlands",
|
||||||
|
"color": 15464630,
|
||||||
|
"rainfall": 0.5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 42,
|
||||||
|
"name": "end_highlands",
|
||||||
|
"category": "the_end",
|
||||||
|
"temperature": 0.5,
|
||||||
|
"precipitation": "none",
|
||||||
|
"depth": 0.1,
|
||||||
|
"dimension": "end",
|
||||||
|
"displayName": "End Highlands",
|
||||||
|
"color": 12828041,
|
||||||
|
"rainfall": 0.5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 43,
|
||||||
|
"name": "end_barrens",
|
||||||
|
"category": "the_end",
|
||||||
|
"temperature": 0.5,
|
||||||
|
"precipitation": "none",
|
||||||
|
"depth": 0.1,
|
||||||
|
"dimension": "end",
|
||||||
|
"displayName": "End Barrens",
|
||||||
|
"color": 9474162,
|
||||||
|
"rainfall": 0.5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 44,
|
||||||
|
"name": "warm_ocean",
|
||||||
|
"category": "ocean",
|
||||||
|
"temperature": 0.5,
|
||||||
|
"precipitation": "rain",
|
||||||
|
"depth": -1,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Warm Ocean",
|
||||||
|
"color": 172,
|
||||||
|
"rainfall": 0.5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 45,
|
||||||
|
"name": "lukewarm_ocean",
|
||||||
|
"category": "ocean",
|
||||||
|
"temperature": 0.5,
|
||||||
|
"precipitation": "rain",
|
||||||
|
"depth": -1,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Lukewarm Ocean",
|
||||||
|
"color": 144,
|
||||||
|
"rainfall": 0.5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 46,
|
||||||
|
"name": "cold_ocean",
|
||||||
|
"category": "ocean",
|
||||||
|
"temperature": 0.5,
|
||||||
|
"precipitation": "rain",
|
||||||
|
"depth": -1,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Cold Ocean",
|
||||||
|
"color": 2105456,
|
||||||
|
"rainfall": 0.5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 47,
|
||||||
|
"name": "deep_warm_ocean",
|
||||||
|
"category": "ocean",
|
||||||
|
"temperature": 0.5,
|
||||||
|
"precipitation": "rain",
|
||||||
|
"depth": -1.8,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Deep Warm Ocean",
|
||||||
|
"color": 80,
|
||||||
|
"rainfall": 0.5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 48,
|
||||||
|
"name": "deep_lukewarm_ocean",
|
||||||
|
"category": "ocean",
|
||||||
|
"temperature": 0.5,
|
||||||
|
"precipitation": "rain",
|
||||||
|
"depth": -1.8,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Deep Lukewarm Ocean",
|
||||||
|
"color": 64,
|
||||||
|
"rainfall": 0.5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 49,
|
||||||
|
"name": "deep_cold_ocean",
|
||||||
|
"category": "ocean",
|
||||||
|
"temperature": 0.5,
|
||||||
|
"precipitation": "rain",
|
||||||
|
"depth": -1.8,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Deep Cold Ocean",
|
||||||
|
"color": 2105400,
|
||||||
|
"rainfall": 0.5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 50,
|
||||||
|
"name": "deep_frozen_ocean",
|
||||||
|
"category": "ocean",
|
||||||
|
"temperature": 0.5,
|
||||||
|
"precipitation": "rain",
|
||||||
|
"depth": -1.8,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Deep Frozen Ocean",
|
||||||
|
"color": 4210832,
|
||||||
|
"rainfall": 0.5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 127,
|
||||||
|
"name": "the_void",
|
||||||
|
"category": "none",
|
||||||
|
"temperature": 0.5,
|
||||||
|
"precipitation": "none",
|
||||||
|
"depth": 0.1,
|
||||||
|
"dimension": "end",
|
||||||
|
"displayName": "the_void",
|
||||||
|
"rainfall": 0.5,
|
||||||
|
"color": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 129,
|
||||||
|
"name": "sunflower_plains",
|
||||||
|
"category": "plains",
|
||||||
|
"temperature": 0.8,
|
||||||
|
"precipitation": "rain",
|
||||||
|
"depth": 0.125,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Sunflower Plains",
|
||||||
|
"color": 11918216,
|
||||||
|
"parent": "plains",
|
||||||
|
"rainfall": 0.4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 130,
|
||||||
|
"name": "desert_lakes",
|
||||||
|
"category": "desert",
|
||||||
|
"temperature": 2,
|
||||||
|
"precipitation": "none",
|
||||||
|
"depth": 0.125,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Desert Lakes",
|
||||||
|
"color": 16759872,
|
||||||
|
"parent": "desert",
|
||||||
|
"rainfall": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 131,
|
||||||
|
"name": "gravelly_mountains",
|
||||||
|
"category": "extreme_hills",
|
||||||
|
"temperature": 0.2,
|
||||||
|
"precipitation": "rain",
|
||||||
|
"depth": 1,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Gravelly Mountains",
|
||||||
|
"color": 8947848,
|
||||||
|
"parent": "mountains",
|
||||||
|
"rainfall": 0.3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 132,
|
||||||
|
"name": "flower_forest",
|
||||||
|
"category": "forest",
|
||||||
|
"temperature": 0.7,
|
||||||
|
"precipitation": "rain",
|
||||||
|
"depth": 0.1,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Flower Forest",
|
||||||
|
"color": 2985545,
|
||||||
|
"parent": "forest",
|
||||||
|
"rainfall": 0.8
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 133,
|
||||||
|
"name": "taiga_mountains",
|
||||||
|
"category": "taiga",
|
||||||
|
"temperature": 0.25,
|
||||||
|
"precipitation": "rain",
|
||||||
|
"depth": 0.3,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Taiga Mountains",
|
||||||
|
"color": 3378817,
|
||||||
|
"parent": "taiga",
|
||||||
|
"rainfall": 0.8
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 134,
|
||||||
|
"name": "swamp_hills",
|
||||||
|
"category": "swamp",
|
||||||
|
"temperature": 0.8,
|
||||||
|
"precipitation": "rain",
|
||||||
|
"depth": -0.1,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Swamp Hills",
|
||||||
|
"color": 3145690,
|
||||||
|
"parent": "swamp",
|
||||||
|
"rainfall": 0.9
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 140,
|
||||||
|
"name": "ice_spikes",
|
||||||
|
"category": "icy",
|
||||||
|
"temperature": 0,
|
||||||
|
"precipitation": "snow",
|
||||||
|
"depth": 0.425,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Ice Spikes",
|
||||||
|
"color": 11853020,
|
||||||
|
"parent": "snowy_tundra",
|
||||||
|
"rainfall": 0.5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 149,
|
||||||
|
"name": "modified_jungle",
|
||||||
|
"category": "jungle",
|
||||||
|
"temperature": 0.95,
|
||||||
|
"precipitation": "rain",
|
||||||
|
"depth": 0.2,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Modified Jungle",
|
||||||
|
"color": 8102705,
|
||||||
|
"parent": "jungle",
|
||||||
|
"rainfall": 0.9
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 151,
|
||||||
|
"name": "modified_jungle_edge",
|
||||||
|
"category": "jungle",
|
||||||
|
"temperature": 0.95,
|
||||||
|
"precipitation": "rain",
|
||||||
|
"depth": 0.2,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Modified Jungle Edge",
|
||||||
|
"color": 9089855,
|
||||||
|
"parent": "jungle_edge",
|
||||||
|
"rainfall": 0.8
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 155,
|
||||||
|
"name": "tall_birch_forest",
|
||||||
|
"category": "forest",
|
||||||
|
"temperature": 0.6,
|
||||||
|
"precipitation": "rain",
|
||||||
|
"depth": 0.2,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Tall Birch Forest",
|
||||||
|
"color": 5807212,
|
||||||
|
"parent": "birch_forest",
|
||||||
|
"rainfall": 0.6
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 156,
|
||||||
|
"name": "tall_birch_hills",
|
||||||
|
"category": "forest",
|
||||||
|
"temperature": 0.6,
|
||||||
|
"precipitation": "rain",
|
||||||
|
"depth": 0.55,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Tall Birch Hills",
|
||||||
|
"color": 4687706,
|
||||||
|
"parent": "birch_forest_hills",
|
||||||
|
"rainfall": 0.6
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 157,
|
||||||
|
"name": "dark_forest_hills",
|
||||||
|
"category": "forest",
|
||||||
|
"temperature": 0.7,
|
||||||
|
"precipitation": "rain",
|
||||||
|
"depth": 0.2,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Dark Forest Hills",
|
||||||
|
"color": 6846786,
|
||||||
|
"parent": "dark_forest",
|
||||||
|
"rainfall": 0.8
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 158,
|
||||||
|
"name": "snowy_taiga_mountains",
|
||||||
|
"category": "taiga",
|
||||||
|
"temperature": -0.5,
|
||||||
|
"precipitation": "snow",
|
||||||
|
"depth": 0.3,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Snowy Taiga Mountains",
|
||||||
|
"color": 5864818,
|
||||||
|
"parent": "snowy_taiga",
|
||||||
|
"rainfall": 0.4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 160,
|
||||||
|
"name": "giant_spruce_taiga",
|
||||||
|
"category": "taiga",
|
||||||
|
"temperature": 0.25,
|
||||||
|
"precipitation": "rain",
|
||||||
|
"depth": 0.2,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Giant Spruce Taiga",
|
||||||
|
"color": 8490617,
|
||||||
|
"parent": "giant_tree_taiga",
|
||||||
|
"rainfall": 0.8
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 161,
|
||||||
|
"name": "giant_spruce_taiga_hills",
|
||||||
|
"category": "taiga",
|
||||||
|
"temperature": 0.25,
|
||||||
|
"precipitation": "rain",
|
||||||
|
"depth": 0.2,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Giant Spruce Taiga Hills",
|
||||||
|
"color": 7173990,
|
||||||
|
"parent": "giant_tree_taiga_hills",
|
||||||
|
"rainfall": 0.8
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 162,
|
||||||
|
"name": "modified_gravelly_mountains",
|
||||||
|
"category": "extreme_hills",
|
||||||
|
"temperature": 0.2,
|
||||||
|
"precipitation": "rain",
|
||||||
|
"depth": 1,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Gravelly Mountains+",
|
||||||
|
"color": 7903352,
|
||||||
|
"parent": "wooded_mountains",
|
||||||
|
"rainfall": 0.3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 163,
|
||||||
|
"name": "shattered_savanna",
|
||||||
|
"category": "savanna",
|
||||||
|
"temperature": 1.1,
|
||||||
|
"precipitation": "none",
|
||||||
|
"depth": 0.3625,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Shattered Savanna",
|
||||||
|
"color": 15063687,
|
||||||
|
"parent": "savanna",
|
||||||
|
"rainfall": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 164,
|
||||||
|
"name": "shattered_savanna_plateau",
|
||||||
|
"category": "savanna",
|
||||||
|
"temperature": 1,
|
||||||
|
"precipitation": "none",
|
||||||
|
"depth": 1.05,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Shattered Savanna Plateau",
|
||||||
|
"color": 13616524,
|
||||||
|
"parent": "savanna_plateau",
|
||||||
|
"rainfall": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 165,
|
||||||
|
"name": "eroded_badlands",
|
||||||
|
"category": "mesa",
|
||||||
|
"temperature": 2,
|
||||||
|
"precipitation": "none",
|
||||||
|
"depth": 0.1,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Eroded Badlands",
|
||||||
|
"color": 16739645,
|
||||||
|
"parent": "badlands",
|
||||||
|
"rainfall": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 166,
|
||||||
|
"name": "modified_wooded_badlands_plateau",
|
||||||
|
"category": "mesa",
|
||||||
|
"temperature": 2,
|
||||||
|
"precipitation": "none",
|
||||||
|
"depth": 0.45,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Modified Wooded Badlands Plateau",
|
||||||
|
"color": 14204813,
|
||||||
|
"parent": "wooded_badlands_plateau",
|
||||||
|
"rainfall": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 167,
|
||||||
|
"name": "modified_badlands_plateau",
|
||||||
|
"category": "mesa",
|
||||||
|
"temperature": 2,
|
||||||
|
"precipitation": "none",
|
||||||
|
"depth": 0.45,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Modified Badlands Plateau",
|
||||||
|
"color": 15905933,
|
||||||
|
"parent": "badlands_plateau",
|
||||||
|
"rainfall": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 168,
|
||||||
|
"name": "bamboo_jungle",
|
||||||
|
"category": "jungle",
|
||||||
|
"temperature": 0.95,
|
||||||
|
"precipitation": "rain",
|
||||||
|
"depth": 0.1,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Bamboo Jungle",
|
||||||
|
"color": 7769620,
|
||||||
|
"rainfall": 0.9
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 169,
|
||||||
|
"name": "bamboo_jungle_hills",
|
||||||
|
"category": "jungle",
|
||||||
|
"temperature": 0.95,
|
||||||
|
"precipitation": "rain",
|
||||||
|
"depth": 0.45,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Bamboo Jungle Hills",
|
||||||
|
"color": 3884810,
|
||||||
|
"rainfall": 0.9
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1,946 @@
|
|||||||
|
{"blocks":{"air":0,
|
||||||
|
"stone":1,
|
||||||
|
"granite":1,
|
||||||
|
"polished_granite":1,
|
||||||
|
"diorite":1,
|
||||||
|
"polished_diorite":1,
|
||||||
|
"andesite":1,
|
||||||
|
"polished_andesite":1,
|
||||||
|
"grass_block":1,
|
||||||
|
"dirt":1,
|
||||||
|
"coarse_dirt":1,
|
||||||
|
"podzol":1,
|
||||||
|
"cobblestone":1,
|
||||||
|
"oak_planks":1,
|
||||||
|
"spruce_planks":1,
|
||||||
|
"birch_planks":1,
|
||||||
|
"jungle_planks":1,
|
||||||
|
"acacia_planks":1,
|
||||||
|
"dark_oak_planks":1,
|
||||||
|
"oak_sapling":0,
|
||||||
|
"spruce_sapling":0,
|
||||||
|
"birch_sapling":0,
|
||||||
|
"jungle_sapling":0,
|
||||||
|
"acacia_sapling":0,
|
||||||
|
"dark_oak_sapling":0,
|
||||||
|
"bedrock":1,
|
||||||
|
"water":0,
|
||||||
|
"lava":0,
|
||||||
|
"sand":1,
|
||||||
|
"red_sand":1,
|
||||||
|
"gravel":1,
|
||||||
|
"gold_ore":1,
|
||||||
|
"iron_ore":1,
|
||||||
|
"coal_ore":1,
|
||||||
|
"oak_log":1,
|
||||||
|
"spruce_log":1,
|
||||||
|
"birch_log":1,
|
||||||
|
"jungle_log":1,
|
||||||
|
"acacia_log":1,
|
||||||
|
"dark_oak_log":1,
|
||||||
|
"stripped_spruce_log":1,
|
||||||
|
"stripped_birch_log":1,
|
||||||
|
"stripped_jungle_log":1,
|
||||||
|
"stripped_acacia_log":1,
|
||||||
|
"stripped_dark_oak_log":1,
|
||||||
|
"stripped_oak_log":1,
|
||||||
|
"oak_wood":1,
|
||||||
|
"spruce_wood":1,
|
||||||
|
"birch_wood":1,
|
||||||
|
"jungle_wood":1,
|
||||||
|
"acacia_wood":1,
|
||||||
|
"dark_oak_wood":1,
|
||||||
|
"stripped_oak_wood":1,
|
||||||
|
"stripped_spruce_wood":1,
|
||||||
|
"stripped_birch_wood":1,
|
||||||
|
"stripped_jungle_wood":1,
|
||||||
|
"stripped_acacia_wood":1,
|
||||||
|
"stripped_dark_oak_wood":1,
|
||||||
|
"oak_leaves":1,
|
||||||
|
"spruce_leaves":1,
|
||||||
|
"birch_leaves":1,
|
||||||
|
"jungle_leaves":1,
|
||||||
|
"acacia_leaves":1,
|
||||||
|
"dark_oak_leaves":1,
|
||||||
|
"sponge":1,
|
||||||
|
"wet_sponge":1,
|
||||||
|
"glass":1,
|
||||||
|
"lapis_ore":1,
|
||||||
|
"lapis_block":1,
|
||||||
|
"dispenser":1,
|
||||||
|
"sandstone":1,
|
||||||
|
"chiseled_sandstone":1,
|
||||||
|
"cut_sandstone":1,
|
||||||
|
"note_block":1,
|
||||||
|
"white_bed":[3,2,3,2,2,3,2,3,4,5,4,5,5,4,5,4],
|
||||||
|
"orange_bed":[3,2,3,2,2,3,2,3,4,5,4,5,5,4,5,4],
|
||||||
|
"magenta_bed":[3,2,3,2,2,3,2,3,4,5,4,5,5,4,5,4],
|
||||||
|
"light_blue_bed":[3,2,3,2,2,3,2,3,4,5,4,5,5,4,5,4],
|
||||||
|
"yellow_bed":[3,2,3,2,2,3,2,3,4,5,4,5,5,4,5,4],
|
||||||
|
"lime_bed":[3,2,3,2,2,3,2,3,4,5,4,5,5,4,5,4],
|
||||||
|
"pink_bed":[3,2,3,2,2,3,2,3,4,5,4,5,5,4,5,4],
|
||||||
|
"gray_bed":[3,2,3,2,2,3,2,3,4,5,4,5,5,4,5,4],
|
||||||
|
"light_gray_bed":[3,2,3,2,2,3,2,3,4,5,4,5,5,4,5,4],
|
||||||
|
"cyan_bed":[3,2,3,2,2,3,2,3,4,5,4,5,5,4,5,4],
|
||||||
|
"purple_bed":[3,2,3,2,2,3,2,3,4,5,4,5,5,4,5,4],
|
||||||
|
"blue_bed":[3,2,3,2,2,3,2,3,4,5,4,5,5,4,5,4],
|
||||||
|
"brown_bed":[3,2,3,2,2,3,2,3,4,5,4,5,5,4,5,4],
|
||||||
|
"green_bed":[3,2,3,2,2,3,2,3,4,5,4,5,5,4,5,4],
|
||||||
|
"red_bed":[3,2,3,2,2,3,2,3,4,5,4,5,5,4,5,4],
|
||||||
|
"black_bed":[3,2,3,2,2,3,2,3,4,5,4,5,5,4,5,4],
|
||||||
|
"powered_rail":0,
|
||||||
|
"detector_rail":0,
|
||||||
|
"sticky_piston":[6,7,8,9,10,11,1,1,1,1,1,1],
|
||||||
|
"cobweb":0,
|
||||||
|
"grass":0,
|
||||||
|
"fern":0,
|
||||||
|
"dead_bush":0,
|
||||||
|
"seagrass":0,
|
||||||
|
"tall_seagrass":0,
|
||||||
|
"piston":[6,7,8,9,10,11,1,1,1,1,1,1],
|
||||||
|
"piston_head":[13,13,12,12,14,14,15,15,16,16,17,17,18,18,19,19,20,20,21,21,22,22,23,23],
|
||||||
|
"white_wool":1,
|
||||||
|
"orange_wool":1,
|
||||||
|
"magenta_wool":1,
|
||||||
|
"light_blue_wool":1,
|
||||||
|
"yellow_wool":1,
|
||||||
|
"lime_wool":1,
|
||||||
|
"pink_wool":1,
|
||||||
|
"gray_wool":1,
|
||||||
|
"light_gray_wool":1,
|
||||||
|
"cyan_wool":1,
|
||||||
|
"purple_wool":1,
|
||||||
|
"blue_wool":1,
|
||||||
|
"brown_wool":1,
|
||||||
|
"green_wool":1,
|
||||||
|
"red_wool":1,
|
||||||
|
"black_wool":1,
|
||||||
|
"moving_piston":0,
|
||||||
|
"dandelion":0,
|
||||||
|
"poppy":0,
|
||||||
|
"blue_orchid":0,
|
||||||
|
"allium":0,
|
||||||
|
"azure_bluet":0,
|
||||||
|
"red_tulip":0,
|
||||||
|
"orange_tulip":0,
|
||||||
|
"white_tulip":0,
|
||||||
|
"pink_tulip":0,
|
||||||
|
"oxeye_daisy":0,
|
||||||
|
"cornflower":0,
|
||||||
|
"wither_rose":0,
|
||||||
|
"lily_of_the_valley":0,
|
||||||
|
"brown_mushroom":0,
|
||||||
|
"red_mushroom":0,
|
||||||
|
"gold_block":1,
|
||||||
|
"iron_block":1,
|
||||||
|
"bricks":1,
|
||||||
|
"tnt":1,
|
||||||
|
"bookshelf":1,
|
||||||
|
"mossy_cobblestone":1,
|
||||||
|
"obsidian":1,
|
||||||
|
"torch":0,
|
||||||
|
"wall_torch":0,
|
||||||
|
"fire":0,
|
||||||
|
"spawner":1,
|
||||||
|
"oak_stairs":[25,25,26,26,27,27,28,28,29,29,24,24,30,30,31,31,32,32,33,33,34,34,35,35,36,36,37,37,38,38,39,39,40,40,41,41,42,42,43,43,44,44,36,36,26,26,38,38,28,28,45,45,41,41,30,30,43,43,32,32,46,46,27,27,35,35,29,29,37,37,47,47,31,31,40,40,33,33,42,42],
|
||||||
|
"chest":[48,48,49,49,50,50,48,48,50,50,49,49,48,48,51,51,52,52,48,48,52,52,51,51],
|
||||||
|
"redstone_wire":0,
|
||||||
|
"diamond_ore":1,
|
||||||
|
"diamond_block":1,
|
||||||
|
"crafting_table":1,
|
||||||
|
"wheat":0,
|
||||||
|
"farmland":53,
|
||||||
|
"furnace":1,
|
||||||
|
"oak_sign":0,
|
||||||
|
"spruce_sign":0,
|
||||||
|
"birch_sign":0,
|
||||||
|
"acacia_sign":0,
|
||||||
|
"jungle_sign":0,
|
||||||
|
"dark_oak_sign":0,
|
||||||
|
"oak_door":[55,55,54,54,56,56,54,54,55,55,54,54,56,56,54,54,56,56,57,57,55,55,57,57,56,56,57,57,55,55,57,57,54,54,56,56,57,57,56,56,54,54,56,56,57,57,56,56,57,57,55,55,54,54,55,55,57,57,55,55,54,54,55,55],
|
||||||
|
"ladder":[54,54,57,57,56,56,55,55],
|
||||||
|
"rail":0,
|
||||||
|
"cobblestone_stairs":[25,25,26,26,27,27,28,28,29,29,24,24,30,30,31,31,32,32,33,33,34,34,35,35,36,36,37,37,38,38,39,39,40,40,41,41,42,42,43,43,44,44,36,36,26,26,38,38,28,28,45,45,41,41,30,30,43,43,32,32,46,46,27,27,35,35,29,29,37,37,47,47,31,31,40,40,33,33,42,42],
|
||||||
|
"oak_wall_sign":0,
|
||||||
|
"spruce_wall_sign":0,
|
||||||
|
"birch_wall_sign":0,
|
||||||
|
"acacia_wall_sign":0,
|
||||||
|
"jungle_wall_sign":0,
|
||||||
|
"dark_oak_wall_sign":0,
|
||||||
|
"lever":0,
|
||||||
|
"stone_pressure_plate":0,
|
||||||
|
"iron_door":[55,55,54,54,56,56,54,54,55,55,54,54,56,56,54,54,56,56,57,57,55,55,57,57,56,56,57,57,55,55,57,57,54,54,56,56,57,57,56,56,54,54,56,56,57,57,56,56,57,57,55,55,54,54,55,55,57,57,55,55,54,54,55,55],
|
||||||
|
"oak_pressure_plate":0,
|
||||||
|
"spruce_pressure_plate":0,
|
||||||
|
"birch_pressure_plate":0,
|
||||||
|
"jungle_pressure_plate":0,
|
||||||
|
"acacia_pressure_plate":0,
|
||||||
|
"dark_oak_pressure_plate":0,
|
||||||
|
"redstone_ore":1,
|
||||||
|
"redstone_torch":0,
|
||||||
|
"redstone_wall_torch":0,
|
||||||
|
"stone_button":0,
|
||||||
|
"snow":[0,58,59,60,61,62,10,63],
|
||||||
|
"ice":1,
|
||||||
|
"snow_block":1,
|
||||||
|
"cactus":64,
|
||||||
|
"clay":1,
|
||||||
|
"sugar_cane":0,
|
||||||
|
"jukebox":1,
|
||||||
|
"oak_fence":[66,67,66,67,68,69,68,69,70,71,70,71,72,73,72,73,74,75,74,75,76,77,76,77,78,79,78,79,80,65,80,65],
|
||||||
|
"pumpkin":1,
|
||||||
|
"netherrack":1,
|
||||||
|
"soul_sand":63,
|
||||||
|
"glowstone":1,
|
||||||
|
"nether_portal":0,
|
||||||
|
"carved_pumpkin":1,
|
||||||
|
"jack_o_lantern":1,
|
||||||
|
"cake":[81,82,83,84,85,86,87],
|
||||||
|
"repeater":58,
|
||||||
|
"white_stained_glass":1,
|
||||||
|
"orange_stained_glass":1,
|
||||||
|
"magenta_stained_glass":1,
|
||||||
|
"light_blue_stained_glass":1,
|
||||||
|
"yellow_stained_glass":1,
|
||||||
|
"lime_stained_glass":1,
|
||||||
|
"pink_stained_glass":1,
|
||||||
|
"gray_stained_glass":1,
|
||||||
|
"light_gray_stained_glass":1,
|
||||||
|
"cyan_stained_glass":1,
|
||||||
|
"purple_stained_glass":1,
|
||||||
|
"blue_stained_glass":1,
|
||||||
|
"brown_stained_glass":1,
|
||||||
|
"green_stained_glass":1,
|
||||||
|
"red_stained_glass":1,
|
||||||
|
"black_stained_glass":1,
|
||||||
|
"oak_trapdoor":[54,54,54,54,89,89,89,89,54,54,54,54,88,88,88,88,57,57,57,57,89,89,89,89,57,57,57,57,88,88,88,88,56,56,56,56,89,89,89,89,56,56,56,56,88,88,88,88,55,55,55,55,89,89,89,89,55,55,55,55,88,88,88,88],
|
||||||
|
"spruce_trapdoor":[54,54,54,54,89,89,89,89,54,54,54,54,88,88,88,88,57,57,57,57,89,89,89,89,57,57,57,57,88,88,88,88,56,56,56,56,89,89,89,89,56,56,56,56,88,88,88,88,55,55,55,55,89,89,89,89,55,55,55,55,88,88,88,88],
|
||||||
|
"birch_trapdoor":[54,54,54,54,89,89,89,89,54,54,54,54,88,88,88,88,57,57,57,57,89,89,89,89,57,57,57,57,88,88,88,88,56,56,56,56,89,89,89,89,56,56,56,56,88,88,88,88,55,55,55,55,89,89,89,89,55,55,55,55,88,88,88,88],
|
||||||
|
"jungle_trapdoor":[54,54,54,54,89,89,89,89,54,54,54,54,88,88,88,88,57,57,57,57,89,89,89,89,57,57,57,57,88,88,88,88,56,56,56,56,89,89,89,89,56,56,56,56,88,88,88,88,55,55,55,55,89,89,89,89,55,55,55,55,88,88,88,88],
|
||||||
|
"acacia_trapdoor":[54,54,54,54,89,89,89,89,54,54,54,54,88,88,88,88,57,57,57,57,89,89,89,89,57,57,57,57,88,88,88,88,56,56,56,56,89,89,89,89,56,56,56,56,88,88,88,88,55,55,55,55,89,89,89,89,55,55,55,55,88,88,88,88],
|
||||||
|
"dark_oak_trapdoor":[54,54,54,54,89,89,89,89,54,54,54,54,88,88,88,88,57,57,57,57,89,89,89,89,57,57,57,57,88,88,88,88,56,56,56,56,89,89,89,89,56,56,56,56,88,88,88,88,55,55,55,55,89,89,89,89,55,55,55,55,88,88,88,88],
|
||||||
|
"stone_bricks":1,
|
||||||
|
"mossy_stone_bricks":1,
|
||||||
|
"cracked_stone_bricks":1,
|
||||||
|
"chiseled_stone_bricks":1,
|
||||||
|
"infested_stone":1,
|
||||||
|
"infested_cobblestone":1,
|
||||||
|
"infested_stone_bricks":1,
|
||||||
|
"infested_mossy_stone_bricks":1,
|
||||||
|
"infested_cracked_stone_bricks":1,
|
||||||
|
"infested_chiseled_stone_bricks":1,
|
||||||
|
"brown_mushroom_block":1,
|
||||||
|
"red_mushroom_block":1,
|
||||||
|
"mushroom_stem":1,
|
||||||
|
"iron_bars":[91,92,91,92,93,94,93,94,95,96,95,96,97,98,97,98,99,100,99,100,101,102,101,102,103,104,103,104,105,90,105,90],
|
||||||
|
"glass_pane":[91,92,91,92,93,94,93,94,95,96,95,96,97,98,97,98,99,100,99,100,101,102,101,102,103,104,103,104,105,90,105,90],
|
||||||
|
"melon":1,
|
||||||
|
"attached_pumpkin_stem":0,
|
||||||
|
"attached_melon_stem":0,
|
||||||
|
"pumpkin_stem":0,
|
||||||
|
"melon_stem":0,
|
||||||
|
"vine":0,
|
||||||
|
"oak_fence_gate":[0,0,72,72,0,0,72,72,0,0,72,72,0,0,72,72,0,0,75,75,0,0,75,75,0,0,75,75,0,0,75,75],
|
||||||
|
"brick_stairs":[25,25,26,26,27,27,28,28,29,29,24,24,30,30,31,31,32,32,33,33,34,34,35,35,36,36,37,37,38,38,39,39,40,40,41,41,42,42,43,43,44,44,36,36,26,26,38,38,28,28,45,45,41,41,30,30,43,43,32,32,46,46,27,27,35,35,29,29,37,37,47,47,31,31,40,40,33,33,42,42],
|
||||||
|
"stone_brick_stairs":[25,25,26,26,27,27,28,28,29,29,24,24,30,30,31,31,32,32,33,33,34,34,35,35,36,36,37,37,38,38,39,39,40,40,41,41,42,42,43,43,44,44,36,36,26,26,38,38,28,28,45,45,41,41,30,30,43,43,32,32,46,46,27,27,35,35,29,29,37,37,47,47,31,31,40,40,33,33,42,42],
|
||||||
|
"mycelium":1,
|
||||||
|
"lily_pad":106,
|
||||||
|
"nether_bricks":1,
|
||||||
|
"nether_brick_fence":[66,67,66,67,68,69,68,69,70,71,70,71,72,73,72,73,74,75,74,75,76,77,76,77,78,79,78,79,80,65,80,65],
|
||||||
|
"nether_brick_stairs":[25,25,26,26,27,27,28,28,29,29,24,24,30,30,31,31,32,32,33,33,34,34,35,35,36,36,37,37,38,38,39,39,40,40,41,41,42,42,43,43,44,44,36,36,26,26,38,38,28,28,45,45,41,41,30,30,43,43,32,32,46,46,27,27,35,35,29,29,37,37,47,47,31,31,40,40,33,33,42,42],
|
||||||
|
"nether_wart":0,
|
||||||
|
"enchanting_table":10,
|
||||||
|
"brewing_stand":107,
|
||||||
|
"cauldron":108,
|
||||||
|
"end_portal":0,
|
||||||
|
"end_portal_frame":[110,110,110,110,109,109,109,109],
|
||||||
|
"end_stone":1,
|
||||||
|
"dragon_egg":111,
|
||||||
|
"redstone_lamp":1,
|
||||||
|
"cocoa":[112,113,114,115,116,117,118,119,120,121,122,123],
|
||||||
|
"sandstone_stairs":[25,25,26,26,27,27,28,28,29,29,24,24,30,30,31,31,32,32,33,33,34,34,35,35,36,36,37,37,38,38,39,39,40,40,41,41,42,42,43,43,44,44,36,36,26,26,38,38,28,28,45,45,41,41,30,30,43,43,32,32,46,46,27,27,35,35,29,29,37,37,47,47,31,31,40,40,33,33,42,42],
|
||||||
|
"emerald_ore":1,
|
||||||
|
"ender_chest":48,
|
||||||
|
"tripwire_hook":0,
|
||||||
|
"tripwire":0,
|
||||||
|
"emerald_block":1,
|
||||||
|
"spruce_stairs":[25,25,26,26,27,27,28,28,29,29,24,24,30,30,31,31,32,32,33,33,34,34,35,35,36,36,37,37,38,38,39,39,40,40,41,41,42,42,43,43,44,44,36,36,26,26,38,38,28,28,45,45,41,41,30,30,43,43,32,32,46,46,27,27,35,35,29,29,37,37,47,47,31,31,40,40,33,33,42,42],
|
||||||
|
"birch_stairs":[25,25,26,26,27,27,28,28,29,29,24,24,30,30,31,31,32,32,33,33,34,34,35,35,36,36,37,37,38,38,39,39,40,40,41,41,42,42,43,43,44,44,36,36,26,26,38,38,28,28,45,45,41,41,30,30,43,43,32,32,46,46,27,27,35,35,29,29,37,37,47,47,31,31,40,40,33,33,42,42],
|
||||||
|
"jungle_stairs":[25,25,26,26,27,27,28,28,29,29,24,24,30,30,31,31,32,32,33,33,34,34,35,35,36,36,37,37,38,38,39,39,40,40,41,41,42,42,43,43,44,44,36,36,26,26,38,38,28,28,45,45,41,41,30,30,43,43,32,32,46,46,27,27,35,35,29,29,37,37,47,47,31,31,40,40,33,33,42,42],
|
||||||
|
"command_block":1,
|
||||||
|
"beacon":1,
|
||||||
|
"cobblestone_wall":[125,126,125,126,127,128,127,128,129,130,129,130,131,132,131,132,133,134,133,134,135,136,135,136,137,138,137,138,139,140,139,140,141,142,141,142,143,144,143,144,145,146,145,146,147,148,147,148,149,150,149,150,151,152,151,152,153,124,153,124,154,155,154,155],
|
||||||
|
"mossy_cobblestone_wall":[125,126,125,126,127,128,127,128,129,130,129,130,131,132,131,132,133,134,133,134,135,136,135,136,137,138,137,138,139,140,139,140,141,142,141,142,143,144,143,144,145,146,145,146,147,148,147,148,149,150,149,150,151,152,151,152,153,124,153,124,154,155,154,155],
|
||||||
|
"flower_pot":156,
|
||||||
|
"potted_oak_sapling":156,
|
||||||
|
"potted_spruce_sapling":156,
|
||||||
|
"potted_birch_sapling":156,
|
||||||
|
"potted_jungle_sapling":156,
|
||||||
|
"potted_acacia_sapling":156,
|
||||||
|
"potted_dark_oak_sapling":156,
|
||||||
|
"potted_fern":156,
|
||||||
|
"potted_dandelion":156,
|
||||||
|
"potted_poppy":156,
|
||||||
|
"potted_blue_orchid":156,
|
||||||
|
"potted_allium":156,
|
||||||
|
"potted_azure_bluet":156,
|
||||||
|
"potted_red_tulip":156,
|
||||||
|
"potted_orange_tulip":156,
|
||||||
|
"potted_white_tulip":156,
|
||||||
|
"potted_pink_tulip":156,
|
||||||
|
"potted_oxeye_daisy":156,
|
||||||
|
"potted_cornflower":156,
|
||||||
|
"potted_lily_of_the_valley":156,
|
||||||
|
"potted_wither_rose":156,
|
||||||
|
"potted_red_mushroom":156,
|
||||||
|
"potted_brown_mushroom":156,
|
||||||
|
"potted_dead_bush":156,
|
||||||
|
"potted_cactus":156,
|
||||||
|
"carrots":0,
|
||||||
|
"potatoes":0,
|
||||||
|
"oak_button":0,
|
||||||
|
"spruce_button":0,
|
||||||
|
"birch_button":0,
|
||||||
|
"jungle_button":0,
|
||||||
|
"acacia_button":0,
|
||||||
|
"dark_oak_button":0,
|
||||||
|
"skeleton_skull":157,
|
||||||
|
"skeleton_wall_skull":[158,159,160,161],
|
||||||
|
"wither_skeleton_skull":157,
|
||||||
|
"wither_skeleton_wall_skull":[158,159,160,161],
|
||||||
|
"zombie_head":157,
|
||||||
|
"zombie_wall_head":[158,159,160,161],
|
||||||
|
"player_head":157,
|
||||||
|
"player_wall_head":[158,159,160,161],
|
||||||
|
"creeper_head":157,
|
||||||
|
"creeper_wall_head":[158,159,160,161],
|
||||||
|
"dragon_head":157,
|
||||||
|
"dragon_wall_head":[158,159,160,161],
|
||||||
|
"anvil":[162,162,163,163],
|
||||||
|
"chipped_anvil":[162,162,163,163],
|
||||||
|
"damaged_anvil":[162,162,163,163],
|
||||||
|
"trapped_chest":[48,48,49,49,50,50,48,48,50,50,49,49,48,48,51,51,52,52,48,48,52,52,51,51],
|
||||||
|
"light_weighted_pressure_plate":0,
|
||||||
|
"heavy_weighted_pressure_plate":0,
|
||||||
|
"comparator":58,
|
||||||
|
"daylight_detector":60,
|
||||||
|
"redstone_block":1,
|
||||||
|
"nether_quartz_ore":1,
|
||||||
|
"hopper":[164,165,166,167,168,164,165,166,167,168],
|
||||||
|
"quartz_block":1,
|
||||||
|
"chiseled_quartz_block":1,
|
||||||
|
"quartz_pillar":1,
|
||||||
|
"quartz_stairs":[25,25,26,26,27,27,28,28,29,29,24,24,30,30,31,31,32,32,33,33,34,34,35,35,36,36,37,37,38,38,39,39,40,40,41,41,42,42,43,43,44,44,36,36,26,26,38,38,28,28,45,45,41,41,30,30,43,43,32,32,46,46,27,27,35,35,29,29,37,37,47,47,31,31,40,40,33,33,42,42],
|
||||||
|
"activator_rail":0,
|
||||||
|
"dropper":1,
|
||||||
|
"white_terracotta":1,
|
||||||
|
"orange_terracotta":1,
|
||||||
|
"magenta_terracotta":1,
|
||||||
|
"light_blue_terracotta":1,
|
||||||
|
"yellow_terracotta":1,
|
||||||
|
"lime_terracotta":1,
|
||||||
|
"pink_terracotta":1,
|
||||||
|
"gray_terracotta":1,
|
||||||
|
"light_gray_terracotta":1,
|
||||||
|
"cyan_terracotta":1,
|
||||||
|
"purple_terracotta":1,
|
||||||
|
"blue_terracotta":1,
|
||||||
|
"brown_terracotta":1,
|
||||||
|
"green_terracotta":1,
|
||||||
|
"red_terracotta":1,
|
||||||
|
"black_terracotta":1,
|
||||||
|
"white_stained_glass_pane":[91,92,91,92,93,94,93,94,95,96,95,96,97,98,97,98,99,100,99,100,101,102,101,102,103,104,103,104,105,90,105,90],
|
||||||
|
"orange_stained_glass_pane":[91,92,91,92,93,94,93,94,95,96,95,96,97,98,97,98,99,100,99,100,101,102,101,102,103,104,103,104,105,90,105,90],
|
||||||
|
"magenta_stained_glass_pane":[91,92,91,92,93,94,93,94,95,96,95,96,97,98,97,98,99,100,99,100,101,102,101,102,103,104,103,104,105,90,105,90],
|
||||||
|
"light_blue_stained_glass_pane":[91,92,91,92,93,94,93,94,95,96,95,96,97,98,97,98,99,100,99,100,101,102,101,102,103,104,103,104,105,90,105,90],
|
||||||
|
"yellow_stained_glass_pane":[91,92,91,92,93,94,93,94,95,96,95,96,97,98,97,98,99,100,99,100,101,102,101,102,103,104,103,104,105,90,105,90],
|
||||||
|
"lime_stained_glass_pane":[91,92,91,92,93,94,93,94,95,96,95,96,97,98,97,98,99,100,99,100,101,102,101,102,103,104,103,104,105,90,105,90],
|
||||||
|
"pink_stained_glass_pane":[91,92,91,92,93,94,93,94,95,96,95,96,97,98,97,98,99,100,99,100,101,102,101,102,103,104,103,104,105,90,105,90],
|
||||||
|
"gray_stained_glass_pane":[91,92,91,92,93,94,93,94,95,96,95,96,97,98,97,98,99,100,99,100,101,102,101,102,103,104,103,104,105,90,105,90],
|
||||||
|
"light_gray_stained_glass_pane":[91,92,91,92,93,94,93,94,95,96,95,96,97,98,97,98,99,100,99,100,101,102,101,102,103,104,103,104,105,90,105,90],
|
||||||
|
"cyan_stained_glass_pane":[91,92,91,92,93,94,93,94,95,96,95,96,97,98,97,98,99,100,99,100,101,102,101,102,103,104,103,104,105,90,105,90],
|
||||||
|
"purple_stained_glass_pane":[91,92,91,92,93,94,93,94,95,96,95,96,97,98,97,98,99,100,99,100,101,102,101,102,103,104,103,104,105,90,105,90],
|
||||||
|
"blue_stained_glass_pane":[91,92,91,92,93,94,93,94,95,96,95,96,97,98,97,98,99,100,99,100,101,102,101,102,103,104,103,104,105,90,105,90],
|
||||||
|
"brown_stained_glass_pane":[91,92,91,92,93,94,93,94,95,96,95,96,97,98,97,98,99,100,99,100,101,102,101,102,103,104,103,104,105,90,105,90],
|
||||||
|
"green_stained_glass_pane":[91,92,91,92,93,94,93,94,95,96,95,96,97,98,97,98,99,100,99,100,101,102,101,102,103,104,103,104,105,90,105,90],
|
||||||
|
"red_stained_glass_pane":[91,92,91,92,93,94,93,94,95,96,95,96,97,98,97,98,99,100,99,100,101,102,101,102,103,104,103,104,105,90,105,90],
|
||||||
|
"black_stained_glass_pane":[91,92,91,92,93,94,93,94,95,96,95,96,97,98,97,98,99,100,99,100,101,102,101,102,103,104,103,104,105,90,105,90],
|
||||||
|
"acacia_stairs":[25,25,26,26,27,27,28,28,29,29,24,24,30,30,31,31,32,32,33,33,34,34,35,35,36,36,37,37,38,38,39,39,40,40,41,41,42,42,43,43,44,44,36,36,26,26,38,38,28,28,45,45,41,41,30,30,43,43,32,32,46,46,27,27,35,35,29,29,37,37,47,47,31,31,40,40,33,33,42,42],
|
||||||
|
"dark_oak_stairs":[25,25,26,26,27,27,28,28,29,29,24,24,30,30,31,31,32,32,33,33,34,34,35,35,36,36,37,37,38,38,39,39,40,40,41,41,42,42,43,43,44,44,36,36,26,26,38,38,28,28,45,45,41,41,30,30,43,43,32,32,46,46,27,27,35,35,29,29,37,37,47,47,31,31,40,40,33,33,42,42],
|
||||||
|
"slime_block":1,
|
||||||
|
"barrier":1,
|
||||||
|
"iron_trapdoor":[54,54,54,54,89,89,89,89,54,54,54,54,88,88,88,88,57,57,57,57,89,89,89,89,57,57,57,57,88,88,88,88,56,56,56,56,89,89,89,89,56,56,56,56,88,88,88,88,55,55,55,55,89,89,89,89,55,55,55,55,88,88,88,88],
|
||||||
|
"prismarine":1,
|
||||||
|
"prismarine_bricks":1,
|
||||||
|
"dark_prismarine":1,
|
||||||
|
"prismarine_stairs":[25,25,26,26,27,27,28,28,29,29,24,24,30,30,31,31,32,32,33,33,34,34,35,35,36,36,37,37,38,38,39,39,40,40,41,41,42,42,43,43,44,44,36,36,26,26,38,38,28,28,45,45,41,41,30,30,43,43,32,32,46,46,27,27,35,35,29,29,37,37,47,47,31,31,40,40,33,33,42,42],
|
||||||
|
"prismarine_brick_stairs":[25,25,26,26,27,27,28,28,29,29,24,24,30,30,31,31,32,32,33,33,34,34,35,35,36,36,37,37,38,38,39,39,40,40,41,41,42,42,43,43,44,44,36,36,26,26,38,38,28,28,45,45,41,41,30,30,43,43,32,32,46,46,27,27,35,35,29,29,37,37,47,47,31,31,40,40,33,33,42,42],
|
||||||
|
"dark_prismarine_stairs":[25,25,26,26,27,27,28,28,29,29,24,24,30,30,31,31,32,32,33,33,34,34,35,35,36,36,37,37,38,38,39,39,40,40,41,41,42,42,43,43,44,44,36,36,26,26,38,38,28,28,45,45,41,41,30,30,43,43,32,32,46,46,27,27,35,35,29,29,37,37,47,47,31,31,40,40,33,33,42,42],
|
||||||
|
"prismarine_slab":[169,169,61,61,1,1],
|
||||||
|
"prismarine_brick_slab":[169,169,61,61,1,1],
|
||||||
|
"dark_prismarine_slab":[169,169,61,61,1,1],
|
||||||
|
"sea_lantern":1,
|
||||||
|
"hay_block":1,
|
||||||
|
"white_carpet":170,
|
||||||
|
"orange_carpet":170,
|
||||||
|
"magenta_carpet":170,
|
||||||
|
"light_blue_carpet":170,
|
||||||
|
"yellow_carpet":170,
|
||||||
|
"lime_carpet":170,
|
||||||
|
"pink_carpet":170,
|
||||||
|
"gray_carpet":170,
|
||||||
|
"light_gray_carpet":170,
|
||||||
|
"cyan_carpet":170,
|
||||||
|
"purple_carpet":170,
|
||||||
|
"blue_carpet":170,
|
||||||
|
"brown_carpet":170,
|
||||||
|
"green_carpet":170,
|
||||||
|
"red_carpet":170,
|
||||||
|
"black_carpet":170,
|
||||||
|
"terracotta":1,
|
||||||
|
"coal_block":1,
|
||||||
|
"packed_ice":1,
|
||||||
|
"sunflower":0,
|
||||||
|
"lilac":0,
|
||||||
|
"rose_bush":0,
|
||||||
|
"peony":0,
|
||||||
|
"tall_grass":0,
|
||||||
|
"large_fern":0,
|
||||||
|
"white_banner":0,
|
||||||
|
"orange_banner":0,
|
||||||
|
"magenta_banner":0,
|
||||||
|
"light_blue_banner":0,
|
||||||
|
"yellow_banner":0,
|
||||||
|
"lime_banner":0,
|
||||||
|
"pink_banner":0,
|
||||||
|
"gray_banner":0,
|
||||||
|
"light_gray_banner":0,
|
||||||
|
"cyan_banner":0,
|
||||||
|
"purple_banner":0,
|
||||||
|
"blue_banner":0,
|
||||||
|
"brown_banner":0,
|
||||||
|
"green_banner":0,
|
||||||
|
"red_banner":0,
|
||||||
|
"black_banner":0,
|
||||||
|
"white_wall_banner":0,
|
||||||
|
"orange_wall_banner":0,
|
||||||
|
"magenta_wall_banner":0,
|
||||||
|
"light_blue_wall_banner":0,
|
||||||
|
"yellow_wall_banner":0,
|
||||||
|
"lime_wall_banner":0,
|
||||||
|
"pink_wall_banner":0,
|
||||||
|
"gray_wall_banner":0,
|
||||||
|
"light_gray_wall_banner":0,
|
||||||
|
"cyan_wall_banner":0,
|
||||||
|
"purple_wall_banner":0,
|
||||||
|
"blue_wall_banner":0,
|
||||||
|
"brown_wall_banner":0,
|
||||||
|
"green_wall_banner":0,
|
||||||
|
"red_wall_banner":0,
|
||||||
|
"black_wall_banner":0,
|
||||||
|
"red_sandstone":1,
|
||||||
|
"chiseled_red_sandstone":1,
|
||||||
|
"cut_red_sandstone":1,
|
||||||
|
"red_sandstone_stairs":[25,25,26,26,27,27,28,28,29,29,24,24,30,30,31,31,32,32,33,33,34,34,35,35,36,36,37,37,38,38,39,39,40,40,41,41,42,42,43,43,44,44,36,36,26,26,38,38,28,28,45,45,41,41,30,30,43,43,32,32,46,46,27,27,35,35,29,29,37,37,47,47,31,31,40,40,33,33,42,42],
|
||||||
|
"oak_slab":[169,169,61,61,1,1],
|
||||||
|
"spruce_slab":[169,169,61,61,1,1],
|
||||||
|
"birch_slab":[169,169,61,61,1,1],
|
||||||
|
"jungle_slab":[169,169,61,61,1,1],
|
||||||
|
"acacia_slab":[169,169,61,61,1,1],
|
||||||
|
"dark_oak_slab":[169,169,61,61,1,1],
|
||||||
|
"stone_slab":[169,169,61,61,1,1],
|
||||||
|
"smooth_stone_slab":[169,169,61,61,1,1],
|
||||||
|
"sandstone_slab":[169,169,61,61,1,1],
|
||||||
|
"cut_sandstone_slab":[169,169,61,61,1,1],
|
||||||
|
"petrified_oak_slab":[169,169,61,61,1,1],
|
||||||
|
"cobblestone_slab":[169,169,61,61,1,1],
|
||||||
|
"brick_slab":[169,169,61,61,1,1],
|
||||||
|
"stone_brick_slab":[169,169,61,61,1,1],
|
||||||
|
"nether_brick_slab":[169,169,61,61,1,1],
|
||||||
|
"quartz_slab":[169,169,61,61,1,1],
|
||||||
|
"red_sandstone_slab":[169,169,61,61,1,1],
|
||||||
|
"cut_red_sandstone_slab":[169,169,61,61,1,1],
|
||||||
|
"purpur_slab":[169,169,61,61,1,1],
|
||||||
|
"smooth_stone":1,
|
||||||
|
"smooth_sandstone":1,
|
||||||
|
"smooth_quartz":1,
|
||||||
|
"smooth_red_sandstone":1,
|
||||||
|
"spruce_fence_gate":[0,0,72,72,0,0,72,72,0,0,72,72,0,0,72,72,0,0,75,75,0,0,75,75,0,0,75,75,0,0,75,75],
|
||||||
|
"birch_fence_gate":[0,0,72,72,0,0,72,72,0,0,72,72,0,0,72,72,0,0,75,75,0,0,75,75,0,0,75,75,0,0,75,75],
|
||||||
|
"jungle_fence_gate":[0,0,72,72,0,0,72,72,0,0,72,72,0,0,72,72,0,0,75,75,0,0,75,75,0,0,75,75,0,0,75,75],
|
||||||
|
"acacia_fence_gate":[0,0,72,72,0,0,72,72,0,0,72,72,0,0,72,72,0,0,75,75,0,0,75,75,0,0,75,75,0,0,75,75],
|
||||||
|
"dark_oak_fence_gate":[0,0,72,72,0,0,72,72,0,0,72,72,0,0,72,72,0,0,75,75,0,0,75,75,0,0,75,75,0,0,75,75],
|
||||||
|
"spruce_fence":[66,67,66,67,68,69,68,69,70,71,70,71,72,73,72,73,74,75,74,75,76,77,76,77,78,79,78,79,80,65,80,65],
|
||||||
|
"birch_fence":[66,67,66,67,68,69,68,69,70,71,70,71,72,73,72,73,74,75,74,75,76,77,76,77,78,79,78,79,80,65,80,65],
|
||||||
|
"jungle_fence":[66,67,66,67,68,69,68,69,70,71,70,71,72,73,72,73,74,75,74,75,76,77,76,77,78,79,78,79,80,65,80,65],
|
||||||
|
"acacia_fence":[66,67,66,67,68,69,68,69,70,71,70,71,72,73,72,73,74,75,74,75,76,77,76,77,78,79,78,79,80,65,80,65],
|
||||||
|
"dark_oak_fence":[66,67,66,67,68,69,68,69,70,71,70,71,72,73,72,73,74,75,74,75,76,77,76,77,78,79,78,79,80,65,80,65],
|
||||||
|
"spruce_door":[55,55,54,54,56,56,54,54,55,55,54,54,56,56,54,54,56,56,57,57,55,55,57,57,56,56,57,57,55,55,57,57,54,54,56,56,57,57,56,56,54,54,56,56,57,57,56,56,57,57,55,55,54,54,55,55,57,57,55,55,54,54,55,55],
|
||||||
|
"birch_door":[55,55,54,54,56,56,54,54,55,55,54,54,56,56,54,54,56,56,57,57,55,55,57,57,56,56,57,57,55,55,57,57,54,54,56,56,57,57,56,56,54,54,56,56,57,57,56,56,57,57,55,55,54,54,55,55,57,57,55,55,54,54,55,55],
|
||||||
|
"jungle_door":[55,55,54,54,56,56,54,54,55,55,54,54,56,56,54,54,56,56,57,57,55,55,57,57,56,56,57,57,55,55,57,57,54,54,56,56,57,57,56,56,54,54,56,56,57,57,56,56,57,57,55,55,54,54,55,55,57,57,55,55,54,54,55,55],
|
||||||
|
"acacia_door":[55,55,54,54,56,56,54,54,55,55,54,54,56,56,54,54,56,56,57,57,55,55,57,57,56,56,57,57,55,55,57,57,54,54,56,56,57,57,56,56,54,54,56,56,57,57,56,56,57,57,55,55,54,54,55,55,57,57,55,55,54,54,55,55],
|
||||||
|
"dark_oak_door":[55,55,54,54,56,56,54,54,55,55,54,54,56,56,54,54,56,56,57,57,55,55,57,57,56,56,57,57,55,55,57,57,54,54,56,56,57,57,56,56,54,54,56,56,57,57,56,56,57,57,55,55,54,54,55,55,57,57,55,55,54,54,55,55],
|
||||||
|
"end_rod":[172,173,172,173,171,171],
|
||||||
|
"chorus_plant":[175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,174],
|
||||||
|
"chorus_flower":1,
|
||||||
|
"purpur_block":1,
|
||||||
|
"purpur_pillar":1,
|
||||||
|
"purpur_stairs":[25,25,26,26,27,27,28,28,29,29,24,24,30,30,31,31,32,32,33,33,34,34,35,35,36,36,37,37,38,38,39,39,40,40,41,41,42,42,43,43,44,44,36,36,26,26,38,38,28,28,45,45,41,41,30,30,43,43,32,32,46,46,27,27,35,35,29,29,37,37,47,47,31,31,40,40,33,33,42,42],
|
||||||
|
"end_stone_bricks":1,
|
||||||
|
"beetroots":0,
|
||||||
|
"grass_path":53,
|
||||||
|
"end_gateway":0,
|
||||||
|
"repeating_command_block":1,
|
||||||
|
"chain_command_block":1,
|
||||||
|
"frosted_ice":1,
|
||||||
|
"magma_block":1,
|
||||||
|
"nether_wart_block":1,
|
||||||
|
"red_nether_bricks":1,
|
||||||
|
"bone_block":1,
|
||||||
|
"structure_void":0,
|
||||||
|
"observer":1,
|
||||||
|
"shulker_box":1,
|
||||||
|
"white_shulker_box":1,
|
||||||
|
"orange_shulker_box":1,
|
||||||
|
"magenta_shulker_box":1,
|
||||||
|
"light_blue_shulker_box":1,
|
||||||
|
"yellow_shulker_box":1,
|
||||||
|
"lime_shulker_box":1,
|
||||||
|
"pink_shulker_box":1,
|
||||||
|
"gray_shulker_box":1,
|
||||||
|
"light_gray_shulker_box":1,
|
||||||
|
"cyan_shulker_box":1,
|
||||||
|
"purple_shulker_box":1,
|
||||||
|
"blue_shulker_box":1,
|
||||||
|
"brown_shulker_box":1,
|
||||||
|
"green_shulker_box":1,
|
||||||
|
"red_shulker_box":1,
|
||||||
|
"black_shulker_box":1,
|
||||||
|
"white_glazed_terracotta":1,
|
||||||
|
"orange_glazed_terracotta":1,
|
||||||
|
"magenta_glazed_terracotta":1,
|
||||||
|
"light_blue_glazed_terracotta":1,
|
||||||
|
"yellow_glazed_terracotta":1,
|
||||||
|
"lime_glazed_terracotta":1,
|
||||||
|
"pink_glazed_terracotta":1,
|
||||||
|
"gray_glazed_terracotta":1,
|
||||||
|
"light_gray_glazed_terracotta":1,
|
||||||
|
"cyan_glazed_terracotta":1,
|
||||||
|
"purple_glazed_terracotta":1,
|
||||||
|
"blue_glazed_terracotta":1,
|
||||||
|
"brown_glazed_terracotta":1,
|
||||||
|
"green_glazed_terracotta":1,
|
||||||
|
"red_glazed_terracotta":1,
|
||||||
|
"black_glazed_terracotta":1,
|
||||||
|
"white_concrete":1,
|
||||||
|
"orange_concrete":1,
|
||||||
|
"magenta_concrete":1,
|
||||||
|
"light_blue_concrete":1,
|
||||||
|
"yellow_concrete":1,
|
||||||
|
"lime_concrete":1,
|
||||||
|
"pink_concrete":1,
|
||||||
|
"gray_concrete":1,
|
||||||
|
"light_gray_concrete":1,
|
||||||
|
"cyan_concrete":1,
|
||||||
|
"purple_concrete":1,
|
||||||
|
"blue_concrete":1,
|
||||||
|
"brown_concrete":1,
|
||||||
|
"green_concrete":1,
|
||||||
|
"red_concrete":1,
|
||||||
|
"black_concrete":1,
|
||||||
|
"white_concrete_powder":1,
|
||||||
|
"orange_concrete_powder":1,
|
||||||
|
"magenta_concrete_powder":1,
|
||||||
|
"light_blue_concrete_powder":1,
|
||||||
|
"yellow_concrete_powder":1,
|
||||||
|
"lime_concrete_powder":1,
|
||||||
|
"pink_concrete_powder":1,
|
||||||
|
"gray_concrete_powder":1,
|
||||||
|
"light_gray_concrete_powder":1,
|
||||||
|
"cyan_concrete_powder":1,
|
||||||
|
"purple_concrete_powder":1,
|
||||||
|
"blue_concrete_powder":1,
|
||||||
|
"brown_concrete_powder":1,
|
||||||
|
"green_concrete_powder":1,
|
||||||
|
"red_concrete_powder":1,
|
||||||
|
"black_concrete_powder":1,
|
||||||
|
"kelp":0,
|
||||||
|
"kelp_plant":0,
|
||||||
|
"dried_kelp_block":1,
|
||||||
|
"turtle_egg":[238,238,238,239,239,239,239,239,239,239,239,239],
|
||||||
|
"dead_tube_coral_block":1,
|
||||||
|
"dead_brain_coral_block":1,
|
||||||
|
"dead_bubble_coral_block":1,
|
||||||
|
"dead_fire_coral_block":1,
|
||||||
|
"dead_horn_coral_block":1,
|
||||||
|
"tube_coral_block":1,
|
||||||
|
"brain_coral_block":1,
|
||||||
|
"bubble_coral_block":1,
|
||||||
|
"fire_coral_block":1,
|
||||||
|
"horn_coral_block":1,
|
||||||
|
"dead_tube_coral":0,
|
||||||
|
"dead_brain_coral":0,
|
||||||
|
"dead_bubble_coral":0,
|
||||||
|
"dead_fire_coral":0,
|
||||||
|
"dead_horn_coral":0,
|
||||||
|
"tube_coral":0,
|
||||||
|
"brain_coral":0,
|
||||||
|
"bubble_coral":0,
|
||||||
|
"fire_coral":0,
|
||||||
|
"horn_coral":0,
|
||||||
|
"dead_tube_coral_fan":0,
|
||||||
|
"dead_brain_coral_fan":0,
|
||||||
|
"dead_bubble_coral_fan":0,
|
||||||
|
"dead_fire_coral_fan":0,
|
||||||
|
"dead_horn_coral_fan":0,
|
||||||
|
"tube_coral_fan":0,
|
||||||
|
"brain_coral_fan":0,
|
||||||
|
"bubble_coral_fan":0,
|
||||||
|
"fire_coral_fan":0,
|
||||||
|
"horn_coral_fan":0,
|
||||||
|
"dead_tube_coral_wall_fan":0,
|
||||||
|
"dead_brain_coral_wall_fan":0,
|
||||||
|
"dead_bubble_coral_wall_fan":0,
|
||||||
|
"dead_fire_coral_wall_fan":0,
|
||||||
|
"dead_horn_coral_wall_fan":0,
|
||||||
|
"tube_coral_wall_fan":0,
|
||||||
|
"brain_coral_wall_fan":0,
|
||||||
|
"bubble_coral_wall_fan":0,
|
||||||
|
"fire_coral_wall_fan":0,
|
||||||
|
"horn_coral_wall_fan":0,
|
||||||
|
"sea_pickle":[240,240,241,241,242,242,243,243],
|
||||||
|
"blue_ice":1,
|
||||||
|
"conduit":244,
|
||||||
|
"bamboo_sapling":0,
|
||||||
|
"bamboo":245,
|
||||||
|
"potted_bamboo":156,
|
||||||
|
"void_air":0,
|
||||||
|
"cave_air":0,
|
||||||
|
"bubble_column":0,
|
||||||
|
"polished_granite_stairs":[25,25,26,26,27,27,28,28,29,29,24,24,30,30,31,31,32,32,33,33,34,34,35,35,36,36,37,37,38,38,39,39,40,40,41,41,42,42,43,43,44,44,36,36,26,26,38,38,28,28,45,45,41,41,30,30,43,43,32,32,46,46,27,27,35,35,29,29,37,37,47,47,31,31,40,40,33,33,42,42],
|
||||||
|
"smooth_red_sandstone_stairs":[25,25,26,26,27,27,28,28,29,29,24,24,30,30,31,31,32,32,33,33,34,34,35,35,36,36,37,37,38,38,39,39,40,40,41,41,42,42,43,43,44,44,36,36,26,26,38,38,28,28,45,45,41,41,30,30,43,43,32,32,46,46,27,27,35,35,29,29,37,37,47,47,31,31,40,40,33,33,42,42],
|
||||||
|
"mossy_stone_brick_stairs":[25,25,26,26,27,27,28,28,29,29,24,24,30,30,31,31,32,32,33,33,34,34,35,35,36,36,37,37,38,38,39,39,40,40,41,41,42,42,43,43,44,44,36,36,26,26,38,38,28,28,45,45,41,41,30,30,43,43,32,32,46,46,27,27,35,35,29,29,37,37,47,47,31,31,40,40,33,33,42,42],
|
||||||
|
"polished_diorite_stairs":[25,25,26,26,27,27,28,28,29,29,24,24,30,30,31,31,32,32,33,33,34,34,35,35,36,36,37,37,38,38,39,39,40,40,41,41,42,42,43,43,44,44,36,36,26,26,38,38,28,28,45,45,41,41,30,30,43,43,32,32,46,46,27,27,35,35,29,29,37,37,47,47,31,31,40,40,33,33,42,42],
|
||||||
|
"mossy_cobblestone_stairs":[25,25,26,26,27,27,28,28,29,29,24,24,30,30,31,31,32,32,33,33,34,34,35,35,36,36,37,37,38,38,39,39,40,40,41,41,42,42,43,43,44,44,36,36,26,26,38,38,28,28,45,45,41,41,30,30,43,43,32,32,46,46,27,27,35,35,29,29,37,37,47,47,31,31,40,40,33,33,42,42],
|
||||||
|
"end_stone_brick_stairs":[25,25,26,26,27,27,28,28,29,29,24,24,30,30,31,31,32,32,33,33,34,34,35,35,36,36,37,37,38,38,39,39,40,40,41,41,42,42,43,43,44,44,36,36,26,26,38,38,28,28,45,45,41,41,30,30,43,43,32,32,46,46,27,27,35,35,29,29,37,37,47,47,31,31,40,40,33,33,42,42],
|
||||||
|
"stone_stairs":[25,25,26,26,27,27,28,28,29,29,24,24,30,30,31,31,32,32,33,33,34,34,35,35,36,36,37,37,38,38,39,39,40,40,41,41,42,42,43,43,44,44,36,36,26,26,38,38,28,28,45,45,41,41,30,30,43,43,32,32,46,46,27,27,35,35,29,29,37,37,47,47,31,31,40,40,33,33,42,42],
|
||||||
|
"smooth_sandstone_stairs":[25,25,26,26,27,27,28,28,29,29,24,24,30,30,31,31,32,32,33,33,34,34,35,35,36,36,37,37,38,38,39,39,40,40,41,41,42,42,43,43,44,44,36,36,26,26,38,38,28,28,45,45,41,41,30,30,43,43,32,32,46,46,27,27,35,35,29,29,37,37,47,47,31,31,40,40,33,33,42,42],
|
||||||
|
"smooth_quartz_stairs":[25,25,26,26,27,27,28,28,29,29,24,24,30,30,31,31,32,32,33,33,34,34,35,35,36,36,37,37,38,38,39,39,40,40,41,41,42,42,43,43,44,44,36,36,26,26,38,38,28,28,45,45,41,41,30,30,43,43,32,32,46,46,27,27,35,35,29,29,37,37,47,47,31,31,40,40,33,33,42,42],
|
||||||
|
"granite_stairs":[25,25,26,26,27,27,28,28,29,29,24,24,30,30,31,31,32,32,33,33,34,34,35,35,36,36,37,37,38,38,39,39,40,40,41,41,42,42,43,43,44,44,36,36,26,26,38,38,28,28,45,45,41,41,30,30,43,43,32,32,46,46,27,27,35,35,29,29,37,37,47,47,31,31,40,40,33,33,42,42],
|
||||||
|
"andesite_stairs":[25,25,26,26,27,27,28,28,29,29,24,24,30,30,31,31,32,32,33,33,34,34,35,35,36,36,37,37,38,38,39,39,40,40,41,41,42,42,43,43,44,44,36,36,26,26,38,38,28,28,45,45,41,41,30,30,43,43,32,32,46,46,27,27,35,35,29,29,37,37,47,47,31,31,40,40,33,33,42,42],
|
||||||
|
"red_nether_brick_stairs":[25,25,26,26,27,27,28,28,29,29,24,24,30,30,31,31,32,32,33,33,34,34,35,35,36,36,37,37,38,38,39,39,40,40,41,41,42,42,43,43,44,44,36,36,26,26,38,38,28,28,45,45,41,41,30,30,43,43,32,32,46,46,27,27,35,35,29,29,37,37,47,47,31,31,40,40,33,33,42,42],
|
||||||
|
"polished_andesite_stairs":[25,25,26,26,27,27,28,28,29,29,24,24,30,30,31,31,32,32,33,33,34,34,35,35,36,36,37,37,38,38,39,39,40,40,41,41,42,42,43,43,44,44,36,36,26,26,38,38,28,28,45,45,41,41,30,30,43,43,32,32,46,46,27,27,35,35,29,29,37,37,47,47,31,31,40,40,33,33,42,42],
|
||||||
|
"diorite_stairs":[25,25,26,26,27,27,28,28,29,29,24,24,30,30,31,31,32,32,33,33,34,34,35,35,36,36,37,37,38,38,39,39,40,40,41,41,42,42,43,43,44,44,36,36,26,26,38,38,28,28,45,45,41,41,30,30,43,43,32,32,46,46,27,27,35,35,29,29,37,37,47,47,31,31,40,40,33,33,42,42],
|
||||||
|
"polished_granite_slab":[169,169,61,61,1,1],
|
||||||
|
"smooth_red_sandstone_slab":[169,169,61,61,1,1],
|
||||||
|
"mossy_stone_brick_slab":[169,169,61,61,1,1],
|
||||||
|
"polished_diorite_slab":[169,169,61,61,1,1],
|
||||||
|
"mossy_cobblestone_slab":[169,169,61,61,1,1],
|
||||||
|
"end_stone_brick_slab":[169,169,61,61,1,1],
|
||||||
|
"smooth_sandstone_slab":[169,169,61,61,1,1],
|
||||||
|
"smooth_quartz_slab":[169,169,61,61,1,1],
|
||||||
|
"granite_slab":[169,169,61,61,1,1],
|
||||||
|
"andesite_slab":[169,169,61,61,1,1],
|
||||||
|
"red_nether_brick_slab":[169,169,61,61,1,1],
|
||||||
|
"polished_andesite_slab":[169,169,61,61,1,1],
|
||||||
|
"diorite_slab":[169,169,61,61,1,1],
|
||||||
|
"brick_wall":[125,126,125,126,127,128,127,128,129,130,129,130,131,132,131,132,133,134,133,134,135,136,135,136,137,138,137,138,139,140,139,140,141,142,141,142,143,144,143,144,145,146,145,146,147,148,147,148,149,150,149,150,151,152,151,152,153,124,153,124,154,155,154,155],
|
||||||
|
"prismarine_wall":[125,126,125,126,127,128,127,128,129,130,129,130,131,132,131,132,133,134,133,134,135,136,135,136,137,138,137,138,139,140,139,140,141,142,141,142,143,144,143,144,145,146,145,146,147,148,147,148,149,150,149,150,151,152,151,152,153,124,153,124,154,155,154,155],
|
||||||
|
"red_sandstone_wall":[125,126,125,126,127,128,127,128,129,130,129,130,131,132,131,132,133,134,133,134,135,136,135,136,137,138,137,138,139,140,139,140,141,142,141,142,143,144,143,144,145,146,145,146,147,148,147,148,149,150,149,150,151,152,151,152,153,124,153,124,154,155,154,155],
|
||||||
|
"mossy_stone_brick_wall":[125,126,125,126,127,128,127,128,129,130,129,130,131,132,131,132,133,134,133,134,135,136,135,136,137,138,137,138,139,140,139,140,141,142,141,142,143,144,143,144,145,146,145,146,147,148,147,148,149,150,149,150,151,152,151,152,153,124,153,124,154,155,154,155],
|
||||||
|
"granite_wall":[125,126,125,126,127,128,127,128,129,130,129,130,131,132,131,132,133,134,133,134,135,136,135,136,137,138,137,138,139,140,139,140,141,142,141,142,143,144,143,144,145,146,145,146,147,148,147,148,149,150,149,150,151,152,151,152,153,124,153,124,154,155,154,155],
|
||||||
|
"stone_brick_wall":[125,126,125,126,127,128,127,128,129,130,129,130,131,132,131,132,133,134,133,134,135,136,135,136,137,138,137,138,139,140,139,140,141,142,141,142,143,144,143,144,145,146,145,146,147,148,147,148,149,150,149,150,151,152,151,152,153,124,153,124,154,155,154,155],
|
||||||
|
"nether_brick_wall":[125,126,125,126,127,128,127,128,129,130,129,130,131,132,131,132,133,134,133,134,135,136,135,136,137,138,137,138,139,140,139,140,141,142,141,142,143,144,143,144,145,146,145,146,147,148,147,148,149,150,149,150,151,152,151,152,153,124,153,124,154,155,154,155],
|
||||||
|
"andesite_wall":[125,126,125,126,127,128,127,128,129,130,129,130,131,132,131,132,133,134,133,134,135,136,135,136,137,138,137,138,139,140,139,140,141,142,141,142,143,144,143,144,145,146,145,146,147,148,147,148,149,150,149,150,151,152,151,152,153,124,153,124,154,155,154,155],
|
||||||
|
"red_nether_brick_wall":[125,126,125,126,127,128,127,128,129,130,129,130,131,132,131,132,133,134,133,134,135,136,135,136,137,138,137,138,139,140,139,140,141,142,141,142,143,144,143,144,145,146,145,146,147,148,147,148,149,150,149,150,151,152,151,152,153,124,153,124,154,155,154,155],
|
||||||
|
"sandstone_wall":[125,126,125,126,127,128,127,128,129,130,129,130,131,132,131,132,133,134,133,134,135,136,135,136,137,138,137,138,139,140,139,140,141,142,141,142,143,144,143,144,145,146,145,146,147,148,147,148,149,150,149,150,151,152,151,152,153,124,153,124,154,155,154,155],
|
||||||
|
"end_stone_brick_wall":[125,126,125,126,127,128,127,128,129,130,129,130,131,132,131,132,133,134,133,134,135,136,135,136,137,138,137,138,139,140,139,140,141,142,141,142,143,144,143,144,145,146,145,146,147,148,147,148,149,150,149,150,151,152,151,152,153,124,153,124,154,155,154,155],
|
||||||
|
"diorite_wall":[125,126,125,126,127,128,127,128,129,130,129,130,131,132,131,132,133,134,133,134,135,136,135,136,137,138,137,138,139,140,139,140,141,142,141,142,143,144,143,144,145,146,145,146,147,148,147,148,149,150,149,150,151,152,151,152,153,124,153,124,154,155,154,155],
|
||||||
|
"scaffolding":246,
|
||||||
|
"loom":1,
|
||||||
|
"barrel":1,
|
||||||
|
"smoker":1,
|
||||||
|
"blast_furnace":1,
|
||||||
|
"cartography_table":1,
|
||||||
|
"fletching_table":1,
|
||||||
|
"grindstone":[248,248,249,249,247,250,251,252,253,253,254,254],
|
||||||
|
"lectern":255,
|
||||||
|
"smithing_table":1,
|
||||||
|
"stonecutter":256,
|
||||||
|
"bell":[257,257,258,258,259,259,259,259,260,261,262,263,264,264,265,265],
|
||||||
|
"lantern":[267,266],
|
||||||
|
"campfire":268,
|
||||||
|
"sweet_berry_bush":0,
|
||||||
|
"structure_block":1,
|
||||||
|
"jigsaw":1,
|
||||||
|
"composter":269},
|
||||||
|
"shapes":{"0":[],
|
||||||
|
"1":[[0.0,0.0,0.0,1.0,1.0,1.0]],
|
||||||
|
"2":[[0.0,0.0,0.8125,0.1875,0.5625,1.0],[0.0,0.1875,0.0,1.0,0.5625,0.8125],[0.1875,0.1875,0.8125,1.0,0.5625,1.0],[0.8125,0.0,0.8125,1.0,0.1875,1.0]],
|
||||||
|
"3":[[0.0,0.0,0.0,0.1875,0.5625,0.1875],[0.0,0.1875,0.1875,1.0,0.5625,1.0],[0.1875,0.1875,0.0,1.0,0.5625,0.1875],[0.8125,0.0,0.0,1.0,0.1875,0.1875]],
|
||||||
|
"4":[[0.0,0.0,0.0,0.1875,0.5625,0.1875],[0.0,0.0,0.8125,0.1875,0.5625,1.0],[0.0,0.1875,0.1875,1.0,0.5625,0.8125],[0.1875,0.1875,0.0,1.0,0.5625,0.1875],[0.1875,0.1875,0.8125,1.0,0.5625,1.0]],
|
||||||
|
"5":[[0.0,0.1875,0.0,1.0,0.5625,1.0],[0.8125,0.0,0.0,1.0,0.1875,0.1875],[0.8125,0.0,0.8125,1.0,0.1875,1.0]],
|
||||||
|
"6":[[0.0,0.0,0.25,1.0,1.0,1.0]],
|
||||||
|
"7":[[0.0,0.0,0.0,0.75,1.0,1.0]],
|
||||||
|
"8":[[0.0,0.0,0.0,1.0,1.0,0.75]],
|
||||||
|
"9":[[0.25,0.0,0.0,1.0,1.0,1.0]],
|
||||||
|
"10":[[0.0,0.0,0.0,1.0,0.75,1.0]],
|
||||||
|
"11":[[0.0,0.25,0.0,1.0,1.0,1.0]],
|
||||||
|
"12":[[0.0,0.0,0.0,1.0,1.0,0.25],[0.375,0.375,0.25,0.625,0.625,1.25]],
|
||||||
|
"13":[[0.0,0.0,0.0,1.0,1.0,0.25],[0.375,0.375,0.25,0.625,0.625,1.0]],
|
||||||
|
"14":[[0.0,0.375,0.375,1.0,0.625,0.625],[0.75,0.0,0.0,1.0,0.375,1.0],[0.75,0.375,0.0,1.0,1.0,0.375],[0.75,0.375,0.625,1.0,1.0,1.0],[0.75,0.625,0.375,1.0,1.0,0.625]],
|
||||||
|
"15":[[-0.25,0.375,0.375,1.0,0.625,0.625],[0.75,0.0,0.0,1.0,0.375,1.0],[0.75,0.375,0.0,1.0,1.0,0.375],[0.75,0.375,0.625,1.0,1.0,1.0],[0.75,0.625,0.375,1.0,1.0,0.625]],
|
||||||
|
"16":[[0.0,0.0,0.75,1.0,1.0,1.0],[0.375,0.375,0.0,0.625,0.625,0.75]],
|
||||||
|
"17":[[0.0,0.0,0.75,1.0,1.0,1.0],[0.375,0.375,-0.25,0.625,0.625,0.75]],
|
||||||
|
"18":[[0.0,0.0,0.0,0.25,1.0,1.0],[0.25,0.375,0.375,1.0,0.625,0.625]],
|
||||||
|
"19":[[0.0,0.0,0.0,0.25,1.0,1.0],[0.25,0.375,0.375,1.25,0.625,0.625]],
|
||||||
|
"20":[[0.0,0.75,0.0,1.0,1.0,1.0],[0.375,0.0,0.375,0.625,0.75,0.625]],
|
||||||
|
"21":[[0.0,0.75,0.0,1.0,1.0,1.0],[0.375,-0.25,0.375,0.625,0.75,0.625]],
|
||||||
|
"22":[[0.0,0.0,0.0,1.0,0.25,1.0],[0.375,0.25,0.375,0.625,1.0,0.625]],
|
||||||
|
"23":[[0.0,0.0,0.0,1.0,0.25,1.0],[0.375,0.25,0.375,0.625,1.25,0.625]],
|
||||||
|
"24":[[0.0,0.0,0.0,1.0,0.5,1.0],[0.0,0.5,0.0,1.0,1.0,0.5]],
|
||||||
|
"25":[[0.0,0.0,0.0,1.0,1.0,0.5],[0.0,0.5,0.5,1.0,1.0,1.0]],
|
||||||
|
"26":[[0.0,0.0,0.0,0.5,1.0,1.0],[0.5,0.0,0.0,1.0,1.0,0.5],[0.5,0.5,0.5,1.0,1.0,1.0]],
|
||||||
|
"27":[[0.0,0.0,0.0,1.0,1.0,0.5],[0.0,0.5,0.5,1.0,1.0,1.0],[0.5,0.0,0.5,1.0,0.5,1.0]],
|
||||||
|
"28":[[0.0,0.0,0.0,0.5,1.0,0.5],[0.0,0.5,0.5,1.0,1.0,1.0],[0.5,0.5,0.0,1.0,1.0,0.5]],
|
||||||
|
"29":[[0.0,0.5,0.0,1.0,1.0,1.0],[0.5,0.0,0.0,1.0,0.5,0.5]],
|
||||||
|
"30":[[0.0,0.0,0.0,1.0,0.5,1.0],[0.0,0.5,0.0,0.5,1.0,1.0],[0.5,0.5,0.0,1.0,1.0,0.5]],
|
||||||
|
"31":[[0.0,0.0,0.0,1.0,0.5,1.0],[0.0,0.5,0.0,1.0,1.0,0.5],[0.5,0.5,0.5,1.0,1.0,1.0]],
|
||||||
|
"32":[[0.0,0.0,0.0,1.0,0.5,1.0],[0.0,0.5,0.0,0.5,1.0,0.5]],
|
||||||
|
"33":[[0.0,0.0,0.0,1.0,0.5,1.0],[0.5,0.5,0.0,1.0,1.0,0.5]],
|
||||||
|
"34":[[0.0,0.0,0.5,1.0,1.0,1.0],[0.0,0.5,0.0,1.0,1.0,0.5]],
|
||||||
|
"35":[[0.0,0.0,0.5,1.0,1.0,1.0],[0.0,0.5,0.0,1.0,1.0,0.5],[0.5,0.0,0.0,1.0,0.5,0.5]],
|
||||||
|
"36":[[0.0,0.0,0.0,0.5,1.0,1.0],[0.5,0.0,0.5,1.0,1.0,1.0],[0.5,0.5,0.0,1.0,1.0,0.5]],
|
||||||
|
"37":[[0.0,0.5,0.0,1.0,1.0,1.0],[0.5,0.0,0.5,1.0,0.5,1.0]],
|
||||||
|
"38":[[0.0,0.0,0.5,0.5,1.0,1.0],[0.0,0.5,0.0,1.0,1.0,0.5],[0.5,0.5,0.5,1.0,1.0,1.0]],
|
||||||
|
"39":[[0.0,0.0,0.0,1.0,0.5,1.0],[0.0,0.5,0.5,1.0,1.0,1.0]],
|
||||||
|
"40":[[0.0,0.0,0.0,1.0,0.5,1.0],[0.0,0.5,0.5,1.0,1.0,1.0],[0.5,0.5,0.0,1.0,1.0,0.5]],
|
||||||
|
"41":[[0.0,0.0,0.0,1.0,0.5,1.0],[0.0,0.5,0.0,0.5,1.0,1.0],[0.5,0.5,0.5,1.0,1.0,1.0]],
|
||||||
|
"42":[[0.0,0.0,0.0,1.0,0.5,1.0],[0.5,0.5,0.5,1.0,1.0,1.0]],
|
||||||
|
"43":[[0.0,0.0,0.0,1.0,0.5,1.0],[0.0,0.5,0.5,0.5,1.0,1.0]],
|
||||||
|
"44":[[0.0,0.0,0.0,0.5,1.0,1.0],[0.5,0.5,0.0,1.0,1.0,1.0]],
|
||||||
|
"45":[[0.0,0.0,0.0,1.0,0.5,1.0],[0.0,0.5,0.0,0.5,1.0,1.0]],
|
||||||
|
"46":[[0.0,0.5,0.0,1.0,1.0,1.0],[0.5,0.0,0.0,1.0,0.5,1.0]],
|
||||||
|
"47":[[0.0,0.0,0.0,1.0,0.5,1.0],[0.5,0.5,0.0,1.0,1.0,1.0]],
|
||||||
|
"48":[[0.0625,0.0,0.0625,0.9375,0.875,0.9375]],
|
||||||
|
"49":[[0.0625,0.0,0.0625,1.0,0.875,0.9375]],
|
||||||
|
"50":[[0.0,0.0,0.0625,0.9375,0.875,0.9375]],
|
||||||
|
"51":[[0.0625,0.0,0.0,0.9375,0.875,0.9375]],
|
||||||
|
"52":[[0.0625,0.0,0.0625,0.9375,0.875,1.0]],
|
||||||
|
"53":[[0.0,0.0,0.0,1.0,0.9375,1.0]],
|
||||||
|
"54":[[0.0,0.0,0.8125,1.0,1.0,1.0]],
|
||||||
|
"55":[[0.0,0.0,0.0,0.1875,1.0,1.0]],
|
||||||
|
"56":[[0.8125,0.0,0.0,1.0,1.0,1.0]],
|
||||||
|
"57":[[0.0,0.0,0.0,1.0,1.0,0.1875]],
|
||||||
|
"58":[[0.0,0.0,0.0,1.0,0.125,1.0]],
|
||||||
|
"59":[[0.0,0.0,0.0,1.0,0.25,1.0]],
|
||||||
|
"60":[[0.0,0.0,0.0,1.0,0.375,1.0]],
|
||||||
|
"61":[[0.0,0.0,0.0,1.0,0.5,1.0]],
|
||||||
|
"62":[[0.0,0.0,0.0,1.0,0.625,1.0]],
|
||||||
|
"63":[[0.0,0.0,0.0,1.0,0.875,1.0]],
|
||||||
|
"64":[[0.0625,0.0,0.0625,0.9375,0.9375,0.9375]],
|
||||||
|
"65":[[0.375,0.0,0.375,0.625,1.5,0.625]],
|
||||||
|
"66":[[0.0,0.0,0.375,1.0,1.5,0.625],[0.375,0.0,0.0,0.625,1.5,0.375],[0.375,0.0,0.625,0.625,1.5,1.0]],
|
||||||
|
"67":[[0.375,0.0,0.0,0.625,1.5,1.0],[0.625,0.0,0.375,1.0,1.5,0.625]],
|
||||||
|
"68":[[0.0,0.0,0.375,1.0,1.5,0.625],[0.375,0.0,0.0,0.625,1.5,0.375]],
|
||||||
|
"69":[[0.375,0.0,0.0,0.625,1.5,0.625],[0.625,0.0,0.375,1.0,1.5,0.625]],
|
||||||
|
"70":[[0.0,0.0,0.375,1.0,1.5,0.625],[0.375,0.0,0.625,0.625,1.5,1.0]],
|
||||||
|
"71":[[0.375,0.0,0.375,0.625,1.5,1.0],[0.625,0.0,0.375,1.0,1.5,0.625]],
|
||||||
|
"72":[[0.0,0.0,0.375,1.0,1.5,0.625]],
|
||||||
|
"73":[[0.375,0.0,0.375,1.0,1.5,0.625]],
|
||||||
|
"74":[[0.0,0.0,0.375,0.625,1.5,0.625],[0.375,0.0,0.0,0.625,1.5,0.375],[0.375,0.0,0.625,0.625,1.5,1.0]],
|
||||||
|
"75":[[0.375,0.0,0.0,0.625,1.5,1.0]],
|
||||||
|
"76":[[0.0,0.0,0.375,0.625,1.5,0.625],[0.375,0.0,0.0,0.625,1.5,0.375]],
|
||||||
|
"77":[[0.375,0.0,0.0,0.625,1.5,0.625]],
|
||||||
|
"78":[[0.0,0.0,0.375,0.625,1.5,0.625],[0.375,0.0,0.625,0.625,1.5,1.0]],
|
||||||
|
"79":[[0.375,0.0,0.375,0.625,1.5,1.0]],
|
||||||
|
"80":[[0.0,0.0,0.375,0.625,1.5,0.625]],
|
||||||
|
"81":[[0.0625,0.0,0.0625,0.9375,0.5,0.9375]],
|
||||||
|
"82":[[0.1875,0.0,0.0625,0.9375,0.5,0.9375]],
|
||||||
|
"83":[[0.3125,0.0,0.0625,0.9375,0.5,0.9375]],
|
||||||
|
"84":[[0.4375,0.0,0.0625,0.9375,0.5,0.9375]],
|
||||||
|
"85":[[0.5625,0.0,0.0625,0.9375,0.5,0.9375]],
|
||||||
|
"86":[[0.6875,0.0,0.0625,0.9375,0.5,0.9375]],
|
||||||
|
"87":[[0.8125,0.0,0.0625,0.9375,0.5,0.9375]],
|
||||||
|
"88":[[0.0,0.0,0.0,1.0,0.1875,1.0]],
|
||||||
|
"89":[[0.0,0.8125,0.0,1.0,1.0,1.0]],
|
||||||
|
"90":[[0.4375,0.0,0.4375,0.5625,1.0,0.5625]],
|
||||||
|
"91":[[0.0,0.0,0.4375,1.0,1.0,0.5625],[0.4375,0.0,0.0,0.5625,1.0,0.4375],[0.4375,0.0,0.5625,0.5625,1.0,1.0]],
|
||||||
|
"92":[[0.4375,0.0,0.0,0.5625,1.0,1.0],[0.5625,0.0,0.4375,1.0,1.0,0.5625]],
|
||||||
|
"93":[[0.0,0.0,0.4375,1.0,1.0,0.5625],[0.4375,0.0,0.0,0.5625,1.0,0.4375]],
|
||||||
|
"94":[[0.4375,0.0,0.0,0.5625,1.0,0.5625],[0.5625,0.0,0.4375,1.0,1.0,0.5625]],
|
||||||
|
"95":[[0.0,0.0,0.4375,1.0,1.0,0.5625],[0.4375,0.0,0.5625,0.5625,1.0,1.0]],
|
||||||
|
"96":[[0.4375,0.0,0.4375,0.5625,1.0,1.0],[0.5625,0.0,0.4375,1.0,1.0,0.5625]],
|
||||||
|
"97":[[0.0,0.0,0.4375,1.0,1.0,0.5625]],
|
||||||
|
"98":[[0.4375,0.0,0.4375,1.0,1.0,0.5625]],
|
||||||
|
"99":[[0.0,0.0,0.4375,0.5625,1.0,0.5625],[0.4375,0.0,0.0,0.5625,1.0,0.4375],[0.4375,0.0,0.5625,0.5625,1.0,1.0]],
|
||||||
|
"100":[[0.4375,0.0,0.0,0.5625,1.0,1.0]],
|
||||||
|
"101":[[0.0,0.0,0.4375,0.5625,1.0,0.5625],[0.4375,0.0,0.0,0.5625,1.0,0.4375]],
|
||||||
|
"102":[[0.4375,0.0,0.0,0.5625,1.0,0.5625]],
|
||||||
|
"103":[[0.0,0.0,0.4375,0.5625,1.0,0.5625],[0.4375,0.0,0.5625,0.5625,1.0,1.0]],
|
||||||
|
"104":[[0.4375,0.0,0.4375,0.5625,1.0,1.0]],
|
||||||
|
"105":[[0.0,0.0,0.4375,0.5625,1.0,0.5625]],
|
||||||
|
"106":[[0.0625,0.0,0.0625,0.9375,0.09375,0.9375]],
|
||||||
|
"107":[[0.0625,0.0,0.0625,0.9375,0.125,0.9375],[0.4375,0.125,0.4375,0.5625,0.875,0.5625]],
|
||||||
|
"108":[[0.0,0.0,0.0,0.125,1.0,0.25],[0.0,0.0,0.75,0.125,1.0,1.0],[0.0,0.1875,0.25,1.0,0.25,0.75],[0.0,0.25,0.25,0.125,1.0,0.75],[0.125,0.0,0.0,0.25,1.0,0.125],[0.125,0.0,0.875,0.25,1.0,1.0],[0.125,0.1875,0.125,1.0,0.25,0.25],[0.125,0.1875,0.75,1.0,0.25,0.875],[0.25,0.1875,0.0,1.0,1.0,0.125],[0.25,0.1875,0.875,1.0,1.0,1.0],[0.75,0.0,0.0,1.0,0.1875,0.125],[0.75,0.0,0.875,1.0,0.1875,1.0],[0.875,0.0,0.125,1.0,0.1875,0.25],[0.875,0.0,0.75,1.0,0.1875,0.875],[0.875,0.25,0.125,1.0,1.0,0.875]],
|
||||||
|
"109":[[0.0,0.0,0.0,1.0,0.8125,1.0]],
|
||||||
|
"110":[[0.0,0.0,0.0,1.0,0.8125,1.0],[0.25,0.8125,0.25,0.75,1.0,0.75]],
|
||||||
|
"111":[[0.0625,0.0,0.0625,0.9375,1.0,0.9375]],
|
||||||
|
"112":[[0.375,0.4375,0.0625,0.625,0.75,0.3125]],
|
||||||
|
"113":[[0.375,0.4375,0.6875,0.625,0.75,0.9375]],
|
||||||
|
"114":[[0.0625,0.4375,0.375,0.3125,0.75,0.625]],
|
||||||
|
"115":[[0.6875,0.4375,0.375,0.9375,0.75,0.625]],
|
||||||
|
"116":[[0.3125,0.3125,0.0625,0.6875,0.75,0.4375]],
|
||||||
|
"117":[[0.3125,0.3125,0.5625,0.6875,0.75,0.9375]],
|
||||||
|
"118":[[0.0625,0.3125,0.3125,0.4375,0.75,0.6875]],
|
||||||
|
"119":[[0.5625,0.3125,0.3125,0.9375,0.75,0.6875]],
|
||||||
|
"120":[[0.25,0.1875,0.0625,0.75,0.75,0.5625]],
|
||||||
|
"121":[[0.25,0.1875,0.4375,0.75,0.75,0.9375]],
|
||||||
|
"122":[[0.0625,0.1875,0.25,0.5625,0.75,0.75]],
|
||||||
|
"123":[[0.4375,0.1875,0.25,0.9375,0.75,0.75]],
|
||||||
|
"124":[[0.25,0.0,0.25,0.75,1.5,0.75]],
|
||||||
|
"125":[[0.0,0.0,0.3125,1.0,1.5,0.6875],[0.25,0.0,0.25,0.75,1.5,0.3125],[0.25,0.0,0.6875,0.75,1.5,0.75],[0.3125,0.0,0.0,0.6875,1.5,0.25],[0.3125,0.0,0.75,0.6875,1.5,1.0]],
|
||||||
|
"126":[[0.25,0.0,0.25,0.75,1.5,0.75],[0.3125,0.0,0.0,0.6875,1.5,0.25],[0.3125,0.0,0.75,0.6875,1.5,1.0],[0.75,0.0,0.3125,1.0,1.5,0.6875]],
|
||||||
|
"127":[[0.0,0.0,0.3125,1.0,1.5,0.6875],[0.3125,0.0,0.0,0.6875,1.5,0.3125],[0.3125,0.0,0.6875,0.6875,1.5,1.0]],
|
||||||
|
"128":[[0.3125,0.0,0.0,0.6875,1.5,1.0],[0.6875,0.0,0.3125,1.0,1.5,0.6875]],
|
||||||
|
"129":[[0.0,0.0,0.3125,1.0,1.5,0.6875],[0.25,0.0,0.25,0.75,1.5,0.3125],[0.25,0.0,0.6875,0.75,1.5,0.75],[0.3125,0.0,0.0,0.6875,1.5,0.25]],
|
||||||
|
"130":[[0.25,0.0,0.25,0.75,1.5,0.75],[0.3125,0.0,0.0,0.6875,1.5,0.25],[0.75,0.0,0.3125,1.0,1.5,0.6875]],
|
||||||
|
"131":[[0.0,0.0,0.3125,1.0,1.5,0.6875],[0.3125,0.0,0.0,0.6875,1.5,0.3125]],
|
||||||
|
"132":[[0.3125,0.0,0.0,0.6875,1.5,0.6875],[0.6875,0.0,0.3125,1.0,1.5,0.6875]],
|
||||||
|
"133":[[0.0,0.0,0.3125,1.0,1.5,0.6875],[0.25,0.0,0.25,0.75,1.5,0.3125],[0.25,0.0,0.6875,0.75,1.5,0.75],[0.3125,0.0,0.75,0.6875,1.5,1.0]],
|
||||||
|
"134":[[0.25,0.0,0.25,0.75,1.5,0.75],[0.3125,0.0,0.75,0.6875,1.5,1.0],[0.75,0.0,0.3125,1.0,1.5,0.6875]],
|
||||||
|
"135":[[0.0,0.0,0.3125,1.0,1.5,0.6875],[0.3125,0.0,0.6875,0.6875,1.5,1.0]],
|
||||||
|
"136":[[0.3125,0.0,0.3125,0.6875,1.5,1.0],[0.6875,0.0,0.3125,1.0,1.5,0.6875]],
|
||||||
|
"137":[[0.0,0.0,0.3125,1.0,1.5,0.6875],[0.25,0.0,0.25,0.75,1.5,0.3125],[0.25,0.0,0.6875,0.75,1.5,0.75]],
|
||||||
|
"138":[[0.25,0.0,0.25,0.75,1.5,0.75],[0.75,0.0,0.3125,1.0,1.5,0.6875]],
|
||||||
|
"139":[[0.0,0.0,0.3125,1.0,1.5,0.6875]],
|
||||||
|
"140":[[0.3125,0.0,0.3125,1.0,1.5,0.6875]],
|
||||||
|
"141":[[0.0,0.0,0.3125,0.75,1.5,0.6875],[0.25,0.0,0.25,0.75,1.5,0.3125],[0.25,0.0,0.6875,0.75,1.5,0.75],[0.3125,0.0,0.0,0.6875,1.5,0.25],[0.3125,0.0,0.75,0.6875,1.5,1.0]],
|
||||||
|
"142":[[0.25,0.0,0.25,0.75,1.5,0.75],[0.3125,0.0,0.0,0.6875,1.5,0.25],[0.3125,0.0,0.75,0.6875,1.5,1.0]],
|
||||||
|
"143":[[0.0,0.0,0.3125,0.6875,1.5,0.6875],[0.3125,0.0,0.0,0.6875,1.5,0.3125],[0.3125,0.0,0.6875,0.6875,1.5,1.0]],
|
||||||
|
"144":[[0.3125,0.0,0.0,0.6875,1.5,1.0]],
|
||||||
|
"145":[[0.0,0.0,0.3125,0.75,1.5,0.6875],[0.25,0.0,0.25,0.75,1.5,0.3125],[0.25,0.0,0.6875,0.75,1.5,0.75],[0.3125,0.0,0.0,0.6875,1.5,0.25]],
|
||||||
|
"146":[[0.25,0.0,0.25,0.75,1.5,0.75],[0.3125,0.0,0.0,0.6875,1.5,0.25]],
|
||||||
|
"147":[[0.0,0.0,0.3125,0.6875,1.5,0.6875],[0.3125,0.0,0.0,0.6875,1.5,0.3125]],
|
||||||
|
"148":[[0.3125,0.0,0.0,0.6875,1.5,0.6875]],
|
||||||
|
"149":[[0.0,0.0,0.3125,0.75,1.5,0.6875],[0.25,0.0,0.25,0.75,1.5,0.3125],[0.25,0.0,0.6875,0.75,1.5,0.75],[0.3125,0.0,0.75,0.6875,1.5,1.0]],
|
||||||
|
"150":[[0.25,0.0,0.25,0.75,1.5,0.75],[0.3125,0.0,0.75,0.6875,1.5,1.0]],
|
||||||
|
"151":[[0.0,0.0,0.3125,0.6875,1.5,0.6875],[0.3125,0.0,0.6875,0.6875,1.5,1.0]],
|
||||||
|
"152":[[0.3125,0.0,0.3125,0.6875,1.5,1.0]],
|
||||||
|
"153":[[0.0,0.0,0.3125,0.75,1.5,0.6875],[0.25,0.0,0.25,0.75,1.5,0.3125],[0.25,0.0,0.6875,0.75,1.5,0.75]],
|
||||||
|
"154":[[0.0,0.0,0.3125,0.6875,1.5,0.6875]],
|
||||||
|
"155":[[0.5,0.0,0.5,0.5,1.5,0.5]],
|
||||||
|
"156":[[0.3125,0.0,0.3125,0.6875,0.375,0.6875]],
|
||||||
|
"157":[[0.25,0.0,0.25,0.75,0.5,0.75]],
|
||||||
|
"158":[[0.25,0.25,0.5,0.75,0.75,1.0]],
|
||||||
|
"159":[[0.25,0.25,0.0,0.75,0.75,0.5]],
|
||||||
|
"160":[[0.5,0.25,0.25,1.0,0.75,0.75]],
|
||||||
|
"161":[[0.0,0.25,0.25,0.5,0.75,0.75]],
|
||||||
|
"162":[[0.125,0.0,0.125,0.875,0.25,0.875],[0.1875,0.625,0.0,0.8125,1.0,1.0],[0.25,0.25,0.1875,0.75,0.3125,0.8125],[0.375,0.3125,0.25,0.625,0.625,0.75]],
|
||||||
|
"163":[[0.0,0.625,0.1875,1.0,1.0,0.8125],[0.125,0.0,0.125,0.875,0.25,0.875],[0.1875,0.25,0.25,0.8125,0.3125,0.75],[0.25,0.3125,0.375,0.75,0.625,0.625]],
|
||||||
|
"164":[[0.0,0.625,0.0,1.0,0.6875,1.0],[0.0,0.6875,0.0,0.125,1.0,1.0],[0.125,0.6875,0.0,1.0,1.0,0.125],[0.125,0.6875,0.875,1.0,1.0,1.0],[0.25,0.25,0.25,0.75,0.625,0.75],[0.375,0.0,0.375,0.625,0.25,0.625],[0.875,0.6875,0.125,1.0,1.0,0.875]],
|
||||||
|
"165":[[0.0,0.625,0.0,1.0,0.6875,1.0],[0.0,0.6875,0.0,0.125,1.0,1.0],[0.125,0.6875,0.0,1.0,1.0,0.125],[0.125,0.6875,0.875,1.0,1.0,1.0],[0.25,0.25,0.25,0.75,0.625,0.75],[0.375,0.25,0.0,0.625,0.5,0.25],[0.875,0.6875,0.125,1.0,1.0,0.875]],
|
||||||
|
"166":[[0.0,0.625,0.0,1.0,0.6875,1.0],[0.0,0.6875,0.0,0.125,1.0,1.0],[0.125,0.6875,0.0,1.0,1.0,0.125],[0.125,0.6875,0.875,1.0,1.0,1.0],[0.25,0.25,0.25,0.75,0.625,0.75],[0.375,0.25,0.75,0.625,0.5,1.0],[0.875,0.6875,0.125,1.0,1.0,0.875]],
|
||||||
|
"167":[[0.0,0.25,0.375,0.75,0.5,0.625],[0.0,0.625,0.0,1.0,0.6875,1.0],[0.0,0.6875,0.0,0.125,1.0,1.0],[0.125,0.6875,0.0,1.0,1.0,0.125],[0.125,0.6875,0.875,1.0,1.0,1.0],[0.25,0.25,0.25,0.75,0.625,0.375],[0.25,0.25,0.625,0.75,0.625,0.75],[0.25,0.5,0.375,0.75,0.625,0.625],[0.875,0.6875,0.125,1.0,1.0,0.875]],
|
||||||
|
"168":[[0.0,0.625,0.0,1.0,0.6875,1.0],[0.0,0.6875,0.0,0.125,1.0,1.0],[0.125,0.6875,0.0,1.0,1.0,0.125],[0.125,0.6875,0.875,1.0,1.0,1.0],[0.25,0.25,0.25,0.75,0.625,0.75],[0.75,0.25,0.375,1.0,0.5,0.625],[0.875,0.6875,0.125,1.0,1.0,0.875]],
|
||||||
|
"169":[[0.0,0.5,0.0,1.0,1.0,1.0]],
|
||||||
|
"170":[[0.0,0.0,0.0,1.0,0.0625,1.0]],
|
||||||
|
"171":[[0.375,0.0,0.375,0.625,1.0,0.625]],
|
||||||
|
"172":[[0.375,0.375,0.0,0.625,0.625,1.0]],
|
||||||
|
"173":[[0.0,0.375,0.375,1.0,0.625,0.625]],
|
||||||
|
"174":[[0.1875,0.1875,0.1875,0.8125,0.8125,0.8125]],
|
||||||
|
"175":[[0.0,0.1875,0.1875,1.0,0.8125,0.8125],[0.1875,0.0,0.1875,0.8125,0.1875,0.8125],[0.1875,0.1875,0.0,0.8125,0.8125,0.1875],[0.1875,0.1875,0.8125,0.8125,0.8125,1.0],[0.1875,0.8125,0.1875,0.8125,1.0,0.8125]],
|
||||||
|
"176":[[0.1875,0.0,0.1875,0.8125,1.0,0.8125],[0.1875,0.1875,0.0,0.8125,0.8125,0.1875],[0.1875,0.1875,0.8125,0.8125,0.8125,1.0],[0.8125,0.1875,0.1875,1.0,0.8125,0.8125]],
|
||||||
|
"177":[[0.0,0.1875,0.1875,1.0,0.8125,0.8125],[0.1875,0.0,0.1875,0.8125,0.1875,0.8125],[0.1875,0.1875,0.0,0.8125,0.8125,0.1875],[0.1875,0.1875,0.8125,0.8125,0.8125,1.0]],
|
||||||
|
"178":[[0.1875,0.0,0.1875,0.8125,0.8125,0.8125],[0.1875,0.1875,0.0,0.8125,0.8125,0.1875],[0.1875,0.1875,0.8125,0.8125,0.8125,1.0],[0.8125,0.1875,0.1875,1.0,0.8125,0.8125]],
|
||||||
|
"179":[[0.0,0.1875,0.1875,1.0,0.8125,0.8125],[0.1875,0.0,0.1875,0.8125,0.1875,0.8125],[0.1875,0.1875,0.0,0.8125,0.8125,0.1875],[0.1875,0.8125,0.1875,0.8125,1.0,0.8125]],
|
||||||
|
"180":[[0.1875,0.0,0.1875,0.8125,1.0,0.8125],[0.1875,0.1875,0.0,0.8125,0.8125,0.1875],[0.8125,0.1875,0.1875,1.0,0.8125,0.8125]],
|
||||||
|
"181":[[0.0,0.1875,0.1875,1.0,0.8125,0.8125],[0.1875,0.0,0.1875,0.8125,0.1875,0.8125],[0.1875,0.1875,0.0,0.8125,0.8125,0.1875]],
|
||||||
|
"182":[[0.1875,0.0,0.1875,0.8125,0.8125,0.8125],[0.1875,0.1875,0.0,0.8125,0.8125,0.1875],[0.8125,0.1875,0.1875,1.0,0.8125,0.8125]],
|
||||||
|
"183":[[0.0,0.1875,0.1875,1.0,0.8125,0.8125],[0.1875,0.0,0.1875,0.8125,0.1875,0.8125],[0.1875,0.1875,0.8125,0.8125,0.8125,1.0],[0.1875,0.8125,0.1875,0.8125,1.0,0.8125]],
|
||||||
|
"184":[[0.1875,0.0,0.1875,0.8125,1.0,0.8125],[0.1875,0.1875,0.8125,0.8125,0.8125,1.0],[0.8125,0.1875,0.1875,1.0,0.8125,0.8125]],
|
||||||
|
"185":[[0.0,0.1875,0.1875,1.0,0.8125,0.8125],[0.1875,0.0,0.1875,0.8125,0.1875,0.8125],[0.1875,0.1875,0.8125,0.8125,0.8125,1.0]],
|
||||||
|
"186":[[0.1875,0.0,0.1875,0.8125,0.8125,0.8125],[0.1875,0.1875,0.8125,0.8125,0.8125,1.0],[0.8125,0.1875,0.1875,1.0,0.8125,0.8125]],
|
||||||
|
"187":[[0.0,0.1875,0.1875,1.0,0.8125,0.8125],[0.1875,0.0,0.1875,0.8125,0.1875,0.8125],[0.1875,0.8125,0.1875,0.8125,1.0,0.8125]],
|
||||||
|
"188":[[0.1875,0.0,0.1875,0.8125,1.0,0.8125],[0.8125,0.1875,0.1875,1.0,0.8125,0.8125]],
|
||||||
|
"189":[[0.0,0.1875,0.1875,1.0,0.8125,0.8125],[0.1875,0.0,0.1875,0.8125,0.1875,0.8125]],
|
||||||
|
"190":[[0.1875,0.0,0.1875,0.8125,0.8125,0.8125],[0.8125,0.1875,0.1875,1.0,0.8125,0.8125]],
|
||||||
|
"191":[[0.0,0.1875,0.1875,0.8125,0.8125,0.8125],[0.1875,0.0,0.1875,0.8125,0.1875,0.8125],[0.1875,0.1875,0.0,0.8125,0.8125,0.1875],[0.1875,0.1875,0.8125,0.8125,0.8125,1.0],[0.1875,0.8125,0.1875,0.8125,1.0,0.8125]],
|
||||||
|
"192":[[0.1875,0.0,0.1875,0.8125,1.0,0.8125],[0.1875,0.1875,0.0,0.8125,0.8125,0.1875],[0.1875,0.1875,0.8125,0.8125,0.8125,1.0]],
|
||||||
|
"193":[[0.0,0.1875,0.1875,0.8125,0.8125,0.8125],[0.1875,0.0,0.1875,0.8125,0.1875,0.8125],[0.1875,0.1875,0.0,0.8125,0.8125,0.1875],[0.1875,0.1875,0.8125,0.8125,0.8125,1.0]],
|
||||||
|
"194":[[0.1875,0.0,0.1875,0.8125,0.8125,0.8125],[0.1875,0.1875,0.0,0.8125,0.8125,0.1875],[0.1875,0.1875,0.8125,0.8125,0.8125,1.0]],
|
||||||
|
"195":[[0.0,0.1875,0.1875,0.8125,0.8125,0.8125],[0.1875,0.0,0.1875,0.8125,0.1875,0.8125],[0.1875,0.1875,0.0,0.8125,0.8125,0.1875],[0.1875,0.8125,0.1875,0.8125,1.0,0.8125]],
|
||||||
|
"196":[[0.1875,0.0,0.1875,0.8125,1.0,0.8125],[0.1875,0.1875,0.0,0.8125,0.8125,0.1875]],
|
||||||
|
"197":[[0.0,0.1875,0.1875,0.8125,0.8125,0.8125],[0.1875,0.0,0.1875,0.8125,0.1875,0.8125],[0.1875,0.1875,0.0,0.8125,0.8125,0.1875]],
|
||||||
|
"198":[[0.1875,0.0,0.1875,0.8125,0.8125,0.8125],[0.1875,0.1875,0.0,0.8125,0.8125,0.1875]],
|
||||||
|
"199":[[0.0,0.1875,0.1875,0.8125,0.8125,0.8125],[0.1875,0.0,0.1875,0.8125,0.1875,0.8125],[0.1875,0.1875,0.8125,0.8125,0.8125,1.0],[0.1875,0.8125,0.1875,0.8125,1.0,0.8125]],
|
||||||
|
"200":[[0.1875,0.0,0.1875,0.8125,1.0,0.8125],[0.1875,0.1875,0.8125,0.8125,0.8125,1.0]],
|
||||||
|
"201":[[0.0,0.1875,0.1875,0.8125,0.8125,0.8125],[0.1875,0.0,0.1875,0.8125,0.1875,0.8125],[0.1875,0.1875,0.8125,0.8125,0.8125,1.0]],
|
||||||
|
"202":[[0.1875,0.0,0.1875,0.8125,0.8125,0.8125],[0.1875,0.1875,0.8125,0.8125,0.8125,1.0]],
|
||||||
|
"203":[[0.0,0.1875,0.1875,0.8125,0.8125,0.8125],[0.1875,0.0,0.1875,0.8125,0.1875,0.8125],[0.1875,0.8125,0.1875,0.8125,1.0,0.8125]],
|
||||||
|
"204":[[0.1875,0.0,0.1875,0.8125,1.0,0.8125]],
|
||||||
|
"205":[[0.0,0.1875,0.1875,0.8125,0.8125,0.8125],[0.1875,0.0,0.1875,0.8125,0.1875,0.8125]],
|
||||||
|
"206":[[0.1875,0.0,0.1875,0.8125,0.8125,0.8125]],
|
||||||
|
"207":[[0.0,0.1875,0.1875,1.0,0.8125,0.8125],[0.1875,0.1875,0.0,0.8125,0.8125,0.1875],[0.1875,0.1875,0.8125,0.8125,0.8125,1.0],[0.1875,0.8125,0.1875,0.8125,1.0,0.8125]],
|
||||||
|
"208":[[0.1875,0.1875,0.0,0.8125,0.8125,1.0],[0.1875,0.8125,0.1875,0.8125,1.0,0.8125],[0.8125,0.1875,0.1875,1.0,0.8125,0.8125]],
|
||||||
|
"209":[[0.0,0.1875,0.1875,1.0,0.8125,0.8125],[0.1875,0.1875,0.0,0.8125,0.8125,0.1875],[0.1875,0.1875,0.8125,0.8125,0.8125,1.0]],
|
||||||
|
"210":[[0.1875,0.1875,0.0,0.8125,0.8125,1.0],[0.8125,0.1875,0.1875,1.0,0.8125,0.8125]],
|
||||||
|
"211":[[0.0,0.1875,0.1875,1.0,0.8125,0.8125],[0.1875,0.1875,0.0,0.8125,0.8125,0.1875],[0.1875,0.8125,0.1875,0.8125,1.0,0.8125]],
|
||||||
|
"212":[[0.1875,0.1875,0.0,0.8125,0.8125,0.8125],[0.1875,0.8125,0.1875,0.8125,1.0,0.8125],[0.8125,0.1875,0.1875,1.0,0.8125,0.8125]],
|
||||||
|
"213":[[0.0,0.1875,0.1875,1.0,0.8125,0.8125],[0.1875,0.1875,0.0,0.8125,0.8125,0.1875]],
|
||||||
|
"214":[[0.1875,0.1875,0.0,0.8125,0.8125,0.8125],[0.8125,0.1875,0.1875,1.0,0.8125,0.8125]],
|
||||||
|
"215":[[0.0,0.1875,0.1875,1.0,0.8125,0.8125],[0.1875,0.1875,0.8125,0.8125,0.8125,1.0],[0.1875,0.8125,0.1875,0.8125,1.0,0.8125]],
|
||||||
|
"216":[[0.1875,0.1875,0.1875,0.8125,0.8125,1.0],[0.1875,0.8125,0.1875,0.8125,1.0,0.8125],[0.8125,0.1875,0.1875,1.0,0.8125,0.8125]],
|
||||||
|
"217":[[0.0,0.1875,0.1875,1.0,0.8125,0.8125],[0.1875,0.1875,0.8125,0.8125,0.8125,1.0]],
|
||||||
|
"218":[[0.1875,0.1875,0.1875,0.8125,0.8125,1.0],[0.8125,0.1875,0.1875,1.0,0.8125,0.8125]],
|
||||||
|
"219":[[0.0,0.1875,0.1875,1.0,0.8125,0.8125],[0.1875,0.8125,0.1875,0.8125,1.0,0.8125]],
|
||||||
|
"220":[[0.1875,0.1875,0.1875,1.0,0.8125,0.8125],[0.1875,0.8125,0.1875,0.8125,1.0,0.8125]],
|
||||||
|
"221":[[0.0,0.1875,0.1875,1.0,0.8125,0.8125]],
|
||||||
|
"222":[[0.1875,0.1875,0.1875,1.0,0.8125,0.8125]],
|
||||||
|
"223":[[0.0,0.1875,0.1875,0.8125,0.8125,0.8125],[0.1875,0.1875,0.0,0.8125,0.8125,0.1875],[0.1875,0.1875,0.8125,0.8125,0.8125,1.0],[0.1875,0.8125,0.1875,0.8125,1.0,0.8125]],
|
||||||
|
"224":[[0.1875,0.1875,0.0,0.8125,0.8125,1.0],[0.1875,0.8125,0.1875,0.8125,1.0,0.8125]],
|
||||||
|
"225":[[0.0,0.1875,0.1875,0.8125,0.8125,0.8125],[0.1875,0.1875,0.0,0.8125,0.8125,0.1875],[0.1875,0.1875,0.8125,0.8125,0.8125,1.0]],
|
||||||
|
"226":[[0.1875,0.1875,0.0,0.8125,0.8125,1.0]],
|
||||||
|
"227":[[0.0,0.1875,0.1875,0.8125,0.8125,0.8125],[0.1875,0.1875,0.0,0.8125,0.8125,0.1875],[0.1875,0.8125,0.1875,0.8125,1.0,0.8125]],
|
||||||
|
"228":[[0.1875,0.1875,0.0,0.8125,0.8125,0.8125],[0.1875,0.8125,0.1875,0.8125,1.0,0.8125]],
|
||||||
|
"229":[[0.0,0.1875,0.1875,0.8125,0.8125,0.8125],[0.1875,0.1875,0.0,0.8125,0.8125,0.1875]],
|
||||||
|
"230":[[0.1875,0.1875,0.0,0.8125,0.8125,0.8125]],
|
||||||
|
"231":[[0.0,0.1875,0.1875,0.8125,0.8125,0.8125],[0.1875,0.1875,0.8125,0.8125,0.8125,1.0],[0.1875,0.8125,0.1875,0.8125,1.0,0.8125]],
|
||||||
|
"232":[[0.1875,0.1875,0.1875,0.8125,0.8125,1.0],[0.1875,0.8125,0.1875,0.8125,1.0,0.8125]],
|
||||||
|
"233":[[0.0,0.1875,0.1875,0.8125,0.8125,0.8125],[0.1875,0.1875,0.8125,0.8125,0.8125,1.0]],
|
||||||
|
"234":[[0.1875,0.1875,0.1875,0.8125,0.8125,1.0]],
|
||||||
|
"235":[[0.0,0.1875,0.1875,0.8125,0.8125,0.8125],[0.1875,0.8125,0.1875,0.8125,1.0,0.8125]],
|
||||||
|
"236":[[0.1875,0.1875,0.1875,0.8125,1.0,0.8125]],
|
||||||
|
"237":[[0.0,0.1875,0.1875,0.8125,0.8125,0.8125]],
|
||||||
|
"238":[[0.1875,0.0,0.1875,0.75,0.4375,0.75]],
|
||||||
|
"239":[[0.0625,0.0,0.0625,0.9375,0.4375,0.9375]],
|
||||||
|
"240":[[0.375,0.0,0.375,0.625,0.375,0.625]],
|
||||||
|
"241":[[0.1875,0.0,0.1875,0.8125,0.375,0.8125]],
|
||||||
|
"242":[[0.125,0.0,0.125,0.875,0.375,0.875]],
|
||||||
|
"243":[[0.125,0.0,0.125,0.875,0.4375,0.875]],
|
||||||
|
"244":[[0.3125,0.3125,0.3125,0.6875,0.6875,0.6875]],
|
||||||
|
"245":[[0.15625,0.0,0.15625,0.34375,1.0,0.34375]],
|
||||||
|
"246":[[0.0,0.0,0.0,0.125,1.0,0.125],[0.0,0.0,0.875,0.125,1.0,1.0],[0.0,0.875,0.125,1.0,1.0,0.875],[0.125,0.875,0.0,1.0,1.0,0.125],[0.125,0.875,0.875,1.0,1.0,1.0],[0.875,0.0,0.0,1.0,0.875,0.125],[0.875,0.0,0.875,1.0,0.875,1.0]],
|
||||||
|
"247":[[0.125,0.3125,0.1875,0.875,0.6875,0.5625],[0.125,0.375,0.5625,0.25,0.625,1.0],[0.25,0.125,0.0,0.75,0.3125,0.75],[0.25,0.3125,0.0,0.75,0.875,0.1875],[0.25,0.3125,0.5625,0.75,0.875,0.75],[0.25,0.6875,0.1875,0.75,0.875,0.5625],[0.75,0.375,0.5625,0.875,0.625,1.0]],
|
||||||
|
"248":[[0.125,0.0,0.375,0.25,0.8125,0.625],[0.125,0.4375,0.3125,0.875,0.8125,0.375],[0.125,0.4375,0.625,0.875,0.8125,0.6875],[0.25,0.25,0.125,0.75,0.4375,0.875],[0.25,0.4375,0.125,0.75,1.0,0.3125],[0.25,0.4375,0.375,0.875,0.8125,0.625],[0.25,0.4375,0.6875,0.75,1.0,0.875],[0.25,0.8125,0.3125,0.75,1.0,0.6875],[0.75,0.0,0.375,0.875,0.4375,0.625]],
|
||||||
|
"249":[[0.125,0.25,0.25,0.875,1.0,0.75],[0.3125,0.4375,0.125,0.6875,0.8125,0.25],[0.3125,0.4375,0.75,0.6875,0.8125,0.875],[0.375,0.0,0.125,0.625,0.4375,0.25],[0.375,0.0,0.75,0.625,0.4375,0.875]],
|
||||||
|
"250":[[0.125,0.3125,0.4375,0.875,0.6875,0.8125],[0.125,0.375,0.0,0.25,0.625,0.4375],[0.25,0.125,0.25,0.75,0.3125,1.0],[0.25,0.3125,0.25,0.75,0.875,0.4375],[0.25,0.3125,0.8125,0.75,0.875,1.0],[0.25,0.6875,0.4375,0.75,0.875,0.8125],[0.75,0.375,0.0,0.875,0.625,0.4375]],
|
||||||
|
"251":[[0.0,0.125,0.25,0.75,0.875,0.75],[0.1875,0.3125,0.125,0.5625,0.6875,0.25],[0.1875,0.3125,0.75,0.5625,0.6875,0.875],[0.5625,0.375,0.125,1.0,0.625,0.25],[0.5625,0.375,0.75,1.0,0.625,0.875]],
|
||||||
|
"252":[[0.0,0.375,0.125,0.8125,0.625,0.25],[0.0,0.375,0.75,0.8125,0.625,0.875],[0.25,0.125,0.25,1.0,0.875,0.75],[0.4375,0.3125,0.125,0.8125,0.375,0.25],[0.4375,0.3125,0.75,0.8125,0.375,0.875],[0.4375,0.625,0.125,0.8125,0.6875,0.25],[0.4375,0.625,0.75,0.8125,0.6875,0.875]],
|
||||||
|
"253":[[0.125,0.1875,0.3125,0.875,0.5625,0.6875],[0.125,0.5625,0.375,0.875,0.75,0.625],[0.125,0.75,0.375,0.25,1.0,0.625],[0.25,0.0,0.125,0.75,0.1875,0.875],[0.25,0.1875,0.125,0.75,0.75,0.3125],[0.25,0.1875,0.6875,0.75,0.75,0.875],[0.25,0.5625,0.3125,0.75,0.75,0.375],[0.25,0.5625,0.625,0.75,0.75,0.6875],[0.75,0.75,0.375,0.875,1.0,0.625]],
|
||||||
|
"254":[[0.125,0.0,0.25,0.875,0.75,0.75],[0.3125,0.1875,0.125,0.6875,0.5625,0.25],[0.3125,0.1875,0.75,0.6875,0.5625,0.875],[0.375,0.5625,0.125,0.625,1.0,0.25],[0.375,0.5625,0.75,0.625,1.0,0.875]],
|
||||||
|
"255":[[0.0,0.0,0.0,1.0,0.125,1.0],[0.25,0.125,0.25,0.75,0.875,0.75]],
|
||||||
|
"256":[[0.0,0.0,0.0,1.0,0.5625,1.0]],
|
||||||
|
"257":[[0.0,0.0,0.25,1.0,1.0,0.75]],
|
||||||
|
"258":[[0.25,0.0,0.0,0.75,1.0,1.0]],
|
||||||
|
"259":[[0.25,0.25,0.25,0.75,0.375,0.75],[0.3125,0.375,0.3125,0.6875,0.8125,0.6875],[0.4375,0.8125,0.4375,0.5625,1.0,0.5625]],
|
||||||
|
"260":[[0.25,0.25,0.25,0.75,0.375,0.75],[0.3125,0.375,0.3125,0.6875,0.8125,0.6875],[0.4375,0.8125,0.0,0.5625,0.9375,0.8125]],
|
||||||
|
"261":[[0.25,0.25,0.25,0.75,0.375,0.75],[0.3125,0.375,0.3125,0.6875,0.8125,0.6875],[0.4375,0.8125,0.1875,0.5625,0.9375,1.0]],
|
||||||
|
"262":[[0.0,0.8125,0.4375,0.8125,0.9375,0.5625],[0.25,0.25,0.25,0.75,0.375,0.75],[0.3125,0.375,0.3125,0.6875,0.8125,0.6875]],
|
||||||
|
"263":[[0.1875,0.8125,0.4375,1.0,0.9375,0.5625],[0.25,0.25,0.25,0.75,0.375,0.75],[0.3125,0.375,0.3125,0.6875,0.8125,0.6875]],
|
||||||
|
"264":[[0.25,0.25,0.25,0.75,0.375,0.75],[0.3125,0.375,0.3125,0.6875,0.8125,0.6875],[0.4375,0.8125,0.0,0.5625,0.9375,1.0]],
|
||||||
|
"265":[[0.0,0.8125,0.4375,1.0,0.9375,0.5625],[0.25,0.25,0.25,0.75,0.375,0.75],[0.3125,0.375,0.3125,0.6875,0.8125,0.6875]],
|
||||||
|
"266":[[0.3125,0.0,0.3125,0.6875,0.4375,0.6875],[0.375,0.4375,0.375,0.625,0.5625,0.625]],
|
||||||
|
"267":[[0.3125,0.0625,0.3125,0.6875,0.5,0.6875],[0.375,0.5,0.375,0.625,0.625,0.625]],
|
||||||
|
"268":[[0.0,0.0,0.0,1.0,0.4375,1.0]],
|
||||||
|
"269":[[0.0,0.0,0.0,1.0,0.125,1.0],[0.0,0.125,0.0,0.125,1.0,1.0],[0.125,0.125,0.0,1.0,1.0,0.125],[0.125,0.125,0.875,1.0,1.0,1.0],[0.875,0.125,0.125,1.0,1.0,0.875]]}}
|
||||||
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,200 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": "Speed",
|
||||||
|
"displayName": "Speed",
|
||||||
|
"type": "good"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"name": "Slowness",
|
||||||
|
"displayName": "Slowness",
|
||||||
|
"type": "bad"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"name": "Haste",
|
||||||
|
"displayName": "Haste",
|
||||||
|
"type": "good"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 4,
|
||||||
|
"name": "MiningFatigue",
|
||||||
|
"displayName": "Mining Fatigue",
|
||||||
|
"type": "bad"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 5,
|
||||||
|
"name": "Strength",
|
||||||
|
"displayName": "Strength",
|
||||||
|
"type": "good"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 6,
|
||||||
|
"name": "InstantHealth",
|
||||||
|
"displayName": "Instant Health",
|
||||||
|
"type": "good"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 7,
|
||||||
|
"name": "InstantDamage",
|
||||||
|
"displayName": "Instant Damage",
|
||||||
|
"type": "bad"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 8,
|
||||||
|
"name": "JumpBoost",
|
||||||
|
"displayName": "Jump Boost",
|
||||||
|
"type": "good"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 9,
|
||||||
|
"name": "Nausea",
|
||||||
|
"displayName": "Nausea",
|
||||||
|
"type": "bad"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 10,
|
||||||
|
"name": "Regeneration",
|
||||||
|
"displayName": "Regeneration",
|
||||||
|
"type": "good"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 11,
|
||||||
|
"name": "Resistance",
|
||||||
|
"displayName": "Resistance",
|
||||||
|
"type": "good"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 12,
|
||||||
|
"name": "FireResistance",
|
||||||
|
"displayName": "Fire Resistance",
|
||||||
|
"type": "good"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 13,
|
||||||
|
"name": "WaterBreathing",
|
||||||
|
"displayName": "Water Breathing",
|
||||||
|
"type": "good"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 14,
|
||||||
|
"name": "Invisibility",
|
||||||
|
"displayName": "Invisibility",
|
||||||
|
"type": "good"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 15,
|
||||||
|
"name": "Blindness",
|
||||||
|
"displayName": "Blindness",
|
||||||
|
"type": "bad"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 16,
|
||||||
|
"name": "NightVision",
|
||||||
|
"displayName": "Night Vision",
|
||||||
|
"type": "good"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 17,
|
||||||
|
"name": "Hunger",
|
||||||
|
"displayName": "Hunger",
|
||||||
|
"type": "bad"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 18,
|
||||||
|
"name": "Weakness",
|
||||||
|
"displayName": "Weakness",
|
||||||
|
"type": "bad"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 19,
|
||||||
|
"name": "Poison",
|
||||||
|
"displayName": "Poison",
|
||||||
|
"type": "bad"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 20,
|
||||||
|
"name": "Wither",
|
||||||
|
"displayName": "Wither",
|
||||||
|
"type": "bad"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 21,
|
||||||
|
"name": "HealthBoost",
|
||||||
|
"displayName": "Health Boost",
|
||||||
|
"type": "good"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 22,
|
||||||
|
"name": "Absorption",
|
||||||
|
"displayName": "Absorption",
|
||||||
|
"type": "good"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 23,
|
||||||
|
"name": "Saturation",
|
||||||
|
"displayName": "Saturation",
|
||||||
|
"type": "good"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 24,
|
||||||
|
"name": "Glowing",
|
||||||
|
"displayName": "Glowing",
|
||||||
|
"type": "bad"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 25,
|
||||||
|
"name": "Levitation",
|
||||||
|
"displayName": "Levitation",
|
||||||
|
"type": "bad"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 26,
|
||||||
|
"name": "Luck",
|
||||||
|
"displayName": "Luck",
|
||||||
|
"type": "good"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 27,
|
||||||
|
"name": "BadLuck",
|
||||||
|
"displayName": "Bad Luck",
|
||||||
|
"type": "bad"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 28,
|
||||||
|
"name": "SlowFalling",
|
||||||
|
"displayName": "Slow Falling",
|
||||||
|
"type": "good"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 29,
|
||||||
|
"name": "ConduitPower",
|
||||||
|
"displayName": "Conduit Power",
|
||||||
|
"type": "good"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 30,
|
||||||
|
"name": "DolphinsGrace",
|
||||||
|
"displayName": "Dolphin's Grace",
|
||||||
|
"type": "good"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 31,
|
||||||
|
"name": "DolphinsGrace",
|
||||||
|
"displayName": "Dolphin's Grace",
|
||||||
|
"type": "good"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 32,
|
||||||
|
"name": "BadOmen",
|
||||||
|
"displayName": "Bad Omen",
|
||||||
|
"type": "good"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 32,
|
||||||
|
"name": "HeroOfTheVillage",
|
||||||
|
"displayName": "Hero Of The Village",
|
||||||
|
"type": "good"
|
||||||
|
}
|
||||||
|
]
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,392 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": 524,
|
||||||
|
"displayName": "Apple",
|
||||||
|
"name": "apple",
|
||||||
|
"stackSize": 64,
|
||||||
|
"foodPoints": 4,
|
||||||
|
"saturation": 2.4,
|
||||||
|
"effectiveQuality": 6.4,
|
||||||
|
"saturationRatio": 0.6
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 547,
|
||||||
|
"displayName": "Mushroom Stew",
|
||||||
|
"name": "mushroom_stew",
|
||||||
|
"stackSize": 1,
|
||||||
|
"foodPoints": 6,
|
||||||
|
"saturation": 7.2,
|
||||||
|
"effectiveQuality": 13.2,
|
||||||
|
"saturationRatio": 1.2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 562,
|
||||||
|
"displayName": "Bread",
|
||||||
|
"name": "bread",
|
||||||
|
"stackSize": 64,
|
||||||
|
"foodPoints": 5,
|
||||||
|
"saturation": 6,
|
||||||
|
"effectiveQuality": 11,
|
||||||
|
"saturationRatio": 1.2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 584,
|
||||||
|
"displayName": "Raw Porkchop",
|
||||||
|
"name": "porkchop",
|
||||||
|
"stackSize": 64,
|
||||||
|
"foodPoints": 3,
|
||||||
|
"saturation": 1.8,
|
||||||
|
"effectiveQuality": 4.8,
|
||||||
|
"saturationRatio": 0.6
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 585,
|
||||||
|
"displayName": "Cooked Porkchop",
|
||||||
|
"name": "cooked_porkchop",
|
||||||
|
"stackSize": 64,
|
||||||
|
"foodPoints": 8,
|
||||||
|
"saturation": 12.8,
|
||||||
|
"effectiveQuality": 20.8,
|
||||||
|
"saturationRatio": 1.6
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 587,
|
||||||
|
"displayName": "Golden Apple",
|
||||||
|
"name": "golden_apple",
|
||||||
|
"stackSize": 64,
|
||||||
|
"foodPoints": 4,
|
||||||
|
"saturation": 9.6,
|
||||||
|
"effectiveQuality": 13.6,
|
||||||
|
"saturationRatio": 2.4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 588,
|
||||||
|
"displayName": "Enchanted Golden Apple",
|
||||||
|
"name": "enchanted_golden_apple",
|
||||||
|
"stackSize": 64,
|
||||||
|
"foodPoints": 4,
|
||||||
|
"saturation": 9.6,
|
||||||
|
"effectiveQuality": 13.6,
|
||||||
|
"saturationRatio": 2.4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 625,
|
||||||
|
"displayName": "Raw Cod",
|
||||||
|
"name": "cod",
|
||||||
|
"stackSize": 64,
|
||||||
|
"foodPoints": 2,
|
||||||
|
"saturation": 0.4,
|
||||||
|
"effectiveQuality": 2.4,
|
||||||
|
"saturationRatio": 0.2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 626,
|
||||||
|
"displayName": "Raw Salmon",
|
||||||
|
"name": "salmon",
|
||||||
|
"stackSize": 64,
|
||||||
|
"foodPoints": 2,
|
||||||
|
"saturation": 0.4,
|
||||||
|
"effectiveQuality": 2.4,
|
||||||
|
"saturationRatio": 0.2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 627,
|
||||||
|
"displayName": "Tropical Fish",
|
||||||
|
"name": "tropical_fish",
|
||||||
|
"stackSize": 64,
|
||||||
|
"foodPoints": 1,
|
||||||
|
"saturation": 0.2,
|
||||||
|
"effectiveQuality": 1.2,
|
||||||
|
"saturationRatio": 0.2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 628,
|
||||||
|
"displayName": "Pufferfish",
|
||||||
|
"name": "pufferfish",
|
||||||
|
"stackSize": 64,
|
||||||
|
"foodPoints": 1,
|
||||||
|
"saturation": 0.2,
|
||||||
|
"effectiveQuality": 1.2,
|
||||||
|
"saturationRatio": 0.2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 629,
|
||||||
|
"displayName": "Cooked Cod",
|
||||||
|
"name": "cooked_cod",
|
||||||
|
"stackSize": 64,
|
||||||
|
"foodPoints": 5,
|
||||||
|
"saturation": 6,
|
||||||
|
"effectiveQuality": 11,
|
||||||
|
"saturationRatio": 1.2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 630,
|
||||||
|
"displayName": "Cooked Salmon",
|
||||||
|
"name": "cooked_salmon",
|
||||||
|
"stackSize": 64,
|
||||||
|
"foodPoints": 6,
|
||||||
|
"saturation": 9.6,
|
||||||
|
"effectiveQuality": 15.6,
|
||||||
|
"saturationRatio": 1.5999999999999999
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 653,
|
||||||
|
"displayName": "Cake",
|
||||||
|
"name": "cake",
|
||||||
|
"stackSize": 1,
|
||||||
|
"foodPoints": 2,
|
||||||
|
"saturation": 0.4,
|
||||||
|
"effectiveQuality": 2.4,
|
||||||
|
"saturationRatio": 0.2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 670,
|
||||||
|
"displayName": "Cookie",
|
||||||
|
"name": "cookie",
|
||||||
|
"stackSize": 64,
|
||||||
|
"foodPoints": 2,
|
||||||
|
"saturation": 0.4,
|
||||||
|
"effectiveQuality": 2.4,
|
||||||
|
"saturationRatio": 0.2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 673,
|
||||||
|
"displayName": "Melon Slice",
|
||||||
|
"name": "melon_slice",
|
||||||
|
"stackSize": 64,
|
||||||
|
"foodPoints": 2,
|
||||||
|
"saturation": 1.2,
|
||||||
|
"effectiveQuality": 3.2,
|
||||||
|
"saturationRatio": 0.6
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 674,
|
||||||
|
"displayName": "Dried Kelp",
|
||||||
|
"name": "dried_kelp",
|
||||||
|
"stackSize": 64,
|
||||||
|
"foodPoints": 1,
|
||||||
|
"saturation": 0.6,
|
||||||
|
"effectiveQuality": 1.6,
|
||||||
|
"saturationRatio": 0.6
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 677,
|
||||||
|
"displayName": "Raw Beef",
|
||||||
|
"name": "beef",
|
||||||
|
"stackSize": 64,
|
||||||
|
"foodPoints": 3,
|
||||||
|
"saturation": 1.8,
|
||||||
|
"effectiveQuality": 4.8,
|
||||||
|
"saturationRatio": 0.6
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 678,
|
||||||
|
"displayName": "Steak",
|
||||||
|
"name": "cooked_beef",
|
||||||
|
"stackSize": 64,
|
||||||
|
"foodPoints": 8,
|
||||||
|
"saturation": 12.8,
|
||||||
|
"effectiveQuality": 20.8,
|
||||||
|
"saturationRatio": 1.6
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 679,
|
||||||
|
"displayName": "Raw Chicken",
|
||||||
|
"name": "chicken",
|
||||||
|
"stackSize": 64,
|
||||||
|
"foodPoints": 2,
|
||||||
|
"saturation": 1.2,
|
||||||
|
"effectiveQuality": 3.2,
|
||||||
|
"saturationRatio": 0.6
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 680,
|
||||||
|
"displayName": "Cooked Chicken",
|
||||||
|
"name": "cooked_chicken",
|
||||||
|
"stackSize": 64,
|
||||||
|
"foodPoints": 6,
|
||||||
|
"saturation": 7.2,
|
||||||
|
"effectiveQuality": 13.2,
|
||||||
|
"saturationRatio": 1.2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 681,
|
||||||
|
"displayName": "Rotten Flesh",
|
||||||
|
"name": "rotten_flesh",
|
||||||
|
"stackSize": 64,
|
||||||
|
"foodPoints": 4,
|
||||||
|
"saturation": 0.8,
|
||||||
|
"effectiveQuality": 4.8,
|
||||||
|
"saturationRatio": 0.2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 689,
|
||||||
|
"displayName": "Spider Eye",
|
||||||
|
"name": "spider_eye",
|
||||||
|
"stackSize": 64,
|
||||||
|
"foodPoints": 2,
|
||||||
|
"saturation": 3.2,
|
||||||
|
"effectiveQuality": 5.2,
|
||||||
|
"saturationRatio": 1.6
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 762,
|
||||||
|
"displayName": "Carrot",
|
||||||
|
"name": "carrot",
|
||||||
|
"stackSize": 64,
|
||||||
|
"foodPoints": 3,
|
||||||
|
"saturation": 3.6,
|
||||||
|
"effectiveQuality": 6.6,
|
||||||
|
"saturationRatio": 1.2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 763,
|
||||||
|
"displayName": "Potato",
|
||||||
|
"name": "potato",
|
||||||
|
"stackSize": 64,
|
||||||
|
"foodPoints": 1,
|
||||||
|
"saturation": 0.6,
|
||||||
|
"effectiveQuality": 1.6,
|
||||||
|
"saturationRatio": 0.6
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 764,
|
||||||
|
"displayName": "Baked Potato",
|
||||||
|
"name": "baked_potato",
|
||||||
|
"stackSize": 64,
|
||||||
|
"foodPoints": 5,
|
||||||
|
"saturation": 6,
|
||||||
|
"effectiveQuality": 11,
|
||||||
|
"saturationRatio": 1.2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 765,
|
||||||
|
"displayName": "Poisonous Potato",
|
||||||
|
"name": "poisonous_potato",
|
||||||
|
"stackSize": 64,
|
||||||
|
"foodPoints": 2,
|
||||||
|
"saturation": 1.2,
|
||||||
|
"effectiveQuality": 3.2,
|
||||||
|
"saturationRatio": 0.6
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 767,
|
||||||
|
"displayName": "Golden Carrot",
|
||||||
|
"name": "golden_carrot",
|
||||||
|
"stackSize": 64,
|
||||||
|
"foodPoints": 6,
|
||||||
|
"saturation": 14.4,
|
||||||
|
"effectiveQuality": 20.4,
|
||||||
|
"saturationRatio": 2.4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 776,
|
||||||
|
"displayName": "Pumpkin Pie",
|
||||||
|
"name": "pumpkin_pie",
|
||||||
|
"stackSize": 64,
|
||||||
|
"foodPoints": 8,
|
||||||
|
"saturation": 4.8,
|
||||||
|
"effectiveQuality": 12.8,
|
||||||
|
"saturationRatio": 0.6
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 786,
|
||||||
|
"displayName": "Raw Rabbit",
|
||||||
|
"name": "rabbit",
|
||||||
|
"stackSize": 64,
|
||||||
|
"foodPoints": 3,
|
||||||
|
"saturation": 1.8,
|
||||||
|
"effectiveQuality": 4.8,
|
||||||
|
"saturationRatio": 0.6
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 787,
|
||||||
|
"displayName": "Cooked Rabbit",
|
||||||
|
"name": "cooked_rabbit",
|
||||||
|
"stackSize": 64,
|
||||||
|
"foodPoints": 5,
|
||||||
|
"saturation": 6,
|
||||||
|
"effectiveQuality": 11,
|
||||||
|
"saturationRatio": 1.2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 788,
|
||||||
|
"displayName": "Rabbit Stew",
|
||||||
|
"name": "rabbit_stew",
|
||||||
|
"stackSize": 1,
|
||||||
|
"foodPoints": 10,
|
||||||
|
"saturation": 12,
|
||||||
|
"effectiveQuality": 22,
|
||||||
|
"saturationRatio": 1.2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 799,
|
||||||
|
"displayName": "Raw Mutton",
|
||||||
|
"name": "mutton",
|
||||||
|
"stackSize": 64,
|
||||||
|
"foodPoints": 2,
|
||||||
|
"saturation": 1.2,
|
||||||
|
"effectiveQuality": 3.2,
|
||||||
|
"saturationRatio": 0.6
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 800,
|
||||||
|
"displayName": "Cooked Mutton",
|
||||||
|
"name": "cooked_mutton",
|
||||||
|
"stackSize": 64,
|
||||||
|
"foodPoints": 6,
|
||||||
|
"saturation": 9.6,
|
||||||
|
"effectiveQuality": 15.6,
|
||||||
|
"saturationRatio": 1.5999999999999999
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 818,
|
||||||
|
"displayName": "Chorus Fruit",
|
||||||
|
"name": "chorus_fruit",
|
||||||
|
"stackSize": 64,
|
||||||
|
"foodPoints": 4,
|
||||||
|
"saturation": 2.4,
|
||||||
|
"effectiveQuality": 6.4,
|
||||||
|
"saturationRatio": 0.6
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 820,
|
||||||
|
"displayName": "Beetroot",
|
||||||
|
"name": "beetroot",
|
||||||
|
"stackSize": 64,
|
||||||
|
"foodPoints": 1,
|
||||||
|
"saturation": 1.2,
|
||||||
|
"effectiveQuality": 2.2,
|
||||||
|
"saturationRatio": 1.2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 822,
|
||||||
|
"displayName": "Beetroot Soup",
|
||||||
|
"name": "beetroot_soup",
|
||||||
|
"stackSize": 1,
|
||||||
|
"foodPoints": 6,
|
||||||
|
"saturation": 7.2,
|
||||||
|
"effectiveQuality": 13.2,
|
||||||
|
"saturationRatio": 1.2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 857,
|
||||||
|
"displayName": "Suspicious Stew",
|
||||||
|
"name": "suspicious_stew",
|
||||||
|
"stackSize": 1,
|
||||||
|
"foodPoints": 6,
|
||||||
|
"saturation": 7.2,
|
||||||
|
"effectiveQuality": 13.2,
|
||||||
|
"saturationRatio": 1.2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 875,
|
||||||
|
"displayName": "Sweet Berries",
|
||||||
|
"name": "sweet_berries",
|
||||||
|
"stackSize": 64,
|
||||||
|
"foodPoints": 2,
|
||||||
|
"saturation": 0.4,
|
||||||
|
"effectiveQuality": 2.4,
|
||||||
|
"saturationRatio": 0.2
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": 0,
|
||||||
|
"name": "harp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": "basedrum"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"name": "snare"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"name": "hat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 4,
|
||||||
|
"name": "bass"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 5,
|
||||||
|
"name": "flute"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 6,
|
||||||
|
"name": "bell"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 7,
|
||||||
|
"name": "guitar"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 8,
|
||||||
|
"name": "chime"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 9,
|
||||||
|
"name": "xylophone"
|
||||||
|
}
|
||||||
|
]
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,61 @@
|
|||||||
|
{
|
||||||
|
"rock": {
|
||||||
|
"521": 6,
|
||||||
|
"535": 2,
|
||||||
|
"539": 4,
|
||||||
|
"543": 8,
|
||||||
|
"550": 12
|
||||||
|
},
|
||||||
|
"wood": {
|
||||||
|
"522": 6,
|
||||||
|
"536": 2,
|
||||||
|
"540": 4,
|
||||||
|
"544": 8,
|
||||||
|
"551": 12
|
||||||
|
},
|
||||||
|
"plant": {
|
||||||
|
"522": 6,
|
||||||
|
"532": 1.5,
|
||||||
|
"533": 1.5,
|
||||||
|
"536": 2,
|
||||||
|
"537": 1.5,
|
||||||
|
"540": 4,
|
||||||
|
"541": 1.5,
|
||||||
|
"544": 8,
|
||||||
|
"548": 1.5,
|
||||||
|
"551": 12
|
||||||
|
},
|
||||||
|
"melon": {
|
||||||
|
"532": 1.5,
|
||||||
|
"533": 1.5,
|
||||||
|
"537": 1.5,
|
||||||
|
"541": 1.5,
|
||||||
|
"548": 1.5
|
||||||
|
},
|
||||||
|
"leaves": {
|
||||||
|
"532": 1.5,
|
||||||
|
"533": 1.5,
|
||||||
|
"537": 1.5,
|
||||||
|
"541": 1.5,
|
||||||
|
"548": 1.5,
|
||||||
|
"672": 6
|
||||||
|
},
|
||||||
|
"dirt": {
|
||||||
|
"520": 6,
|
||||||
|
"534": 2,
|
||||||
|
"538": 4,
|
||||||
|
"542": 8,
|
||||||
|
"549": 12
|
||||||
|
},
|
||||||
|
"web": {
|
||||||
|
"532": 15,
|
||||||
|
"533": 15,
|
||||||
|
"537": 15,
|
||||||
|
"541": 15,
|
||||||
|
"548": 15,
|
||||||
|
"672": 15
|
||||||
|
},
|
||||||
|
"wool": {
|
||||||
|
"672": 4.8
|
||||||
|
}
|
||||||
|
}
|
||||||
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,5 @@
|
|||||||
|
{
|
||||||
|
"minecraftVersion": "1.14.4",
|
||||||
|
"version": 498,
|
||||||
|
"majorVersion": "1.14"
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user