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 |
@@ -1,6 +0,0 @@
|
|||||||
**/*_test.go export-ignore
|
|
||||||
**/testdata export-ignore
|
|
||||||
*.zip filter=lfs diff=lfs merge=lfs -text
|
|
||||||
*.tmcpr filter=lfs diff=lfs merge=lfs -text
|
|
||||||
*.mcpr filter=lfs diff=lfs merge=lfs -text
|
|
||||||
*.mca filter=lfs diff=lfs merge=lfs -text
|
|
||||||
@@ -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 ./...
|
||||||
+2
-2
@@ -1,5 +1,5 @@
|
|||||||
.devres/
|
.devres/
|
||||||
.idea/
|
.idea/
|
||||||
.failed_packets/
|
.failed_packets/
|
||||||
tools/.*/
|
.vscode/proto_generator
|
||||||
.vscode/
|
proto_generator
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
package api
|
|
||||||
|
|
||||||
import "github.com/admin-else/strom/crypto"
|
|
||||||
|
|
||||||
func NewOfflineAccount(name string) *Account {
|
|
||||||
return &Account{
|
|
||||||
Name: name,
|
|
||||||
Uuid: crypto.FromOfflinePlayer(name),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
package client
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net"
|
|
||||||
|
|
||||||
"github.com/admin-else/strom/proto"
|
|
||||||
"github.com/admin-else/strom/proto_base"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Connect connects to the given address note that it does not resolve the SRV records it's just a raw net.Dial
|
|
||||||
// You must close the connection when you're done with it
|
|
||||||
func Connect(addr string) (ret *proto.Conn, err error) {
|
|
||||||
s, err := Status(addr)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ret, err = ConnectVersionLess(addr)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = ret.SetProtocolVersion(s.Version.Protocol)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func ConnectVersionLess(addr string) (ret *proto.Conn, err error) {
|
|
||||||
return ConnectVersionLessCtx(context.Background(), addr)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ConnectVersionLess connects to the given address without setting the version you should set it
|
|
||||||
// when using this function.
|
|
||||||
// You must close the connection when you're done with it
|
|
||||||
func ConnectVersionLessCtx(ctx context.Context, addr string) (ret *proto.Conn, err error) {
|
|
||||||
ret = proto.NewConnCtx(ctx)
|
|
||||||
ret.Actor = proto_base.Client
|
|
||||||
ret.Conn, err = (&net.Dialer{}).DialContext(ctx, "tcp", addr)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ret.R = ret.Conn
|
|
||||||
ret.W = ret.Conn
|
|
||||||
return
|
|
||||||
}
|
|
||||||
-126
@@ -1,126 +0,0 @@
|
|||||||
package client
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"net"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type DNSCheckFunc func(addr string) error
|
|
||||||
|
|
||||||
// DoDns resolves a DNS name to an IP address
|
|
||||||
// It should resolve a minecraft compatible address into something net.Dial
|
|
||||||
func DoDns(in string) (out string, host string, portN uint16, err error) {
|
|
||||||
return DoDNSChecked(in, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DoDNSChecked resolves a DNS name to an IP address
|
|
||||||
// It should resolve a minecraft compatible address into something net.Dial
|
|
||||||
// It also optionally accepts a function to "check" the address
|
|
||||||
func DoDNSChecked(in string, f DNSCheckFunc) (connectTo string, host string, portN uint16, err error) {
|
|
||||||
// https://mcsrc.dev/1/26.1-pre-1/net/minecraft/client/multiplayer/resolver/ServerAddress#L34
|
|
||||||
host, port, err := net.SplitHostPort(in)
|
|
||||||
if err != nil {
|
|
||||||
var addrErr *net.AddrError
|
|
||||||
if errors.As(err, &addrErr) && addrErr.Err == "missing port in address" {
|
|
||||||
err = nil
|
|
||||||
host = in
|
|
||||||
port = "25565"
|
|
||||||
} else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
porti, err := strconv.Atoi(port)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if f == nil {
|
|
||||||
f = func(addr string) error { return nil }
|
|
||||||
}
|
|
||||||
|
|
||||||
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(context.Background(), time.Second) // otherwise this takes 10+ seconds
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
r := &net.Resolver{}
|
|
||||||
|
|
||||||
_, srvs, _ := r.LookupSRV(ctx, "minecraft", "tcp", host)
|
|
||||||
if len(srvs) == 0 {
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
host = strings.TrimSuffix(srvs[0].Target, ".")
|
|
||||||
portN = srvs[0].Port
|
|
||||||
outIP, err = net.ResolveIPAddr("ip", host)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = f(outIP.String())
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
connectTo = outIP.String() + ":" + port
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// DoDnsSimple resolves a DNS name to an IP address
|
|
||||||
// It should resolve a minecraft compatible address into something net.Dial
|
|
||||||
// It does not check the address
|
|
||||||
func DoDnsSimple(in string) (out string, err error) {
|
|
||||||
host, port, err := net.SplitHostPort(in)
|
|
||||||
if err != nil {
|
|
||||||
var addrErr *net.AddrError
|
|
||||||
if errors.As(err, &addrErr) && addrErr.Err == "missing port in address" {
|
|
||||||
err = nil
|
|
||||||
host = in
|
|
||||||
port = "25565"
|
|
||||||
} else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
porti, err := strconv.Atoi(port)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
portN := uint16(porti)
|
|
||||||
_, err = net.ResolveIPAddr("ip", host)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
out = host + ":" + strconv.Itoa(int(portN))
|
|
||||||
if portN != 25565 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second) // otherwise this takes 10+ seconds
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
r := &net.Resolver{}
|
|
||||||
|
|
||||||
_, srvs, _ := r.LookupSRV(ctx, "minecraft", "tcp", host)
|
|
||||||
if len(srvs) == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
host = strings.TrimSuffix(srvs[0].Target, ".")
|
|
||||||
portN = srvs[0].Port
|
|
||||||
out = host + ":" + strconv.Itoa(int(portN))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
-179
@@ -1,179 +0,0 @@
|
|||||||
package client
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/rand"
|
|
||||||
"crypto/rsa"
|
|
||||||
"crypto/x509"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
|
|
||||||
"github.com/admin-else/strom/api"
|
|
||||||
"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
|
|
||||||
}
|
|
||||||
|
|
||||||
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(compress *v1_21_8.LoginToClientPacketCompress) (err error) {
|
|
||||||
s.SetCompressionThreshold(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 = NoTokenErr
|
|
||||||
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 = BadPublicKeyTypeErr
|
|
||||||
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 (s *LoginClient) OnStart() (err error) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func LoginRaw(c *proto.Conn, account *api.Account) (err error) {
|
|
||||||
lc := &LoginClient{
|
|
||||||
Conn: c,
|
|
||||||
Account: account,
|
|
||||||
}
|
|
||||||
lc.RegisterCritical(lc.OnDefault)
|
|
||||||
lc.RegisterCritical(lc.OnClose)
|
|
||||||
lc.RegisterCriticalUntilLatest(lc.OnCompress)
|
|
||||||
lc.RegisterCriticalUntilLatest(lc.OnEncrypt)
|
|
||||||
lc.RegisterCriticalUntilLatest(lc.OnDisconnect)
|
|
||||||
lc.RegisterCriticalUntilLatest(lc.OnSuccess)
|
|
||||||
|
|
||||||
p, err := MakeHandshakePacket(lc.Conn, proto_base.Login)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = lc.Send(p)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
lc.SetState(proto_base.Login)
|
|
||||||
err = lc.Send(&v1_21_8.LoginToServerPacketLoginStart{Username: lc.Account.Name, PlayerUUID: lc.Account.Uuid})
|
|
||||||
|
|
||||||
err = lc.StartConn()
|
|
||||||
if err != nil {
|
|
||||||
err = errors.Join(FailedToLoginErr, err)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func LoginNoSrv(connectTo string, account *api.Account) (c *proto.Conn, err error) {
|
|
||||||
c, err = Connect(connectTo)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = LoginRaw(c, account)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func Login(connectTo string, account *api.Account) (c *proto.Conn, err error) {
|
|
||||||
connectTo, err = DoDnsSimple(connectTo)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c, err = Connect(connectTo)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = LoginRaw(c, account)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
@@ -4,9 +4,10 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"flag"
|
"flag"
|
||||||
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/admin-else/strom/api"
|
"git.anygate.cloud/anygatecloud/strom/mc/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
var cmd = flag.NewFlagSet("api", flag.ContinueOnError)
|
var cmd = flag.NewFlagSet("api", flag.ContinueOnError)
|
||||||
@@ -52,7 +53,7 @@ func Run(args []string) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
err = PleaseProvideArgumentErr
|
slog.Info("account", "name", a.Name, "uuid", a.Uuid)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = json.NewEncoder(os.Stdout).Encode(data)
|
err = json.NewEncoder(os.Stdout).Encode(data)
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -7,9 +7,9 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"slices"
|
"slices"
|
||||||
|
|
||||||
"github.com/admin-else/strom/data"
|
mcdata "git.anygate.cloud/anygatecloud/strom/mc/data"
|
||||||
"github.com/admin-else/strom/proto_generated"
|
"git.anygate.cloud/anygatecloud/strom/mc/proto_generated"
|
||||||
"github.com/admin-else/strom/util"
|
"git.anygate.cloud/anygatecloud/strom/mc/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// strom data 1.21.11 blocks id 0 name => air
|
// strom data 1.21.11 blocks id 0 name => air
|
||||||
@@ -39,11 +39,11 @@ func Run(args []string) (err error) {
|
|||||||
case 0: // print available versions
|
case 0: // print available versions
|
||||||
dataValues = proto_generated.SupportedVersions
|
dataValues = proto_generated.SupportedVersions
|
||||||
case 1: // print available data for version
|
case 1: // print available data for version
|
||||||
dataValues = slices.Collect(maps.Keys(data.Paths.Data[args[0]]))
|
dataValues = slices.Collect(maps.Keys(mcdata.Paths.Data[args[0]]))
|
||||||
case 2:
|
case 2:
|
||||||
err = data.LoadVersionedJson(args[0], args[1], &dataValues)
|
err = mcdata.LoadVersionedJson(args[0], args[1], &dataValues)
|
||||||
case 3:
|
case 3:
|
||||||
err = data.LoadVersionedJson(args[0], args[1], &dataValues)
|
err = mcdata.LoadVersionedJson(args[0], args[1], &dataValues)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -68,7 +68,7 @@ func Run(args []string) (err error) {
|
|||||||
}
|
}
|
||||||
dataValues = result
|
dataValues = result
|
||||||
case 4, 5:
|
case 4, 5:
|
||||||
err = data.LoadVersionedJson(args[0], args[1], &dataValues)
|
err = mcdata.LoadVersionedJson(args[0], args[1], &dataValues)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import (
|
|||||||
"log/slog"
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/admin-else/strom/level/anvil"
|
"git.anygate.cloud/anygatecloud/strom/mc/level/anvil"
|
||||||
"github.com/admin-else/strom/nbt"
|
"git.anygate.cloud/anygatecloud/strom/mc/nbt"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|||||||
+40
-19
@@ -1,39 +1,53 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"debug/buildinfo"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"maps"
|
"maps"
|
||||||
"os"
|
"os"
|
||||||
|
"runtime/debug"
|
||||||
"slices"
|
"slices"
|
||||||
|
|
||||||
"github.com/admin-else/strom/cmd/strom/api"
|
"git.anygate.cloud/anygatecloud/strom/cmd/strom/api"
|
||||||
"github.com/admin-else/strom/cmd/strom/data"
|
"git.anygate.cloud/anygatecloud/strom/cmd/strom/data"
|
||||||
"github.com/admin-else/strom/cmd/strom/extract_mca"
|
"git.anygate.cloud/anygatecloud/strom/cmd/strom/extract_mca"
|
||||||
"github.com/admin-else/strom/cmd/strom/offline_uuid"
|
"git.anygate.cloud/anygatecloud/strom/cmd/strom/mca_to_tmcpr"
|
||||||
"github.com/admin-else/strom/cmd/strom/packet_info"
|
"git.anygate.cloud/anygatecloud/strom/cmd/strom/messenger"
|
||||||
"github.com/admin-else/strom/cmd/strom/packet_inspector"
|
"git.anygate.cloud/anygatecloud/strom/cmd/strom/offline_uuid"
|
||||||
"github.com/admin-else/strom/cmd/strom/print_nbt"
|
"git.anygate.cloud/anygatecloud/strom/cmd/strom/packet_info"
|
||||||
"github.com/admin-else/strom/cmd/strom/print_replay"
|
"git.anygate.cloud/anygatecloud/strom/cmd/strom/packet_inspector"
|
||||||
"github.com/admin-else/strom/cmd/strom/status"
|
"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 ExpectedASubcommandErr = errors.New("expected a subcommand")
|
||||||
var UnknownSubcommandErr = errors.New("unknown subcommand try 'strom help' for a list of subcommands")
|
var UnknownSubcommandErr = errors.New("unknown subcommand try 'strom help' for a list of subcommands")
|
||||||
|
|
||||||
func Version(args []string) (err error) {
|
func ToolVersion() string {
|
||||||
selfPath, err := os.Executable()
|
info, ok := debug.ReadBuildInfo()
|
||||||
if err != nil {
|
if !ok {
|
||||||
return
|
return "devel"
|
||||||
}
|
}
|
||||||
info, err := buildinfo.ReadFile(selfPath)
|
return info.Main.Version
|
||||||
if err != nil {
|
}
|
||||||
return
|
|
||||||
|
func ToolSum() string {
|
||||||
|
info, ok := debug.ReadBuildInfo()
|
||||||
|
if !ok {
|
||||||
|
return ""
|
||||||
}
|
}
|
||||||
fmt.Println("strom cli ", info.Main.Version, " (", info.Main.Sum, ")")
|
return info.Main.Sum
|
||||||
return
|
}
|
||||||
|
|
||||||
|
func Version(args []string) error {
|
||||||
|
fmt.Println("strom cli ", ToolVersion(), " (", ToolSum(), ")")
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var subcommands = map[string]func(args []string) error{
|
var subcommands = map[string]func(args []string) error{
|
||||||
@@ -47,6 +61,12 @@ var subcommands = map[string]func(args []string) error{
|
|||||||
"extract-mca": extract_mca.Run,
|
"extract-mca": extract_mca.Run,
|
||||||
"data": data.Run,
|
"data": data.Run,
|
||||||
"print-replay": print_replay.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) {
|
func Help(args []string) (err error) {
|
||||||
@@ -59,6 +79,7 @@ func Help(args []string) (err error) {
|
|||||||
func mainE() (err error) {
|
func mainE() (err error) {
|
||||||
subcommands["help"] = Help
|
subcommands["help"] = Help
|
||||||
args := os.Args
|
args := os.Args
|
||||||
|
//args = []string{"TEST", "messenger", "-addr", "localhost"}
|
||||||
|
|
||||||
if len(args) < 2 {
|
if len(args) < 2 {
|
||||||
err = ExpectedASubcommandErr
|
err = ExpectedASubcommandErr
|
||||||
|
|||||||
@@ -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()
|
||||||
|
}
|
||||||
@@ -4,7 +4,7 @@ import (
|
|||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/admin-else/strom/crypto"
|
"git.anygate.cloud/anygatecloud/strom/mc/crypto"
|
||||||
)
|
)
|
||||||
|
|
||||||
var cmd = flag.NewFlagSet("offline-uuid", flag.ContinueOnError)
|
var cmd = flag.NewFlagSet("offline-uuid", flag.ContinueOnError)
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/admin-else/strom/data"
|
"git.anygate.cloud/anygatecloud/strom/mc/data"
|
||||||
"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"
|
"git.anygate.cloud/anygatecloud/strom/mc/proto_generated"
|
||||||
)
|
)
|
||||||
|
|
||||||
var cmd = flag.NewFlagSet("packet-info", flag.ContinueOnError)
|
var cmd = flag.NewFlagSet("packet-info", flag.ContinueOnError)
|
||||||
|
|||||||
@@ -1,18 +1,21 @@
|
|||||||
package packet_inspector
|
package packet_inspector
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"flag"
|
"flag"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/admin-else/strom/api"
|
"git.anygate.cloud/anygatecloud/strom/mc/api"
|
||||||
"github.com/admin-else/strom/client"
|
"git.anygate.cloud/anygatecloud/strom/mc/client"
|
||||||
"github.com/admin-else/strom/event"
|
"git.anygate.cloud/anygatecloud/strom/mc/event"
|
||||||
"github.com/admin-else/strom/nbt"
|
"git.anygate.cloud/anygatecloud/strom/mc/nbt"
|
||||||
"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_11"
|
"git.anygate.cloud/anygatecloud/strom/mc/proto_generated/v1_21_11"
|
||||||
"github.com/admin-else/strom/server"
|
"git.anygate.cloud/anygatecloud/strom/mc/server"
|
||||||
"github.com/admin-else/strom/text"
|
"git.anygate.cloud/anygatecloud/strom/mc/text"
|
||||||
)
|
)
|
||||||
|
|
||||||
var Cmd = flag.NewFlagSet("packet-inspector", flag.ContinueOnError)
|
var Cmd = flag.NewFlagSet("packet-inspector", flag.ContinueOnError)
|
||||||
@@ -22,6 +25,8 @@ var Token = Cmd.String("token", "", "token to use")
|
|||||||
|
|
||||||
var TargetAddr = Cmd.String("addr", "127.0.0.1:25565", "address to connect to")
|
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 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 {
|
type Proxy struct {
|
||||||
Client, Servee *proto.Conn
|
Client, Servee *proto.Conn
|
||||||
@@ -33,6 +38,10 @@ func (p *Proxy) Start() (err error) {
|
|||||||
p.Servee.RegisterCritical(p.OnAnything)
|
p.Servee.RegisterCritical(p.OnAnything)
|
||||||
|
|
||||||
p.Servee.RegisterCriticalUntilLatest(p.OnFinishConfiguration)
|
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)
|
errChan := make(chan error)
|
||||||
go func() {
|
go func() {
|
||||||
@@ -45,6 +54,22 @@ func (p *Proxy) Start() (err error) {
|
|||||||
return
|
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) {
|
func (p *Proxy) OnAnything(e event.Anything) (err error) {
|
||||||
packet, ok := e.Val.(proto_base.EncodeDecodeAble)
|
packet, ok := e.Val.(proto_base.EncodeDecodeAble)
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -54,6 +79,19 @@ func (p *Proxy) OnAnything(e event.Anything) (err error) {
|
|||||||
if !ok {
|
if !ok {
|
||||||
return
|
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 {
|
switch packetInfo.Direction {
|
||||||
case proto_base.ToServer:
|
case proto_base.ToServer:
|
||||||
err = p.Client.Send(packet)
|
err = p.Client.Send(packet)
|
||||||
@@ -74,6 +112,40 @@ func (p *Proxy) OnFinishConfiguration(packet *v1_21_11.ConfigurationToServerPack
|
|||||||
return
|
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{
|
var StatusResponse = server.StatusResponse{
|
||||||
Version: server.StatusResponseVersion{
|
Version: server.StatusResponseVersion{
|
||||||
Name: text.Pretty("STROM"),
|
Name: text.Pretty("STROM"),
|
||||||
@@ -90,38 +162,41 @@ func Run(args []string) (err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
slog.SetLogLoggerLevel(slog.LevelDebug)
|
|
||||||
slog.Info("Starting packet inspector", "listen", *ListenAddr)
|
slog.Info("Starting packet inspector", "listen", *ListenAddr)
|
||||||
err = server.StartServerWithOnConn(*ListenAddr, func(serveeConn *proto.Conn) (err error) {
|
err = server.StartServerWithOnConn(*ListenAddr, func(serveeConn *proto.Conn) (err error) {
|
||||||
serveeConn.DebugPrintPackets = []string{""}
|
|
||||||
p := &Proxy{Servee: serveeConn, Client: nil}
|
p := &Proxy{Servee: serveeConn, Client: nil}
|
||||||
var acc = api.NewOfflineAccount(*OfflineNameFlag)
|
var acc = api.NewOfflineAccount(*OfflineNameFlag)
|
||||||
|
if !*TrueForward {
|
||||||
if *Token != "" {
|
if *Token != "" {
|
||||||
acc, err = api.NewAccountFromYGG(*Token)
|
acc, err = api.NewAccountFromYGG(*Token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_, err = server.ServeLogin(serveeConn, server.WithOtherAccount(acc), server.WithStatus(StatusResponse), server.WithoutOnlineMode())
|
_, err = server.ServeLogin(serveeConn, server.WithOtherAccount(acc), server.WithStatus(StatusResponse), server.WithoutOnlineMode(), server.WithoutEncryption())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
*TargetAddr, err = client.DoDnsSimple(*TargetAddr)
|
*TargetAddr, _, _, err = client.DoDNSChecked(nil, *TargetAddr, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c, err := client.Connect(*TargetAddr)
|
c, err := client.Connect(nil, *TargetAddr, *Version)
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.DebugPrintPackets = []string{""}
|
|
||||||
err = client.LoginRaw(c, acc)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer c.Close()
|
defer c.Close()
|
||||||
|
if !*TrueForward {
|
||||||
|
err = client.LoginRaw(c, acc)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
p.Client = c
|
p.Client = c
|
||||||
|
p.Client.AlwaysDecode = true
|
||||||
|
p.Servee.AlwaysDecode = true
|
||||||
err = p.Start()
|
err = p.Start()
|
||||||
return
|
return
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/admin-else/strom/nbt"
|
"git.anygate.cloud/anygatecloud/strom/mc/nbt"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|||||||
@@ -4,15 +4,15 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
generated "github.com/admin-else/strom/proto_generated/%v"
|
generated "git.anygate.cloud/anygatecloud/strom/mc/proto_generated/%v"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Error: %v
|
// Error: %v
|
||||||
func TestFailedPacket%10X(t *testing.T) {
|
func TestFailedPacket%10X(t *testing.T) {
|
||||||
p := &generated.%v{}
|
p := &generated.%v{}
|
||||||
b := bytes.NewBuffer(%#v)
|
b := bytes.NewReader(%#v)
|
||||||
err := p.Decode(b)
|
err := p.Decode(b)
|
||||||
if err != nil {
|
if err == nil {
|
||||||
t.Fatal(err)
|
t.Fatalf("expected decode error but got nil")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,15 +8,15 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"go/format"
|
||||||
"io"
|
"io"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/admin-else/strom/data"
|
"git.anygate.cloud/anygatecloud/strom/mc/proto"
|
||||||
"github.com/admin-else/strom/proto"
|
"git.anygate.cloud/anygatecloud/strom/mc/proto_base"
|
||||||
"github.com/admin-else/strom/proto_base"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -24,40 +24,47 @@ var (
|
|||||||
FileFlag = cmd.String("file", "", "The tmcpr file to print")
|
FileFlag = cmd.String("file", "", "The tmcpr file to print")
|
||||||
ProtocolVersion = cmd.Int("protocol", -1, "The protocol version")
|
ProtocolVersion = cmd.Int("protocol", -1, "The protocol version")
|
||||||
CreateTestsPath = cmd.String("create-tests", "", "")
|
CreateTestsPath = cmd.String("create-tests", "", "")
|
||||||
StateFlag = cmd.String("state", "login", "Initial state: login, config, play")
|
StateFlag = cmd.String("state", "login", "Initial state: handshake, login, config, status, play")
|
||||||
|
DirectionFlag = cmd.String("direction", "clientbound", "Packet direction: clientbound (ToClient) or serverbound (ToServer)")
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO: maybe use the proto replay api
|
|
||||||
|
|
||||||
//go:embed failed_packet.go.tmpl
|
//go:embed failed_packet.go.tmpl
|
||||||
var TestSrcF string
|
var TestSrcF string
|
||||||
|
|
||||||
func SaveUnCodeAbleAsTest(packet *proto.UnCodablePacket) {
|
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)
|
hUntrimmed := sha256.Sum256(packet.Data)
|
||||||
h := hUntrimmed[:8]
|
h := hUntrimmed[:8]
|
||||||
f, err := os.Create(fmt.Sprintf(*CreateTestsPath+"/%v_%10x_test.go", len(packet.Data), h))
|
if err = os.MkdirAll(*CreateTestsPath, 0755); err != nil {
|
||||||
if err != nil {
|
return
|
||||||
panic(err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ret, err := data.LookUpVersionByProtocolVersion(packet.Info.ProtocolVersion)
|
typeStr := reflect.TypeOf(packet.Info.Type).String()
|
||||||
if err != nil {
|
goPackage := typeStr[1:strings.LastIndex(typeStr, ".")]
|
||||||
panic(err)
|
typeName := reflect.TypeOf(packet.Info.Type).Elem().Name()
|
||||||
}
|
|
||||||
goPackage := "v" + strings.ReplaceAll(ret.MinecraftVersion, ".", "_")
|
|
||||||
|
|
||||||
//goland:noinspection GoUnhandledErrorResult
|
src := fmt.Sprintf(TestSrcF, goPackage, packet.Err, h, typeName, packet.Data)
|
||||||
defer f.Close()
|
formatted, formatErr := format.Source([]byte(src))
|
||||||
_, err = fmt.Fprintf(f, TestSrcF, goPackage, packet.Err, h, reflect.TypeOf(packet.Info.Type).Elem().Name(), packet.Data)
|
if formatErr != nil {
|
||||||
if err != nil {
|
formatted = []byte(src)
|
||||||
panic(err)
|
|
||||||
}
|
}
|
||||||
slog.Info("Packet failed to parse", "error", packet.Err, "saved", f.Name(), "data", fmt.Sprintf("%q", packet.Data))
|
|
||||||
|
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 state = proto_base.Login
|
||||||
|
var direction = proto_base.ToClient
|
||||||
|
|
||||||
func UnpackPacket(r io.Reader) (packet proto_base.EncodeDecodeAble, err error) {
|
func UnpackPacket(r io.Reader) (packet proto_base.EncodeDecodeAble, i proto_base.PacketInfo, err error) {
|
||||||
var timestamp, length uint32
|
var timestamp, length uint32
|
||||||
err = binary.Read(r, binary.BigEndian, ×tamp)
|
err = binary.Read(r, binary.BigEndian, ×tamp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -87,7 +94,8 @@ func UnpackPacket(r io.Reader) (packet proto_base.EncodeDecodeAble, err error) {
|
|||||||
// indexafterid represents how many bytes we've read so far
|
// indexafterid represents how many bytes we've read so far
|
||||||
indexafterid := int(b.Size()) - b.Len()
|
indexafterid := int(b.Size()) - b.Len()
|
||||||
|
|
||||||
i, ok := proto.LookUpTypeByPacketInfo(proto_base.ToClient, state, packetId, int32(*ProtocolVersion))
|
var ok bool
|
||||||
|
i, ok = proto.LookUpTypeByPacketInfo(direction, state, packetId, int32(*ProtocolVersion))
|
||||||
if !ok {
|
if !ok {
|
||||||
packet = &proto.UnCodablePacket{Err: proto.BadPacketIdErr, Data: packetData, Info: i}
|
packet = &proto.UnCodablePacket{Err: proto.BadPacketIdErr, Data: packetData, Info: i}
|
||||||
return
|
return
|
||||||
@@ -98,13 +106,42 @@ func UnpackPacket(r io.Reader) (packet proto_base.EncodeDecodeAble, err error) {
|
|||||||
packet = &proto.UnCodablePacket{Err: err, Data: packetData[indexafterid:], Info: i}
|
packet = &proto.UnCodablePacket{Err: err, Data: packetData[indexafterid:], Info: i}
|
||||||
return
|
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 {
|
switch i.Name {
|
||||||
case "success":
|
case "set_protocol":
|
||||||
state = proto_base.Configuration
|
v := reflect.ValueOf(packet).Elem()
|
||||||
case "finish_configuration":
|
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
|
state = proto_base.Play
|
||||||
}
|
}
|
||||||
return
|
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) {
|
func Run(args []string) (err error) {
|
||||||
@@ -119,15 +156,27 @@ func Run(args []string) (err error) {
|
|||||||
return fmt.Errorf("no file specified")
|
return fmt.Errorf("no file specified")
|
||||||
}
|
}
|
||||||
switch *StateFlag {
|
switch *StateFlag {
|
||||||
|
case "handshake":
|
||||||
|
state = proto_base.Handshaking
|
||||||
case "login":
|
case "login":
|
||||||
state = proto_base.Login
|
state = proto_base.Login
|
||||||
case "config":
|
case "config":
|
||||||
state = proto_base.Configuration
|
state = proto_base.Configuration
|
||||||
case "play":
|
case "play":
|
||||||
state = proto_base.Play
|
state = proto_base.Play
|
||||||
|
case "status":
|
||||||
|
state = proto_base.Status
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("unknown state: %s", *StateFlag)
|
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)
|
f, err := os.Open(*FileFlag)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@@ -137,25 +186,30 @@ func Run(args []string) (err error) {
|
|||||||
var totalPackets, failedPackets int
|
var totalPackets, failedPackets int
|
||||||
for {
|
for {
|
||||||
var packet proto_base.EncodeDecodeAble
|
var packet proto_base.EncodeDecodeAble
|
||||||
packet, err = UnpackPacket(f)
|
var info proto_base.PacketInfo
|
||||||
|
packet, info, err = UnpackPacket(f)
|
||||||
unCodablePacket, ok := packet.(*proto.UnCodablePacket)
|
unCodablePacket, ok := packet.(*proto.UnCodablePacket)
|
||||||
if ok {
|
if ok {
|
||||||
|
slog.Error("failed to decode packet", "error", err, "packet_name", info.Name)
|
||||||
if *CreateTestsPath != "" {
|
if *CreateTestsPath != "" {
|
||||||
SaveUnCodeAbleAsTest(unCodablePacket)
|
err = SaveUnCodeAbleAsTest(unCodablePacket)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("failed to save uncodable packet as test", "error", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
failedPackets++
|
failedPackets++
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
} else {
|
|
||||||
fmt.Printf("Packet: %#v\n", packet)
|
fmt.Printf("Packet: %#v\n", packet)
|
||||||
}
|
if errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF) {
|
||||||
if errors.Is(err, io.EOF) {
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
totalPackets++
|
totalPackets++
|
||||||
}
|
}
|
||||||
|
if totalPackets > 0 {
|
||||||
fmt.Printf("Total packets: %d, failed packets: %d, %%failed: %.2f%%\n", totalPackets, failedPackets, float64(failedPackets)/float64(totalPackets)*100)
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
|
||||||
oid sha256:2d90ee976987678593a6b1d09136bb9b4fd131390796ea6e413376a6ddc63226
|
|
||||||
size 75035693
|
|
||||||
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1 +0,0 @@
|
|||||||
[{"realTimestamp":9845034,"value":{"position":{"x":-31.946051078575692,"y":93.0,"z":29.523387113769626,"yaw":-627.44653,"pitch":37.573395,"roll":0.0}}},{"realTimestamp":9845138,"value":{"position":{"x":-31.946051078575692,"y":93.0,"z":29.523387113769626,"yaw":-627.44653,"pitch":37.573395,"roll":0.0}}},{"realTimestamp":9844902,"value":{"position":{"x":-31.946051078575692,"y":93.0,"z":29.523387113769626,"yaw":-627.44653,"pitch":37.573395,"roll":0.0}}},{"realTimestamp":9908896,"value":{"position":{"x":-1.6053634059630886,"y":91.672484820985,"z":-10.594536888795163,"yaw":-1151.6353,"pitch":16.142092,"roll":0.0}}},{"realTimestamp":9844750,"value":{"position":{"x":-31.946051078575692,"y":93.0,"z":29.523387113769626,"yaw":-627.44653,"pitch":37.573395,"roll":0.0}}},{"realTimestamp":9853735,"value":{"position":{"x":-31.05268144454021,"y":88.0,"z":-25.371850030366133,"yaw":-624.37897,"pitch":16.105839,"roll":0.0}}},{"realTimestamp":9845690,"value":{"position":{"x":-31.946051078575692,"y":93.0,"z":29.523387113769626,"yaw":-627.44653,"pitch":37.573395,"roll":0.0}}},{"realTimestamp":9938194,"value":{"position":{"x":39.88206777447001,"y":103.87657142499006,"z":58.09267383764874,"yaw":-1781.1023,"pitch":37.354122,"roll":0.0}}},{"realTimestamp":9846991,"value":{"position":{"x":-31.946051078575692,"y":93.0,"z":29.523387113769626,"yaw":-628.46875,"pitch":36.04,"roll":0.0}}},{"realTimestamp":9908638,"value":{"position":{"x":-1.5870303355717672,"y":91.672484820985,"z":-11.359450846926011,"yaw":-1142.6902,"pitch":20.997843,"roll":0.0}}},{"realTimestamp":9845490,"value":{"position":{"x":-31.946051078575692,"y":93.0,"z":29.523387113769626,"yaw":-627.44653,"pitch":37.573395,"roll":0.0}}},{"realTimestamp":9845990,"value":{"position":{"x":-31.946051078575692,"y":93.0,"z":29.523387113769626,"yaw":-627.44653,"pitch":37.573395,"roll":0.0}}}]
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
|
||||||
oid sha256:d972b9b5f9d962199fe262eccef32a4b94504283fae97f9963702e7ab1550129
|
|
||||||
size 437123
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{"singleplayer":false,"serverName":"protect-test-server.anygate.cloud","customServerName":"proxied test server","duration":3368,"date":1776531741320,"mcversion":"1.21.11","fileFormat":"MCPR","fileFormatVersion":14,"protocol":774,"generator":"ReplayMod v1.21.11-2.6.26","selfId":-1,"players":[]}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{"requiredMods":[{"modID":"viafabricplus","modName":"ViaFabricPlus","modVersion":"4.4.4"}]}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
|
||||||
oid sha256:cef58ec0e3d1d25e34c2effc5c09241ee4c1b39c085ed9e11b7097dfd5c2d033
|
|
||||||
size 3429878
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
234002168
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{"singleplayer":false,"serverName":"protect-beta.anygate.cloud","customServerName":"protect-beta.anygate.cloud","duration":10198341,"date":1776535673280,"mcversion":"1.21.11","fileFormat":"MCPR","fileFormatVersion":14,"protocol":774,"generator":"ReplayMod v1.21.11-2.6.26","selfId":-1,"players":["fe2d6f64-7ab0-3373-a526-af097abbcb39"]}
|
|
||||||
-1
@@ -1 +0,0 @@
|
|||||||
{"requiredMods":[{"modID":"viafabricplus","modName":"ViaFabricPlus","modVersion":"4.4.4"}]}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
|
||||||
oid sha256:be007aa681f7959708e89e39e05390e228753291823a18274068d310a50aa78b
|
|
||||||
size 526306397
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
270566013
|
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
|
||||||
oid sha256:0da58f5cdd65ef241df8c61f9e633af7317faa77034183d4fb648b039c1e14ed
|
|
||||||
size 842
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{"singleplayer":false,"serverName":"protect-test-server.anygate.cloud","customServerName":"proxied test server","duration":476,"date":1776803431635,"mcversion":"1.21.11","fileFormat":"MCPR","fileFormatVersion":14,"protocol":774,"generator":"ReplayMod v1.21.11-2.6.26","selfId":-1,"players":[]}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{"requiredMods":[{"modID":"viafabricplus","modName":"ViaFabricPlus","modVersion":"4.4.4"}]}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
|
||||||
oid sha256:52fa049f97918289e018fa4c3bf5ff789d4f99f44f590ad875ce1f65e5fa9563
|
|
||||||
size 37
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
1105715802
|
|
||||||
@@ -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)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package main
|
package serve_world
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
@@ -8,14 +8,14 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/admin-else/strom/event"
|
"git.anygate.cloud/anygatecloud/strom/mc/event"
|
||||||
"github.com/admin-else/strom/level"
|
"git.anygate.cloud/anygatecloud/strom/mc/level"
|
||||||
"github.com/admin-else/strom/level/anvil"
|
"git.anygate.cloud/anygatecloud/strom/mc/level/anvil"
|
||||||
"github.com/admin-else/strom/nbt"
|
"git.anygate.cloud/anygatecloud/strom/mc/nbt"
|
||||||
"github.com/admin-else/strom/proto"
|
"git.anygate.cloud/anygatecloud/strom/mc/proto"
|
||||||
"github.com/admin-else/strom/proto_generated/v1_21_8"
|
"git.anygate.cloud/anygatecloud/strom/mc/proto_generated/v1_21_8"
|
||||||
"github.com/admin-else/strom/server"
|
"git.anygate.cloud/anygatecloud/strom/mc/server"
|
||||||
"github.com/admin-else/strom/util"
|
"git.anygate.cloud/anygatecloud/strom/mc/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Server struct {
|
type Server struct {
|
||||||
@@ -28,7 +28,7 @@ type Server struct {
|
|||||||
var FullLight [][]uint8
|
var FullLight [][]uint8
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
light := make([]uint8, 2048) // 4096 values 2 per uint8
|
light := make([]uint8, 2048)
|
||||||
for i := range light {
|
for i := range light {
|
||||||
light[i] = 0xFF
|
light[i] = 0xFF
|
||||||
}
|
}
|
||||||
@@ -52,7 +52,7 @@ func GetChunkFromMca(x, z int32, version string) (b []byte, err error) {
|
|||||||
z += anvil.ChunksWidthRegionFile
|
z += anvil.ChunksWidthRegionFile
|
||||||
}
|
}
|
||||||
|
|
||||||
f, err := os.Open(fmt.Sprintf("./level/anvil/testdata/securechattestworld/region/r.%v.%v.mca", xmca, zmca))
|
f, err := os.Open(fmt.Sprintf("./.devres/anvil_testdata/securechattestworld/region/r.%v.%v.mca", xmca, zmca))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -95,13 +95,11 @@ func Limbo(c *proto.Conn) (err error) {
|
|||||||
s := &Server{
|
s := &Server{
|
||||||
Conn: c,
|
Conn: c,
|
||||||
}
|
}
|
||||||
//s.DebugPrintPackets = []string{""}
|
|
||||||
_, err = server.ServeLogin(s.Conn, server.WithoutOnlineMode())
|
_, err = server.ServeLogin(s.Conn, server.WithoutOnlineMode())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
//goland:noinspection GoResourceLeak captures the conn object which should NOT be closed
|
|
||||||
err = server.ServeConfig(s.Conn)
|
err = server.ServeConfig(s.Conn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@@ -176,10 +174,7 @@ func (s *Server) SendKeepAlive() (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func Run(args []string) error {
|
||||||
slog.SetLogLoggerLevel(slog.LevelDebug)
|
slog.SetLogLoggerLevel(slog.LevelDebug)
|
||||||
err := server.StartServerWithOnConn(":25566", Limbo)
|
return server.StartServerWithOnConn(":25566", Limbo)
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -6,11 +6,11 @@ import (
|
|||||||
"log/slog"
|
"log/slog"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/admin-else/strom/client"
|
"git.anygate.cloud/anygatecloud/strom/mc/client"
|
||||||
"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"
|
||||||
)
|
)
|
||||||
|
|
||||||
var cmd = flag.NewFlagSet("status", flag.ContinueOnError)
|
var cmd = flag.NewFlagSet("status", flag.ContinueOnError)
|
||||||
@@ -46,7 +46,7 @@ func (s *StatusClient) OnPong(p *v1_21_8.StatusToClientPacketPing) (err error) {
|
|||||||
// So ignore the resource leak warning. And maybe attach a warn ignore comment.
|
// So ignore the resource leak warning. And maybe attach a warn ignore comment.
|
||||||
// It does not resolve SRV records.
|
// It does not resolve SRV records.
|
||||||
func StatusRaw(addr string) (s *StatusClient, err error) {
|
func StatusRaw(addr string) (s *StatusClient, err error) {
|
||||||
c, err := client.ConnectVersionLess(addr)
|
c, err := client.ConnectVersionLess(nil, addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -82,7 +82,7 @@ func Run(args []string) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if *srvFlag {
|
if *srvFlag {
|
||||||
*addrFlag, err = client.DoDnsSimple(*addrFlag)
|
*addrFlag, _, _, err = client.DoDNSChecked(nil, *addrFlag, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
module github.com/admin-else/strom
|
module git.anygate.cloud/anygatecloud/strom
|
||||||
|
|
||||||
go 1.26.2
|
go 1.26.2
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,5 @@ github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9L
|
|||||||
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
|
|
||||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
|
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
|||||||
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
-20753
File diff suppressed because it is too large
Load Diff
Vendored
BIN
Binary file not shown.
-26884
File diff suppressed because it is too large
Load Diff
Vendored
-3
@@ -1,3 +0,0 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
|
||||||
oid sha256:14cd0df55488907baf26a6e8da9548d614d044a15259a526bb1f0be44a712764
|
|
||||||
size 10113024
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Vendored
-215
@@ -1,215 +0,0 @@
|
|||||||
{
|
|
||||||
"minecraft:recipes/building_blocks/polished_deepslate": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobbled_deepslate": "2026-01-18 19:41:36 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/building_blocks/deepslate_tile_stairs_from_cobbled_deepslate_stonecutting": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobbled_deepslate": "2026-01-18 19:41:36 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/building_blocks/deepslate_brick_stairs_from_cobbled_deepslate_stonecutting": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobbled_deepslate": "2026-01-18 19:41:36 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/combat/stone_sword": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobblestone": "2026-01-18 19:41:36 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/tools/stone_axe": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobblestone": "2026-01-18 19:41:36 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:story/mine_stone": {
|
|
||||||
"criteria": {
|
|
||||||
"get_stone": "2026-01-18 19:41:36 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/decorations/furnace": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobblestone": "2026-01-18 19:41:36 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/building_blocks/chiseled_deepslate_from_cobbled_deepslate_stonecutting": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobbled_deepslate": "2026-01-18 19:41:36 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/decorations/crafting_table": {
|
|
||||||
"criteria": {
|
|
||||||
"unlock_right_away": "2026-01-18 19:40:40 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/building_blocks/cobbled_deepslate_slab": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobbled_deepslate": "2026-01-18 19:41:36 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/decorations/cobbled_deepslate_wall_from_cobbled_deepslate_stonecutting": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobbled_deepslate": "2026-01-18 19:41:36 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/food/cooked_porkchop_from_campfire_cooking": {
|
|
||||||
"criteria": {
|
|
||||||
"has_porkchop": "2026-01-18 19:40:40 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/decorations/cobbled_deepslate_wall": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobbled_deepslate": "2026-01-18 19:41:36 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/decorations/polished_deepslate_wall_from_cobbled_deepslate_stonecutting": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobbled_deepslate": "2026-01-18 19:41:36 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/building_blocks/cobbled_deepslate_stairs_from_cobbled_deepslate_stonecutting": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobbled_deepslate": "2026-01-18 19:41:36 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/building_blocks/deepslate": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobbled_deepslate": "2026-01-18 19:41:36 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/building_blocks/polished_deepslate_stairs_from_cobbled_deepslate_stonecutting": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobbled_deepslate": "2026-01-18 19:41:36 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/decorations/deepslate_brick_wall_from_cobbled_deepslate_stonecutting": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobbled_deepslate": "2026-01-18 19:41:36 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/building_blocks/polished_deepslate_from_cobbled_deepslate_stonecutting": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobbled_deepslate": "2026-01-18 19:41:36 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/building_blocks/polished_deepslate_slab_from_cobbled_deepslate_stonecutting": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobbled_deepslate": "2026-01-18 19:41:36 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/food/cooked_porkchop": {
|
|
||||||
"criteria": {
|
|
||||||
"has_porkchop": "2026-01-18 19:40:40 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/tools/stone_pickaxe": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobblestone": "2026-01-18 19:41:36 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/building_blocks/deepslate_brick_slab_from_cobbled_deepslate_stonecutting": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobbled_deepslate": "2026-01-18 19:41:36 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/building_blocks/cobbled_deepslate_slab_from_cobbled_deepslate_stonecutting": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobbled_deepslate": "2026-01-18 19:41:36 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/building_blocks/deepslate_tile_slab_from_cobbled_deepslate_stonecutting": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobbled_deepslate": "2026-01-18 19:41:36 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/misc/charcoal": {
|
|
||||||
"criteria": {
|
|
||||||
"has_log": "2026-01-18 19:41:43 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/building_blocks/cobbled_deepslate_stairs": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobbled_deepslate": "2026-01-18 19:41:36 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/decorations/deepslate_tile_wall_from_cobbled_deepslate_stonecutting": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobbled_deepslate": "2026-01-18 19:41:36 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/building_blocks/oak_planks": {
|
|
||||||
"criteria": {
|
|
||||||
"has_logs": "2026-01-18 19:41:43 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/food/cooked_porkchop_from_smoking": {
|
|
||||||
"criteria": {
|
|
||||||
"has_porkchop": "2026-01-18 19:40:40 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/building_blocks/deepslate_bricks_from_cobbled_deepslate_stonecutting": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobbled_deepslate": "2026-01-18 19:41:36 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:adventure/adventuring_time": {
|
|
||||||
"criteria": {
|
|
||||||
"minecraft:forest": "2026-01-18 19:40:40 +0100",
|
|
||||||
"minecraft:deep_dark": "2026-01-18 19:41:56 +0100",
|
|
||||||
"minecraft:stony_shore": "2026-01-18 19:40:46 +0100"
|
|
||||||
},
|
|
||||||
"done": false
|
|
||||||
},
|
|
||||||
"minecraft:recipes/building_blocks/deepslate_tiles_from_cobbled_deepslate_stonecutting": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobbled_deepslate": "2026-01-18 19:41:36 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/tools/stone_hoe": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobblestone": "2026-01-18 19:41:36 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/tools/stone_shovel": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobblestone": "2026-01-18 19:41:36 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"DataVersion": 4556
|
|
||||||
}
|
|
||||||
Vendored
-17
@@ -1,17 +0,0 @@
|
|||||||
{
|
|
||||||
"minecraft:recipes/decorations/crafting_table": {
|
|
||||||
"criteria": {
|
|
||||||
"unlock_right_away": "2026-04-23 22:08:36 +0200"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:adventure/adventuring_time": {
|
|
||||||
"criteria": {
|
|
||||||
"minecraft:taiga": "2026-04-23 22:10:55 +0200",
|
|
||||||
"minecraft:grove": "2026-04-23 22:09:03 +0200",
|
|
||||||
"minecraft:stony_shore": "2026-04-23 22:08:37 +0200"
|
|
||||||
},
|
|
||||||
"done": false
|
|
||||||
},
|
|
||||||
"DataVersion": 4671
|
|
||||||
}
|
|
||||||
Vendored
-17
@@ -1,17 +0,0 @@
|
|||||||
{
|
|
||||||
"minecraft:recipes/decorations/crafting_table": {
|
|
||||||
"criteria": {
|
|
||||||
"unlock_right_away": "2026-05-08 20:07:02 +0200"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:adventure/adventuring_time": {
|
|
||||||
"criteria": {
|
|
||||||
"minecraft:forest": "2026-05-08 20:07:06 +0200",
|
|
||||||
"minecraft:taiga": "2026-05-08 20:07:05 +0200",
|
|
||||||
"minecraft:grove": "2026-05-08 20:07:03 +0200"
|
|
||||||
},
|
|
||||||
"done": false
|
|
||||||
},
|
|
||||||
"DataVersion": 4671
|
|
||||||
}
|
|
||||||
Vendored
-220
@@ -1,220 +0,0 @@
|
|||||||
{
|
|
||||||
"minecraft:recipes/building_blocks/polished_deepslate": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobbled_deepslate": "2026-03-11 19:22:37 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/building_blocks/deepslate_tile_stairs_from_cobbled_deepslate_stonecutting": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobbled_deepslate": "2026-03-11 19:22:37 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/building_blocks/deepslate_brick_stairs_from_cobbled_deepslate_stonecutting": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobbled_deepslate": "2026-03-11 19:22:37 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/combat/stone_sword": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobblestone": "2026-03-11 19:22:37 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/tools/stone_axe": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobblestone": "2026-03-11 19:22:37 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:story/mine_stone": {
|
|
||||||
"criteria": {
|
|
||||||
"get_stone": "2026-03-11 19:22:37 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/decorations/furnace": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobblestone": "2026-03-11 19:22:37 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/building_blocks/chiseled_deepslate_from_cobbled_deepslate_stonecutting": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobbled_deepslate": "2026-03-11 19:22:37 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/decorations/crafting_table": {
|
|
||||||
"criteria": {
|
|
||||||
"unlock_right_away": "2026-03-11 19:22:37 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/building_blocks/cobbled_deepslate_slab": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobbled_deepslate": "2026-03-11 19:22:37 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/decorations/cobbled_deepslate_wall_from_cobbled_deepslate_stonecutting": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobbled_deepslate": "2026-03-11 19:22:37 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/food/cooked_porkchop_from_campfire_cooking": {
|
|
||||||
"criteria": {
|
|
||||||
"has_porkchop": "2026-03-11 19:22:37 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/decorations/cobbled_deepslate_wall": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobbled_deepslate": "2026-03-11 19:22:37 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/decorations/polished_deepslate_wall_from_cobbled_deepslate_stonecutting": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobbled_deepslate": "2026-03-11 19:22:37 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/building_blocks/cobbled_deepslate_stairs_from_cobbled_deepslate_stonecutting": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobbled_deepslate": "2026-03-11 19:22:37 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/building_blocks/deepslate": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobbled_deepslate": "2026-03-11 19:22:37 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/building_blocks/polished_deepslate_stairs_from_cobbled_deepslate_stonecutting": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobbled_deepslate": "2026-03-11 19:22:37 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/decorations/deepslate_brick_wall_from_cobbled_deepslate_stonecutting": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobbled_deepslate": "2026-03-11 19:22:37 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/building_blocks/polished_deepslate_from_cobbled_deepslate_stonecutting": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobbled_deepslate": "2026-03-11 19:22:37 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/building_blocks/polished_deepslate_slab_from_cobbled_deepslate_stonecutting": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobbled_deepslate": "2026-03-11 19:22:37 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/food/cooked_porkchop": {
|
|
||||||
"criteria": {
|
|
||||||
"has_porkchop": "2026-03-11 19:22:37 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/tools/stone_pickaxe": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobblestone": "2026-03-11 19:22:37 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/building_blocks/deepslate_brick_slab_from_cobbled_deepslate_stonecutting": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobbled_deepslate": "2026-03-11 19:22:37 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/building_blocks/cobbled_deepslate_slab_from_cobbled_deepslate_stonecutting": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobbled_deepslate": "2026-03-11 19:22:37 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/building_blocks/deepslate_tile_slab_from_cobbled_deepslate_stonecutting": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobbled_deepslate": "2026-03-11 19:22:37 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/misc/charcoal": {
|
|
||||||
"criteria": {
|
|
||||||
"has_log": "2026-03-11 19:22:37 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/building_blocks/cobbled_deepslate_stairs": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobbled_deepslate": "2026-03-11 19:22:37 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/combat/stone_spear": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobblestone": "2026-03-11 19:22:37 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/decorations/deepslate_tile_wall_from_cobbled_deepslate_stonecutting": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobbled_deepslate": "2026-03-11 19:22:37 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/building_blocks/oak_planks": {
|
|
||||||
"criteria": {
|
|
||||||
"has_logs": "2026-03-11 19:22:37 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/food/cooked_porkchop_from_smoking": {
|
|
||||||
"criteria": {
|
|
||||||
"has_porkchop": "2026-03-11 19:22:37 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/building_blocks/deepslate_bricks_from_cobbled_deepslate_stonecutting": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobbled_deepslate": "2026-03-11 19:22:37 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:adventure/adventuring_time": {
|
|
||||||
"criteria": {
|
|
||||||
"minecraft:stony_shore": "2026-03-11 19:22:59 +0100",
|
|
||||||
"minecraft:plains": "2026-03-11 19:22:38 +0100"
|
|
||||||
},
|
|
||||||
"done": false
|
|
||||||
},
|
|
||||||
"minecraft:recipes/building_blocks/deepslate_tiles_from_cobbled_deepslate_stonecutting": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobbled_deepslate": "2026-03-11 19:22:37 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/tools/stone_hoe": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobblestone": "2026-03-11 19:22:37 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/tools/stone_shovel": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobblestone": "2026-03-11 19:22:37 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"DataVersion": 4671
|
|
||||||
}
|
|
||||||
Vendored
-222
@@ -1,222 +0,0 @@
|
|||||||
{
|
|
||||||
"minecraft:recipes/building_blocks/polished_deepslate": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobbled_deepslate": "2026-02-07 19:19:03 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/building_blocks/deepslate_tile_stairs_from_cobbled_deepslate_stonecutting": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobbled_deepslate": "2026-02-07 19:19:03 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/building_blocks/deepslate_brick_stairs_from_cobbled_deepslate_stonecutting": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobbled_deepslate": "2026-02-07 19:19:03 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/combat/stone_sword": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobblestone": "2026-02-07 19:19:03 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/tools/stone_axe": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobblestone": "2026-02-07 19:19:03 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:story/mine_stone": {
|
|
||||||
"criteria": {
|
|
||||||
"get_stone": "2026-02-07 19:19:03 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/decorations/furnace": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobblestone": "2026-02-07 19:19:03 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/building_blocks/chiseled_deepslate_from_cobbled_deepslate_stonecutting": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobbled_deepslate": "2026-02-07 19:19:03 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/decorations/crafting_table": {
|
|
||||||
"criteria": {
|
|
||||||
"unlock_right_away": "2026-02-07 19:19:03 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/building_blocks/cobbled_deepslate_slab": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobbled_deepslate": "2026-02-07 19:19:03 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/decorations/cobbled_deepslate_wall_from_cobbled_deepslate_stonecutting": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobbled_deepslate": "2026-02-07 19:19:03 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/food/cooked_porkchop_from_campfire_cooking": {
|
|
||||||
"criteria": {
|
|
||||||
"has_porkchop": "2026-02-07 19:19:03 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/decorations/cobbled_deepslate_wall": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobbled_deepslate": "2026-02-07 19:19:03 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/decorations/polished_deepslate_wall_from_cobbled_deepslate_stonecutting": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobbled_deepslate": "2026-02-07 19:19:03 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/building_blocks/cobbled_deepslate_stairs_from_cobbled_deepslate_stonecutting": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobbled_deepslate": "2026-02-07 19:19:03 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/building_blocks/deepslate": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobbled_deepslate": "2026-02-07 19:19:03 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/building_blocks/polished_deepslate_stairs_from_cobbled_deepslate_stonecutting": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobbled_deepslate": "2026-02-07 19:19:03 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/decorations/deepslate_brick_wall_from_cobbled_deepslate_stonecutting": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobbled_deepslate": "2026-02-07 19:19:03 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/building_blocks/polished_deepslate_from_cobbled_deepslate_stonecutting": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobbled_deepslate": "2026-02-07 19:19:03 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/building_blocks/polished_deepslate_slab_from_cobbled_deepslate_stonecutting": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobbled_deepslate": "2026-02-07 19:19:03 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/food/cooked_porkchop": {
|
|
||||||
"criteria": {
|
|
||||||
"has_porkchop": "2026-02-07 19:19:03 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/tools/stone_pickaxe": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobblestone": "2026-02-07 19:19:03 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/building_blocks/deepslate_brick_slab_from_cobbled_deepslate_stonecutting": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobbled_deepslate": "2026-02-07 19:19:03 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/building_blocks/cobbled_deepslate_slab_from_cobbled_deepslate_stonecutting": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobbled_deepslate": "2026-02-07 19:19:03 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/building_blocks/deepslate_tile_slab_from_cobbled_deepslate_stonecutting": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobbled_deepslate": "2026-02-07 19:19:03 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/misc/charcoal": {
|
|
||||||
"criteria": {
|
|
||||||
"has_log": "2026-02-07 19:19:03 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/building_blocks/cobbled_deepslate_stairs": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobbled_deepslate": "2026-02-07 19:19:03 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/combat/stone_spear": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobblestone": "2026-02-07 19:19:03 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/decorations/deepslate_tile_wall_from_cobbled_deepslate_stonecutting": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobbled_deepslate": "2026-02-07 19:19:03 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/building_blocks/oak_planks": {
|
|
||||||
"criteria": {
|
|
||||||
"has_logs": "2026-02-07 19:19:03 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/food/cooked_porkchop_from_smoking": {
|
|
||||||
"criteria": {
|
|
||||||
"has_porkchop": "2026-02-07 19:19:03 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/building_blocks/deepslate_bricks_from_cobbled_deepslate_stonecutting": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobbled_deepslate": "2026-02-07 19:19:03 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:adventure/adventuring_time": {
|
|
||||||
"criteria": {
|
|
||||||
"minecraft:forest": "2026-02-07 19:19:04 +0100",
|
|
||||||
"minecraft:river": "2026-02-07 19:20:26 +0100",
|
|
||||||
"minecraft:stony_shore": "2026-02-07 19:20:11 +0100",
|
|
||||||
"minecraft:plains": "2026-02-07 19:20:12 +0100"
|
|
||||||
},
|
|
||||||
"done": false
|
|
||||||
},
|
|
||||||
"minecraft:recipes/building_blocks/deepslate_tiles_from_cobbled_deepslate_stonecutting": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobbled_deepslate": "2026-02-07 19:19:03 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/tools/stone_hoe": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobblestone": "2026-02-07 19:19:03 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/tools/stone_shovel": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobblestone": "2026-02-07 19:19:03 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"DataVersion": 4671
|
|
||||||
}
|
|
||||||
Vendored
-119
@@ -1,119 +0,0 @@
|
|||||||
{
|
|
||||||
"minecraft:recipes/decorations/crafting_table": {
|
|
||||||
"criteria": {
|
|
||||||
"unlock_right_away": "2025-11-30 09:32:28 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/transportation/cherry_boat": {
|
|
||||||
"criteria": {
|
|
||||||
"in_water": "2025-11-30 10:05:13 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:adventure/kill_all_mobs": {
|
|
||||||
"criteria": {
|
|
||||||
"minecraft:zombie": "2025-11-30 10:06:33 +0100"
|
|
||||||
},
|
|
||||||
"done": false
|
|
||||||
},
|
|
||||||
"minecraft:recipes/food/cooked_porkchop_from_campfire_cooking": {
|
|
||||||
"criteria": {
|
|
||||||
"has_porkchop": "2025-11-30 10:06:57 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/transportation/bamboo_raft": {
|
|
||||||
"criteria": {
|
|
||||||
"in_water": "2025-11-30 10:05:13 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/transportation/spruce_boat": {
|
|
||||||
"criteria": {
|
|
||||||
"in_water": "2025-11-30 10:05:13 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/transportation/birch_boat": {
|
|
||||||
"criteria": {
|
|
||||||
"in_water": "2025-11-30 10:05:13 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/transportation/oak_boat": {
|
|
||||||
"criteria": {
|
|
||||||
"in_water": "2025-11-30 10:05:13 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/transportation/mangrove_boat": {
|
|
||||||
"criteria": {
|
|
||||||
"in_water": "2025-11-30 10:05:13 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/food/cooked_porkchop": {
|
|
||||||
"criteria": {
|
|
||||||
"has_porkchop": "2025-11-30 10:06:57 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/transportation/acacia_boat": {
|
|
||||||
"criteria": {
|
|
||||||
"in_water": "2025-11-30 10:05:13 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:adventure/root": {
|
|
||||||
"criteria": {
|
|
||||||
"killed_something": "2025-11-30 10:06:33 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/food/cooked_porkchop_from_smoking": {
|
|
||||||
"criteria": {
|
|
||||||
"has_porkchop": "2025-11-30 10:06:57 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:adventure/kill_a_mob": {
|
|
||||||
"criteria": {
|
|
||||||
"minecraft:zombie": "2025-11-30 10:06:33 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/transportation/pale_oak_boat": {
|
|
||||||
"criteria": {
|
|
||||||
"in_water": "2025-11-30 10:05:13 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:adventure/adventuring_time": {
|
|
||||||
"criteria": {
|
|
||||||
"minecraft:old_growth_birch_forest": "2025-11-30 10:03:58 +0100",
|
|
||||||
"minecraft:forest": "2025-11-30 10:04:40 +0100",
|
|
||||||
"minecraft:taiga": "2025-11-30 09:32:29 +0100",
|
|
||||||
"minecraft:deep_dark": "2025-11-30 10:05:12 +0100",
|
|
||||||
"minecraft:river": "2025-11-30 10:09:03 +0100",
|
|
||||||
"minecraft:grove": "2025-11-30 09:32:32 +0100",
|
|
||||||
"minecraft:meadow": "2025-11-30 09:32:44 +0100",
|
|
||||||
"minecraft:birch_forest": "2025-11-30 10:05:33 +0100",
|
|
||||||
"minecraft:frozen_peaks": "2025-11-30 09:32:55 +0100"
|
|
||||||
},
|
|
||||||
"done": false
|
|
||||||
},
|
|
||||||
"minecraft:recipes/transportation/jungle_boat": {
|
|
||||||
"criteria": {
|
|
||||||
"in_water": "2025-11-30 10:05:13 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/transportation/dark_oak_boat": {
|
|
||||||
"criteria": {
|
|
||||||
"in_water": "2025-11-30 10:05:13 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"DataVersion": 4556
|
|
||||||
}
|
|
||||||
Vendored
-220
@@ -1,220 +0,0 @@
|
|||||||
{
|
|
||||||
"minecraft:recipes/building_blocks/polished_deepslate": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobbled_deepslate": "2026-03-12 21:17:28 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/building_blocks/deepslate_tile_stairs_from_cobbled_deepslate_stonecutting": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobbled_deepslate": "2026-03-12 21:17:28 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/building_blocks/deepslate_brick_stairs_from_cobbled_deepslate_stonecutting": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobbled_deepslate": "2026-03-12 21:17:28 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/combat/stone_sword": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobblestone": "2026-03-12 21:17:28 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/tools/stone_axe": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobblestone": "2026-03-12 21:17:28 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:story/mine_stone": {
|
|
||||||
"criteria": {
|
|
||||||
"get_stone": "2026-03-12 21:17:28 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/decorations/furnace": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobblestone": "2026-03-12 21:17:28 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/building_blocks/chiseled_deepslate_from_cobbled_deepslate_stonecutting": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobbled_deepslate": "2026-03-12 21:17:28 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/decorations/crafting_table": {
|
|
||||||
"criteria": {
|
|
||||||
"unlock_right_away": "2026-03-12 21:17:28 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/building_blocks/cobbled_deepslate_slab": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobbled_deepslate": "2026-03-12 21:17:28 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/decorations/cobbled_deepslate_wall_from_cobbled_deepslate_stonecutting": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobbled_deepslate": "2026-03-12 21:17:28 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/food/cooked_porkchop_from_campfire_cooking": {
|
|
||||||
"criteria": {
|
|
||||||
"has_porkchop": "2026-03-12 21:17:28 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/decorations/cobbled_deepslate_wall": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobbled_deepslate": "2026-03-12 21:17:28 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/decorations/polished_deepslate_wall_from_cobbled_deepslate_stonecutting": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobbled_deepslate": "2026-03-12 21:17:28 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/building_blocks/cobbled_deepslate_stairs_from_cobbled_deepslate_stonecutting": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobbled_deepslate": "2026-03-12 21:17:28 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/building_blocks/deepslate": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobbled_deepslate": "2026-03-12 21:17:28 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/building_blocks/polished_deepslate_stairs_from_cobbled_deepslate_stonecutting": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobbled_deepslate": "2026-03-12 21:17:28 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/decorations/deepslate_brick_wall_from_cobbled_deepslate_stonecutting": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobbled_deepslate": "2026-03-12 21:17:28 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/building_blocks/polished_deepslate_from_cobbled_deepslate_stonecutting": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobbled_deepslate": "2026-03-12 21:17:28 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/building_blocks/polished_deepslate_slab_from_cobbled_deepslate_stonecutting": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobbled_deepslate": "2026-03-12 21:17:28 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/food/cooked_porkchop": {
|
|
||||||
"criteria": {
|
|
||||||
"has_porkchop": "2026-03-12 21:17:28 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/tools/stone_pickaxe": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobblestone": "2026-03-12 21:17:28 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/building_blocks/deepslate_brick_slab_from_cobbled_deepslate_stonecutting": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobbled_deepslate": "2026-03-12 21:17:28 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/building_blocks/cobbled_deepslate_slab_from_cobbled_deepslate_stonecutting": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobbled_deepslate": "2026-03-12 21:17:28 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/building_blocks/deepslate_tile_slab_from_cobbled_deepslate_stonecutting": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobbled_deepslate": "2026-03-12 21:17:28 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/misc/charcoal": {
|
|
||||||
"criteria": {
|
|
||||||
"has_log": "2026-03-12 21:17:28 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/building_blocks/cobbled_deepslate_stairs": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobbled_deepslate": "2026-03-12 21:17:28 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/combat/stone_spear": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobblestone": "2026-03-12 21:17:28 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/decorations/deepslate_tile_wall_from_cobbled_deepslate_stonecutting": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobbled_deepslate": "2026-03-12 21:17:28 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/building_blocks/oak_planks": {
|
|
||||||
"criteria": {
|
|
||||||
"has_logs": "2026-03-12 21:17:28 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/food/cooked_porkchop_from_smoking": {
|
|
||||||
"criteria": {
|
|
||||||
"has_porkchop": "2026-03-12 21:17:28 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/building_blocks/deepslate_bricks_from_cobbled_deepslate_stonecutting": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobbled_deepslate": "2026-03-12 21:17:28 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:adventure/adventuring_time": {
|
|
||||||
"criteria": {
|
|
||||||
"minecraft:stony_shore": "2026-03-12 21:17:48 +0100",
|
|
||||||
"minecraft:plains": "2026-03-12 21:17:31 +0100"
|
|
||||||
},
|
|
||||||
"done": false
|
|
||||||
},
|
|
||||||
"minecraft:recipes/building_blocks/deepslate_tiles_from_cobbled_deepslate_stonecutting": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobbled_deepslate": "2026-03-12 21:17:28 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/tools/stone_hoe": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobblestone": "2026-03-12 21:17:28 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"minecraft:recipes/tools/stone_shovel": {
|
|
||||||
"criteria": {
|
|
||||||
"has_cobblestone": "2026-03-12 21:17:28 +0100"
|
|
||||||
},
|
|
||||||
"done": true
|
|
||||||
},
|
|
||||||
"DataVersion": 4671
|
|
||||||
}
|
|
||||||
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -1 +0,0 @@
|
|||||||
https://github.com/cabaletta/baritone
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user