Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
84b28d4c12 | ||
|
|
30cfc507ba | ||
|
|
d84618c116 | ||
|
|
5a55520126 | ||
|
|
8ac5e00c8e | ||
|
|
d46c678b89 | ||
|
|
0b714edcf5 | ||
|
|
45151add64 | ||
|
|
b90830881f | ||
|
|
94301a4a50 | ||
|
|
aa5d15a4dc | ||
|
|
d388e2c350 | ||
|
|
126ff5bca0 | ||
|
|
a347aedfc2 | ||
|
|
e324a95727 | ||
|
|
7f878f40a1 | ||
|
|
7baa9990cb | ||
|
|
5c5c391aa6 | ||
|
|
b966925212 | ||
|
|
efd7049859 | ||
|
|
3ef9446f50 | ||
|
|
68612969c9 | ||
|
|
b4712ff5c6 | ||
|
|
eab050e291 | ||
|
|
a8b090227b | ||
|
|
201e0dfa44 | ||
|
|
666f250b3e | ||
|
|
b2784574bd | ||
|
|
c0b1501f23 | ||
|
|
4de76d8023 | ||
|
|
f08684fb67 | ||
|
|
9e4f9409c5 | ||
|
|
4d3fbed957 | ||
|
|
b338888b58 | ||
|
|
4956c1d874 | ||
|
|
d4fe362d9f | ||
|
|
5d8ff870df | ||
|
|
58c9a60236 | ||
|
|
3e5b141ec7 | ||
|
|
efeb5ecda6 | ||
|
|
624e8ad4de | ||
|
|
764cfd8c4d | ||
|
|
d43ff7f83d | ||
|
|
a39999b344 | ||
|
|
8ee48e1a45 | ||
|
|
bf6affeb97 | ||
|
|
ede9a47ca2 | ||
|
|
2fb290572a | ||
|
|
021b8ddc1a | ||
|
|
05e8da2ff4 | ||
|
|
a8936d92a5 | ||
|
|
c56898e787 | ||
|
|
c483bf9553 | ||
|
|
b5ee17d1ae | ||
|
|
57cef25e27 | ||
|
|
148302c94d | ||
|
|
02c402251d | ||
|
|
c800c5d471 | ||
|
|
477f51cce7 | ||
|
|
60103ebf47 | ||
|
|
21eeebf0f7 | ||
|
|
bb6f6548d5 | ||
|
|
d6c07cf428 | ||
|
|
e98921755d | ||
|
|
60383fede1 | ||
|
|
3bfa70d384 | ||
|
|
60147fd4b6 | ||
|
|
fe51287cc6 | ||
|
|
a44c5f8606 | ||
|
|
d5979b2c3d | ||
|
|
4b95ea9ae2 | ||
|
|
a80fd29297 | ||
|
|
589f35d147 | ||
|
|
c7b5f6ba0f | ||
|
|
0600315e69 | ||
|
|
12005df68b | ||
|
|
600842a679 | ||
|
|
c93d907d46 | ||
|
|
be6d2fb461 | ||
|
|
b2df88203d | ||
|
|
2ed4498181 | ||
|
|
0316b0aa09 | ||
|
|
62d12386e1 | ||
|
|
00ea02a529 | ||
|
|
e5231dfdb6 | ||
|
|
3b1b436ecf | ||
|
|
47180bf650 | ||
|
|
325db98b99 | ||
|
|
ceae543352 | ||
|
|
aef9615476 | ||
|
|
5d3860b91e | ||
|
|
94d15258d8 |
@@ -1,2 +1,6 @@
|
|||||||
**/*_test.go export-ignore
|
**/*_test.go export-ignore
|
||||||
**/testdata 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
|
||||||
|
|||||||
@@ -14,15 +14,22 @@ jobs:
|
|||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- name: Checkout with LFS
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
lfs: true
|
||||||
|
|
||||||
|
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v4
|
uses: actions/setup-go@v4
|
||||||
with:
|
with:
|
||||||
go-version: '1.25'
|
go-version: '1.26.2'
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: go build -v ./...
|
run: go build -v ./...
|
||||||
|
|
||||||
|
- name: Vet
|
||||||
|
run: go vet -v ./...
|
||||||
|
|
||||||
- name: Test
|
- name: Test
|
||||||
run: go test -v ./...
|
run: go test -v ./...
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
.devres/
|
.devres/
|
||||||
.idea/
|
.idea/
|
||||||
.failed_packets/
|
.failed_packets/
|
||||||
tools/.*/
|
|
||||||
.vscode/
|
.vscode/
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -4,9 +4,10 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"flag"
|
"flag"
|
||||||
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/admin-else/strom/api"
|
"github.com/admin-else/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 "github.com/admin-else/strom/mc/api"
|
||||||
|
|
||||||
|
func Account(s string) (a *api.Account, err error) {
|
||||||
|
if len(s) > 16 {
|
||||||
|
return api.NewAccountFromYGG(s)
|
||||||
|
}
|
||||||
|
a = api.NewOfflineAccount(s)
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
package data
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"maps"
|
||||||
|
"os"
|
||||||
|
"slices"
|
||||||
|
|
||||||
|
mcdata "github.com/admin-else/strom/mc/data"
|
||||||
|
"github.com/admin-else/strom/mc/proto_generated"
|
||||||
|
"github.com/admin-else/strom/mc/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// strom data 1.21.11 blocks id 0 name => air
|
||||||
|
// {
|
||||||
|
// "id": 0,
|
||||||
|
// "name": "air",
|
||||||
|
// "displayName": "Air",
|
||||||
|
// "hardness": 0.0,
|
||||||
|
// "resistance": 0.0,
|
||||||
|
// "stackSize": 64,
|
||||||
|
// "diggable": false,
|
||||||
|
// "material": "default",
|
||||||
|
// "transparent": true,
|
||||||
|
// "emitLight": 0,
|
||||||
|
// "filterLight": 0,
|
||||||
|
// "defaultState": 0,
|
||||||
|
// "minStateId": 0,
|
||||||
|
// "maxStateId": 0,
|
||||||
|
// "states": [],
|
||||||
|
// "drops": [],
|
||||||
|
// "boundingBox": "empty"
|
||||||
|
// },
|
||||||
|
|
||||||
|
func Run(args []string) (err error) {
|
||||||
|
var dataValues any
|
||||||
|
switch len(args) {
|
||||||
|
case 0: // print available versions
|
||||||
|
dataValues = proto_generated.SupportedVersions
|
||||||
|
case 1: // print available data for version
|
||||||
|
dataValues = slices.Collect(maps.Keys(mcdata.Paths.Data[args[0]]))
|
||||||
|
case 2:
|
||||||
|
err = mcdata.LoadVersionedJson(args[0], args[1], &dataValues)
|
||||||
|
case 3:
|
||||||
|
err = mcdata.LoadVersionedJson(args[0], args[1], &dataValues)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var dataSlices []any
|
||||||
|
dataSlices, err = util.AssertTypeError[[]any](dataValues)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var result = make([]any, len(dataSlices))
|
||||||
|
for _, v := range dataSlices {
|
||||||
|
var dataMap map[string]any
|
||||||
|
dataMap, err = util.AssertTypeError[map[string]any](v)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var vToAppend any
|
||||||
|
vToAppend, err = util.MapGetError(dataMap, args[2])
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
result = append(result, vToAppend)
|
||||||
|
}
|
||||||
|
dataValues = result
|
||||||
|
case 4, 5:
|
||||||
|
err = mcdata.LoadVersionedJson(args[0], args[1], &dataValues)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var dataSlices []any
|
||||||
|
dataSlices, err = util.AssertTypeError[[]any](dataValues)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
found := false
|
||||||
|
for _, v := range dataSlices {
|
||||||
|
var dataMap map[string]any
|
||||||
|
dataMap, err = util.AssertTypeError[map[string]any](v)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if v2, ok := dataMap[args[2]]; ok {
|
||||||
|
if args[3] != fmt.Sprint(v2) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
dataValues = dataMap
|
||||||
|
found = true
|
||||||
|
if len(args) == 5 {
|
||||||
|
dataValues, err = util.MapGetError(dataMap, args[4])
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
err = fmt.Errorf("not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
err = fmt.Errorf("too many arguments")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
e := json.NewEncoder(os.Stdout)
|
||||||
|
e.SetIndent("", " ")
|
||||||
|
err = e.Encode(dataValues)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -6,8 +6,8 @@ import (
|
|||||||
"log/slog"
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/admin-else/strom/level/anvil"
|
"github.com/admin-else/strom/mc/level/anvil"
|
||||||
"github.com/admin-else/strom/nbt"
|
"github.com/admin-else/strom/mc/nbt"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|||||||
+45
-24
@@ -1,46 +1,72 @@
|
|||||||
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"
|
"github.com/admin-else/strom/cmd/strom/api"
|
||||||
|
"github.com/admin-else/strom/cmd/strom/data"
|
||||||
"github.com/admin-else/strom/cmd/strom/extract_mca"
|
"github.com/admin-else/strom/cmd/strom/extract_mca"
|
||||||
|
"github.com/admin-else/strom/cmd/strom/mca_to_tmcpr"
|
||||||
|
"github.com/admin-else/strom/cmd/strom/messenger"
|
||||||
"github.com/admin-else/strom/cmd/strom/offline_uuid"
|
"github.com/admin-else/strom/cmd/strom/offline_uuid"
|
||||||
|
"github.com/admin-else/strom/cmd/strom/packet_info"
|
||||||
"github.com/admin-else/strom/cmd/strom/packet_inspector"
|
"github.com/admin-else/strom/cmd/strom/packet_inspector"
|
||||||
"github.com/admin-else/strom/cmd/strom/print_nbt"
|
"github.com/admin-else/strom/cmd/strom/print_nbt"
|
||||||
|
"github.com/admin-else/strom/cmd/strom/print_replay"
|
||||||
|
"github.com/admin-else/strom/cmd/strom/raw_capture"
|
||||||
|
"github.com/admin-else/strom/cmd/strom/registry_data_hunter"
|
||||||
|
"github.com/admin-else/strom/cmd/strom/serve_tmcpr"
|
||||||
|
"github.com/admin-else/strom/cmd/strom/serve_world"
|
||||||
"github.com/admin-else/strom/cmd/strom/status"
|
"github.com/admin-else/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{
|
||||||
"offline-uuid": offline_uuid.Run,
|
"offline-uuid": offline_uuid.Run,
|
||||||
"print-nbt": print_nbt.Run,
|
"packet-info": packet_info.Run,
|
||||||
"status": status.Run,
|
"print-nbt": print_nbt.Run,
|
||||||
"packet-spy": packet_inspector.Run,
|
"status": status.Run,
|
||||||
"api": api.Run,
|
"packet-spy": packet_inspector.Run,
|
||||||
"version": Version,
|
"api": api.Run,
|
||||||
"extract-mca": extract_mca.Run,
|
"version": Version,
|
||||||
|
"extract-mca": extract_mca.Run,
|
||||||
|
"data": data.Run,
|
||||||
|
"print-replay": print_replay.Run,
|
||||||
|
"raw-capture": raw_capture.Run,
|
||||||
|
"registry-data-hunter": registry_data_hunter.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) {
|
||||||
@@ -51,14 +77,9 @@ func Help(args []string) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func mainE() (err error) {
|
func mainE() (err error) {
|
||||||
subcommands["help"] = Help // this is here because if it isn't tools/cli/main.go:17:5: initialization cycle for subcommands
|
subcommands["help"] = Help
|
||||||
// tools/cli/main.go:17:5: subcommands refers to Help
|
|
||||||
// tools/cli/main.go:22:6: Help refers to subcommands
|
|
||||||
args := os.Args
|
args := os.Args
|
||||||
|
//args = []string{"TEST", "messenger", "-addr", "localhost"}
|
||||||
// Debug stuff
|
|
||||||
slog.SetLogLoggerLevel(slog.LevelDebug)
|
|
||||||
//args = []string{"TEST", "version"}
|
|
||||||
|
|
||||||
if len(args) < 2 {
|
if len(args) < 2 {
|
||||||
err = ExpectedASubcommandErr
|
err = ExpectedASubcommandErr
|
||||||
|
|||||||
@@ -0,0 +1,211 @@
|
|||||||
|
package mca_to_tmcpr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/admin-else/strom/mc/level"
|
||||||
|
"github.com/admin-else/strom/mc/level/anvil"
|
||||||
|
"github.com/admin-else/strom/mc/nbt"
|
||||||
|
"github.com/admin-else/strom/mc/proto/replay"
|
||||||
|
"github.com/admin-else/strom/mc/proto_generated/v1_21_11"
|
||||||
|
"github.com/admin-else/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(fmt.Sprintf("./.devres/anvil_testdata/securechattestworld/region/r.%v.%v.mca", xmca, zmca))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
mca, err := anvil.ReadChunkHeader(f)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var n *nbt.Tag
|
||||||
|
n, err = mca.ChunkAt(f, x, z)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if n == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var chunk anvil.Chunk
|
||||||
|
err = nbt.Format.Decode(n.Value, &chunk)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var packetChunk *level.Chunk
|
||||||
|
packetChunk, err = chunk.ToStorage(version)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
buffer := bytes.NewBuffer(nil)
|
||||||
|
err = packetChunk.WriteChunkData(buffer)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
b = buffer.Bytes()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func 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,134 @@
|
|||||||
|
package messenger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/admin-else/strom/cmd/strom/cmd_util"
|
||||||
|
"github.com/admin-else/strom/mc/bot/chat"
|
||||||
|
"github.com/admin-else/strom/mc/bot/keepalive"
|
||||||
|
"github.com/admin-else/strom/mc/bot/world"
|
||||||
|
"github.com/admin-else/strom/mc/client"
|
||||||
|
"github.com/admin-else/strom/mc/data"
|
||||||
|
"github.com/admin-else/strom/mc/event"
|
||||||
|
"github.com/admin-else/strom/mc/proto"
|
||||||
|
"github.com/admin-else/strom/mc/proto_generated/v1_21_11"
|
||||||
|
)
|
||||||
|
|
||||||
|
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")
|
||||||
|
)
|
||||||
|
|
||||||
|
type Messenger struct {
|
||||||
|
*proto.Conn
|
||||||
|
chat *chat.Module
|
||||||
|
world *world.Module
|
||||||
|
}
|
||||||
|
|
||||||
|
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) OnStdin(e event.Stdin) (err error) {
|
||||||
|
input := strings.TrimSpace(e.Val)
|
||||||
|
if strings.HasPrefix(input, ">say ") {
|
||||||
|
return m.chat.SendMessage(strings.TrimSpace(strings.TrimPrefix(input, ">say")))
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(input, ">getblock ") {
|
||||||
|
return m.handleGetBlock(strings.TrimSpace(strings.TrimPrefix(input, ">getblock")))
|
||||||
|
}
|
||||||
|
return m.chat.SendMessage(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer c.Close()
|
||||||
|
|
||||||
|
chatMod, err := chat.Start(c, acc)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shareable world storage. Other bots can subscribe to the same World.
|
||||||
|
w := world.NewWorld(c.Version, -64, 384)
|
||||||
|
|
||||||
|
m := &Messenger{
|
||||||
|
Conn: c,
|
||||||
|
chat: chatMod,
|
||||||
|
world: world.Start(c, w),
|
||||||
|
}
|
||||||
|
|
||||||
|
event.StartListingStdin(m.Loop)
|
||||||
|
m.Register(m.OnStdin)
|
||||||
|
m.RegisterUntilLatest(m.OnChat, m.OnChatUnsigned, m.OnChatSystem)
|
||||||
|
keepalive.Start(m.Conn)
|
||||||
|
|
||||||
|
return m.StartConn()
|
||||||
|
}
|
||||||
@@ -4,7 +4,7 @@ import (
|
|||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/admin-else/strom/crypto"
|
"github.com/admin-else/strom/mc/crypto"
|
||||||
)
|
)
|
||||||
|
|
||||||
var cmd = flag.NewFlagSet("offline-uuid", flag.ContinueOnError)
|
var cmd = flag.NewFlagSet("offline-uuid", flag.ContinueOnError)
|
||||||
|
|||||||
@@ -0,0 +1,169 @@
|
|||||||
|
package packet_info
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/admin-else/strom/mc/data"
|
||||||
|
"github.com/admin-else/strom/mc/proto"
|
||||||
|
"github.com/admin-else/strom/mc/proto_base"
|
||||||
|
"github.com/admin-else/strom/mc/proto_generated"
|
||||||
|
)
|
||||||
|
|
||||||
|
var cmd = flag.NewFlagSet("packet-info", flag.ContinueOnError)
|
||||||
|
var versionFlag = cmd.String("version", "", "protocol version or minecraft version name")
|
||||||
|
var stateFlag = cmd.String("state", "", "handshaking, status, login, configuration, play")
|
||||||
|
var directionFlag = cmd.String("direction", "", "toserver, toclient")
|
||||||
|
var idFlag = cmd.Int("id", -1, "packet id")
|
||||||
|
var nameFlag = cmd.String("name", "", "packet name")
|
||||||
|
|
||||||
|
func Run(args []string) (err error) {
|
||||||
|
err = cmd.Parse(args)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var version int32
|
||||||
|
if *versionFlag != "" {
|
||||||
|
v, err := data.LookUpVersionByName(*versionFlag)
|
||||||
|
if err != nil {
|
||||||
|
v2, err := parseVersion(*versionFlag)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid version: %s", *versionFlag)
|
||||||
|
}
|
||||||
|
v, err = data.LookUpVersionByProtocolVersion(v2)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
version = v.Version
|
||||||
|
}
|
||||||
|
|
||||||
|
var state proto_base.State
|
||||||
|
if *stateFlag != "" {
|
||||||
|
state, err = parseState(*stateFlag)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var direction proto_base.Direction
|
||||||
|
if *directionFlag != "" {
|
||||||
|
direction, err = parseDirection(*directionFlag)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if *idFlag != -1 && *nameFlag != "" {
|
||||||
|
err = fmt.Errorf("cannot specify both id and name")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if *idFlag != -1 {
|
||||||
|
if *versionFlag == "" || *stateFlag == "" || *directionFlag == "" {
|
||||||
|
err = fmt.Errorf("version, state, and direction are required when looking up by id")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p, ok := proto.LookUpTypeByPacketInfo(direction, state, int32(*idFlag), version)
|
||||||
|
if !ok {
|
||||||
|
err = fmt.Errorf("packet not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
printPacketInfo(p)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if *nameFlag != "" {
|
||||||
|
if *versionFlag == "" {
|
||||||
|
err = fmt.Errorf("version is required when looking up by name")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if state != 0 || direction != 0 {
|
||||||
|
p, ok := proto.LookupPacketInfoByNameProtocolVersionStateAndDirection(*nameFlag, version, state, direction)
|
||||||
|
if !ok {
|
||||||
|
err = fmt.Errorf("packet not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
printPacketInfo(p)
|
||||||
|
} else {
|
||||||
|
var found bool
|
||||||
|
for _, p := range proto_generated.Packets {
|
||||||
|
if p.Name == *nameFlag && p.ProtocolVersion == version {
|
||||||
|
printPacketInfo(p)
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
err = fmt.Errorf("packet not found")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = fmt.Errorf("must specify either -id or -name")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseState(s string) (proto_base.State, error) {
|
||||||
|
switch s {
|
||||||
|
case "handshaking":
|
||||||
|
return proto_base.Handshaking, nil
|
||||||
|
case "status":
|
||||||
|
return proto_base.Status, nil
|
||||||
|
case "login":
|
||||||
|
return proto_base.Login, nil
|
||||||
|
case "configuration":
|
||||||
|
return proto_base.Configuration, nil
|
||||||
|
case "play":
|
||||||
|
return proto_base.Play, nil
|
||||||
|
default:
|
||||||
|
return 0, fmt.Errorf("unknown state: %s", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseDirection(s string) (proto_base.Direction, error) {
|
||||||
|
switch s {
|
||||||
|
case "toserver":
|
||||||
|
return proto_base.ToServer, nil
|
||||||
|
case "toclient":
|
||||||
|
return proto_base.ToClient, nil
|
||||||
|
default:
|
||||||
|
return 0, fmt.Errorf("unknown direction: %s", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseVersion(val string) (int32, error) {
|
||||||
|
v, err := strconv.ParseInt(val, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return int32(v), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func printPacketInfo(p proto_base.PacketInfo) {
|
||||||
|
v, _ := data.LookUpVersionByProtocolVersion(p.ProtocolVersion)
|
||||||
|
fmt.Printf("Name: %s\n", p.Name)
|
||||||
|
fmt.Printf("ID: %d\n", p.PacketId)
|
||||||
|
fmt.Printf("State: %s\n", stateToString(p.State))
|
||||||
|
fmt.Printf("Direction: %s\n", p.Direction)
|
||||||
|
fmt.Printf("Version: %d (%s)\n", p.ProtocolVersion, v.MinecraftVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
func stateToString(s proto_base.State) string {
|
||||||
|
switch s {
|
||||||
|
case proto_base.Handshaking:
|
||||||
|
return "handshaking"
|
||||||
|
case proto_base.Status:
|
||||||
|
return "status"
|
||||||
|
case proto_base.Login:
|
||||||
|
return "login"
|
||||||
|
case proto_base.Configuration:
|
||||||
|
return "configuration"
|
||||||
|
case proto_base.Play:
|
||||||
|
return "play"
|
||||||
|
default:
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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"
|
"github.com/admin-else/strom/mc/api"
|
||||||
"github.com/admin-else/strom/client"
|
"github.com/admin-else/strom/mc/client"
|
||||||
"github.com/admin-else/strom/event"
|
"github.com/admin-else/strom/mc/event"
|
||||||
"github.com/admin-else/strom/nbt"
|
"github.com/admin-else/strom/mc/nbt"
|
||||||
"github.com/admin-else/strom/proto"
|
"github.com/admin-else/strom/mc/proto"
|
||||||
"github.com/admin-else/strom/proto_base"
|
"github.com/admin-else/strom/mc/proto_base"
|
||||||
"github.com/admin-else/strom/proto_generated/v1_21_11"
|
"github.com/admin-else/strom/mc/proto_generated/v1_21_11"
|
||||||
"github.com/admin-else/strom/server"
|
"github.com/admin-else/strom/mc/server"
|
||||||
"github.com/admin-else/strom/text"
|
"github.com/admin-else/strom/mc/text"
|
||||||
)
|
)
|
||||||
|
|
||||||
var Cmd = flag.NewFlagSet("packet-inspector", flag.ContinueOnError)
|
var Cmd = flag.NewFlagSet("packet-inspector", flag.ContinueOnError)
|
||||||
@@ -22,6 +25,7 @@ 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")
|
||||||
|
|
||||||
type Proxy struct {
|
type Proxy struct {
|
||||||
Client, Servee *proto.Conn
|
Client, Servee *proto.Conn
|
||||||
@@ -33,6 +37,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 +53,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 +78,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 +111,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 +161,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 *Token != "" {
|
if !*TrueForward {
|
||||||
acc, err = api.NewAccountFromYGG(*Token)
|
if *Token != "" {
|
||||||
|
acc, err = api.NewAccountFromYGG(*Token)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_, err = server.ServeLogin(serveeConn, server.WithOtherAccount(acc), server.WithStatus(StatusResponse), server.WithoutOnlineMode(), server.WithoutEncryption())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_, err = server.ServeLogin(serveeConn, server.WithOtherAccount(acc), server.WithStatus(StatusResponse), server.WithoutOnlineMode())
|
|
||||||
if err != nil {
|
|
||||||
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, "")
|
||||||
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"
|
"github.com/admin-else/strom/mc/nbt"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|||||||
+4
-4
@@ -4,15 +4,15 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/admin-else/strom/proto_generated/v1_21_8"
|
generated "github.com/admin-else/strom/mc/proto_generated/%v"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Error: %v
|
// Error: %v
|
||||||
func TestFailedPacket%10X(t *testing.T) {
|
func TestFailedPacket%10X(t *testing.T) {
|
||||||
p := v1_21_8.Play%vPacket{}
|
p := &generated.%v{}
|
||||||
b := bytes.NewBuffer(%#v)
|
b := bytes.NewBuffer(%#v)
|
||||||
err := p.Decode(b)
|
err := p.Decode(b)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,198 @@
|
|||||||
|
package print_replay
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/sha256"
|
||||||
|
_ "embed"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"go/format"
|
||||||
|
"io"
|
||||||
|
"log/slog"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/admin-else/strom/mc/data"
|
||||||
|
"github.com/admin-else/strom/mc/proto"
|
||||||
|
"github.com/admin-else/strom/mc/proto_base"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
cmd = flag.NewFlagSet("print-replay", flag.ContinueOnError)
|
||||||
|
FileFlag = cmd.String("file", "", "The tmcpr file to print")
|
||||||
|
ProtocolVersion = cmd.Int("protocol", -1, "The protocol version")
|
||||||
|
CreateTestsPath = cmd.String("create-tests", "", "")
|
||||||
|
StateFlag = cmd.String("state", "login", "Initial state: login, config, 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
|
||||||
|
var TestSrcF string
|
||||||
|
|
||||||
|
func SaveUnCodeAbleAsTest(packet *proto.UnCodablePacket) (err error) {
|
||||||
|
if packet.Info.Type == nil {
|
||||||
|
slog.Warn("skipping test for unknown packet id", "packet_id", packet.Info.PacketId, "state", packet.Info.State, "error", packet.Err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
hUntrimmed := sha256.Sum256(packet.Data)
|
||||||
|
h := hUntrimmed[:8]
|
||||||
|
if err = os.MkdirAll(*CreateTestsPath, 0755); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ret, err := data.LookUpVersionByProtocolVersion(packet.Info.ProtocolVersion)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
goPackage := "v" + strings.ReplaceAll(ret.MinecraftVersion, ".", "_")
|
||||||
|
typeName := reflect.TypeOf(packet.Info.Type).Elem().Name()
|
||||||
|
|
||||||
|
src := fmt.Sprintf(TestSrcF, goPackage, packet.Err, h, typeName, packet.Data)
|
||||||
|
formatted, formatErr := format.Source([]byte(src))
|
||||||
|
if formatErr != nil {
|
||||||
|
formatted = []byte(src)
|
||||||
|
}
|
||||||
|
|
||||||
|
filePath := fmt.Sprintf(*CreateTestsPath+"/%v_%10x_test.go", len(packet.Data), h)
|
||||||
|
if err = os.WriteFile(filePath, formatted, 0644); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
slog.Info("Packet failed to parse", "error", packet.Err, "saved", filePath, "data", fmt.Sprintf("%q", packet.Data))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var state = proto_base.Login
|
||||||
|
var direction = proto_base.ToClient
|
||||||
|
|
||||||
|
func UnpackPacket(r io.Reader) (packet proto_base.EncodeDecodeAble, i proto_base.PacketInfo, err error) {
|
||||||
|
var timestamp, length uint32
|
||||||
|
err = binary.Read(r, binary.BigEndian, ×tamp)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = binary.Read(r, binary.BigEndian, &length)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the packet data into a byte slice
|
||||||
|
packetData := make([]byte, length)
|
||||||
|
_, err = io.ReadFull(r, packetData)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use bytes.Reader instead of bytes.Buffer
|
||||||
|
b := bytes.NewReader(packetData)
|
||||||
|
|
||||||
|
packetId, err := proto_base.DecodeVarInt(b)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate position after reading the packet ID
|
||||||
|
// indexafterid represents how many bytes we've read so far
|
||||||
|
indexafterid := int(b.Size()) - b.Len()
|
||||||
|
|
||||||
|
var ok bool
|
||||||
|
i, ok = proto.LookUpTypeByPacketInfo(direction, state, packetId, int32(*ProtocolVersion))
|
||||||
|
if !ok {
|
||||||
|
packet = &proto.UnCodablePacket{Err: proto.BadPacketIdErr, Data: packetData, Info: i}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch direction {
|
||||||
|
case proto_base.ToClient:
|
||||||
|
switch i.Name {
|
||||||
|
case "success":
|
||||||
|
state = proto_base.Configuration
|
||||||
|
case "finish_configuration":
|
||||||
|
state = proto_base.Play
|
||||||
|
}
|
||||||
|
case proto_base.ToServer:
|
||||||
|
switch i.Name {
|
||||||
|
case "login_acknowledged":
|
||||||
|
state = proto_base.Configuration
|
||||||
|
case "finish_configuration":
|
||||||
|
state = proto_base.Play
|
||||||
|
}
|
||||||
|
}
|
||||||
|
packet = reflect.New(reflect.TypeOf(i.Type).Elem()).Interface().(proto_base.EncodeDecodeAble)
|
||||||
|
err = packet.Decode(b)
|
||||||
|
if err != nil {
|
||||||
|
packet = &proto.UnCodablePacket{Err: err, Data: packetData[indexafterid:], Info: i}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if b.Len() != 0 {
|
||||||
|
packet = &proto.UnCodablePacket{Err: proto.PacketNotFullyDecodedErr, Data: packetData[indexafterid:], Info: i}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func Run(args []string) (err error) {
|
||||||
|
err = cmd.Parse(args)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if *ProtocolVersion == -1 {
|
||||||
|
return fmt.Errorf("no protocol version specified")
|
||||||
|
}
|
||||||
|
if *FileFlag == "" {
|
||||||
|
return fmt.Errorf("no file specified")
|
||||||
|
}
|
||||||
|
switch *StateFlag {
|
||||||
|
case "login":
|
||||||
|
state = proto_base.Login
|
||||||
|
case "config":
|
||||||
|
state = proto_base.Configuration
|
||||||
|
case "play":
|
||||||
|
state = proto_base.Play
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unknown state: %s", *StateFlag)
|
||||||
|
}
|
||||||
|
switch *DirectionFlag {
|
||||||
|
case "clientbound", "to-client":
|
||||||
|
direction = proto_base.ToClient
|
||||||
|
case "serverbound", "to-server":
|
||||||
|
direction = proto_base.ToServer
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unknown direction: %s", *DirectionFlag)
|
||||||
|
}
|
||||||
|
f, err := os.Open(*FileFlag)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
var totalPackets, failedPackets int
|
||||||
|
for {
|
||||||
|
var packet proto_base.EncodeDecodeAble
|
||||||
|
var info proto_base.PacketInfo
|
||||||
|
packet, info, err = UnpackPacket(f)
|
||||||
|
unCodablePacket, ok := packet.(*proto.UnCodablePacket)
|
||||||
|
if ok {
|
||||||
|
slog.Error("failed to decode packet", "error", err, "packet_name", info.Name)
|
||||||
|
if *CreateTestsPath != "" {
|
||||||
|
err = SaveUnCodeAbleAsTest(unCodablePacket)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("failed to save uncodable packet as test", "error", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
failedPackets++
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Packet: %#v\n", packet)
|
||||||
|
if errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
totalPackets++
|
||||||
|
}
|
||||||
|
fmt.Printf("Total packets: %d, failed packets: %d, %%failed: %.2f%%\n", totalPackets, failedPackets, float64(failedPackets)/float64(totalPackets)*100)
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -0,0 +1,253 @@
|
|||||||
|
package raw_capture
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/admin-else/strom/mc/api"
|
||||||
|
"github.com/admin-else/strom/mc/client"
|
||||||
|
"github.com/admin-else/strom/mc/data"
|
||||||
|
"github.com/admin-else/strom/mc/event"
|
||||||
|
"github.com/admin-else/strom/mc/proto"
|
||||||
|
"github.com/admin-else/strom/mc/proto/replay"
|
||||||
|
"github.com/admin-else/strom/mc/proto_base"
|
||||||
|
"github.com/admin-else/strom/mc/proto_generated/v1_21_11"
|
||||||
|
"github.com/admin-else/strom/mc/server"
|
||||||
|
"github.com/admin-else/strom/mc/text"
|
||||||
|
)
|
||||||
|
|
||||||
|
var Cmd = flag.NewFlagSet("raw-capture", flag.ContinueOnError)
|
||||||
|
|
||||||
|
var OfflineNameFlag = Cmd.String("name", "raw_capture", "offline player name")
|
||||||
|
var Token = Cmd.String("token", "", "token to use")
|
||||||
|
|
||||||
|
var TargetAddr = Cmd.String("addr", "127.0.0.1:25565", "address to connect to")
|
||||||
|
var ListenAddr = Cmd.String("listen", "127.0.0.1:25566", "address to listen on")
|
||||||
|
var ClientboundFile = Cmd.String("clientbound-file", "clientbound.tmcpr", "file to write clientbound packets to")
|
||||||
|
var ServerboundFile = Cmd.String("serverbound-file", "serverbound.tmcpr", "file to write serverbound packets to")
|
||||||
|
var Version = Cmd.String("version", "", "client protocol version (e.g. 26.1); if empty, detect from server status")
|
||||||
|
|
||||||
|
var StatusResponse = server.StatusResponse{
|
||||||
|
Version: server.StatusResponseVersion{
|
||||||
|
Name: text.Pretty("STROM raw-capture"),
|
||||||
|
Protocol: 0,
|
||||||
|
},
|
||||||
|
Players: server.StatusResponsePlayers{},
|
||||||
|
Description: text.Pretty("STROM raw-capture proxy"),
|
||||||
|
Favicon: "",
|
||||||
|
EnforcesSecureChat: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
type Proxy struct {
|
||||||
|
Client, Servee *proto.Conn
|
||||||
|
|
||||||
|
clientboundFile *os.File
|
||||||
|
serverboundFile *os.File
|
||||||
|
|
||||||
|
clientboundWriter *replay.Writer
|
||||||
|
serverboundWriter *replay.Writer
|
||||||
|
|
||||||
|
startTime time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Proxy) Start() (err error) {
|
||||||
|
p.startTime = time.Now()
|
||||||
|
|
||||||
|
p.Client.RegisterCritical(p.OnAnything)
|
||||||
|
p.Servee.RegisterCritical(p.OnAnything)
|
||||||
|
|
||||||
|
p.Client.RegisterCriticalUntilLatest(p.OnCompress)
|
||||||
|
p.Servee.RegisterCriticalUntilLatest(p.OnHandshake)
|
||||||
|
p.Servee.RegisterCriticalUntilLatest(p.OnLoginAcknowledged)
|
||||||
|
p.Servee.RegisterCriticalUntilLatest(p.OnFinishConfiguration)
|
||||||
|
|
||||||
|
errChan := make(chan error)
|
||||||
|
go func() {
|
||||||
|
errChan <- p.Client.StartConn()
|
||||||
|
}()
|
||||||
|
go func() {
|
||||||
|
errChan <- p.Servee.StartConn()
|
||||||
|
}()
|
||||||
|
err = <-errChan
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Proxy) timestamp() uint32 {
|
||||||
|
return uint32(time.Since(p.startTime).Milliseconds())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Proxy) OnAnything(e event.Anything) (err error) {
|
||||||
|
packet, ok := e.Val.(proto_base.EncodeDecodeAble)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
packetInfo, ok := proto.LookupPacketInfoByType(packet)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if packetInfo.Name == "encryption_begin" {
|
||||||
|
return fmt.Errorf("encryption requested by remote server; raw capture cannot continue")
|
||||||
|
}
|
||||||
|
|
||||||
|
switch packetInfo.Direction {
|
||||||
|
case proto_base.ToServer:
|
||||||
|
err = p.serverboundWriter.WritePacket(packet, p.timestamp())
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = p.Client.Send(packet)
|
||||||
|
case proto_base.ToClient:
|
||||||
|
err = p.clientboundWriter.WritePacket(packet, p.timestamp())
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = p.Servee.Send(packet)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Proxy) OnCompress(packet *v1_21_11.LoginToClientPacketCompress) (err error) {
|
||||||
|
err = p.clientboundWriter.WritePacket(packet, p.timestamp())
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = p.Servee.Send(packet)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.Servee.SetCompressionThreshold(packet.Threshold)
|
||||||
|
p.Client.SetCompressionThreshold(packet.Threshold)
|
||||||
|
return event.DontForwardErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Proxy) OnHandshake(packet *v1_21_11.HandshakingToServerPacketSetProtocol) (err error) {
|
||||||
|
state := proto_base.State(packet.NextState)
|
||||||
|
p.Servee.SetState(state)
|
||||||
|
return event.DontForwardErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Proxy) OnLoginAcknowledged(packet *v1_21_11.LoginToServerPacketLoginAcknowledged) (err error) {
|
||||||
|
err = p.serverboundWriter.WritePacket(packet, p.timestamp())
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = p.Client.Send(packet)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.Servee.SetState(proto_base.Configuration)
|
||||||
|
p.Client.SetState(proto_base.Configuration)
|
||||||
|
return event.DontForwardErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Proxy) OnFinishConfiguration(packet *v1_21_11.ConfigurationToServerPacketFinishConfiguration) (err error) {
|
||||||
|
err = p.serverboundWriter.WritePacket(packet, p.timestamp())
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = p.Client.Send(packet)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.Servee.SetState(proto_base.Play)
|
||||||
|
p.Client.SetState(proto_base.Play)
|
||||||
|
return event.DontForwardErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func Run(args []string) (err error) {
|
||||||
|
err = Cmd.Parse(args)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
clientboundFile, err := os.Create(*ClientboundFile)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("create clientbound file: %w", err)
|
||||||
|
}
|
||||||
|
defer clientboundFile.Close()
|
||||||
|
|
||||||
|
serverboundFile, err := os.Create(*ServerboundFile)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("create serverbound file: %w", err)
|
||||||
|
}
|
||||||
|
defer serverboundFile.Close()
|
||||||
|
|
||||||
|
resolvedAddr, _, _, err := client.DoDNSChecked(nil, *TargetAddr, nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var targetVersion string
|
||||||
|
var targetProtocol int32
|
||||||
|
if *Version == "" {
|
||||||
|
status, err := client.Status(nil, resolvedAddr)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to detect server version: %w", err)
|
||||||
|
}
|
||||||
|
targetProtocol = status.Version.Protocol
|
||||||
|
versionInfo, err := data.LookUpVersionByProtocolVersion(targetProtocol)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unknown protocol version %d: %w", targetProtocol, err)
|
||||||
|
}
|
||||||
|
targetVersion = versionInfo.MinecraftVersion
|
||||||
|
} else {
|
||||||
|
versionInfo, err := data.LookUpVersionByName(*Version)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unknown version %s: %w", *Version, err)
|
||||||
|
}
|
||||||
|
targetVersion = versionInfo.MinecraftVersion
|
||||||
|
targetProtocol = versionInfo.Version
|
||||||
|
}
|
||||||
|
|
||||||
|
StatusResponse.Version.Name = text.Pretty("STROM raw-capture " + targetVersion)
|
||||||
|
StatusResponse.Version.Protocol = targetProtocol
|
||||||
|
StatusResponse.Description = text.Pretty(fmt.Sprintf("Raw capture proxy forwards to %s (%s)", resolvedAddr, targetVersion))
|
||||||
|
|
||||||
|
slog.Info("Starting raw capture", "listen", *ListenAddr, "target", resolvedAddr, "version", targetVersion, "protocol", targetProtocol, "clientbound", *ClientboundFile, "serverbound", *ServerboundFile)
|
||||||
|
|
||||||
|
err = server.StartServerWithOnConn(*ListenAddr, func(serveeConn *proto.Conn) (err error) {
|
||||||
|
proxy := &Proxy{
|
||||||
|
Servee: serveeConn,
|
||||||
|
clientboundFile: clientboundFile,
|
||||||
|
serverboundFile: serverboundFile,
|
||||||
|
clientboundWriter: replay.NewWriter(clientboundFile),
|
||||||
|
serverboundWriter: replay.NewWriter(serverboundFile),
|
||||||
|
}
|
||||||
|
|
||||||
|
acc := api.NewOfflineAccount(*OfflineNameFlag)
|
||||||
|
if *Token != "" {
|
||||||
|
acc, err = api.NewAccountFromYGG(*Token)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = server.ServeLogin(serveeConn, server.WithOtherAccount(acc), server.WithStatus(StatusResponse), server.WithoutOnlineMode(), server.WithoutEncryption())
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c, err := client.Connect(nil, resolvedAddr, targetVersion)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer c.Close()
|
||||||
|
|
||||||
|
err = client.LoginRaw(c, acc)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
proxy.Client = c
|
||||||
|
proxy.Client.AlwaysDecode = true
|
||||||
|
proxy.Servee.AlwaysDecode = true
|
||||||
|
|
||||||
|
return proxy.Start()
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package main
|
package registry_data_hunter
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@@ -6,16 +6,16 @@ import (
|
|||||||
"log/slog"
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/admin-else/strom/api"
|
"github.com/admin-else/strom/mc/api"
|
||||||
"github.com/admin-else/strom/client"
|
"github.com/admin-else/strom/mc/client"
|
||||||
"github.com/admin-else/strom/data"
|
"github.com/admin-else/strom/mc/data"
|
||||||
"github.com/admin-else/strom/event"
|
"github.com/admin-else/strom/mc/event"
|
||||||
"github.com/admin-else/strom/nbt"
|
"github.com/admin-else/strom/mc/nbt"
|
||||||
"github.com/admin-else/strom/proto"
|
"github.com/admin-else/strom/mc/proto"
|
||||||
"github.com/admin-else/strom/proto_base"
|
"github.com/admin-else/strom/mc/proto_base"
|
||||||
"github.com/admin-else/strom/proto_generated/v1_21_8"
|
"github.com/admin-else/strom/mc/proto_generated/v1_21_8"
|
||||||
"github.com/admin-else/strom/server"
|
"github.com/admin-else/strom/mc/server"
|
||||||
"github.com/admin-else/strom/text"
|
"github.com/admin-else/strom/mc/text"
|
||||||
)
|
)
|
||||||
|
|
||||||
func wontFail[T any](v T, err error) T {
|
func wontFail[T any](v T, err error) T {
|
||||||
@@ -25,10 +25,7 @@ func wontFail[T any](v T, err error) T {
|
|||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
// THIS IS REDUNDANT USE LOGIN PACKET IN MINECRAFT DATA
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// Edit these two
|
|
||||||
Version = "1.21.11"
|
Version = "1.21.11"
|
||||||
SaveAs = "data/registry/" + Version + ".nbt"
|
SaveAs = "data/registry/" + Version + ".nbt"
|
||||||
|
|
||||||
@@ -46,7 +43,7 @@ var Status = wontFail(json.Marshal(server.StatusResponse{
|
|||||||
|
|
||||||
type Proxy struct {
|
type Proxy struct {
|
||||||
Client, Servee *proto.Conn
|
Client, Servee *proto.Conn
|
||||||
Data nbt.Tag
|
Data *nbt.Tag
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Proxy) OnAnything(e event.Anything) (err error) {
|
func (p *Proxy) OnAnything(e event.Anything) (err error) {
|
||||||
@@ -113,7 +110,6 @@ func (p *Proxy) OnTags(data *v1_21_8.ConfigurationToClientPacketTags) (err error
|
|||||||
func handleClient(c *proto.Conn) (err error) {
|
func handleClient(c *proto.Conn) (err error) {
|
||||||
acc := api.NewOfflineAccount("RegistryHunter")
|
acc := api.NewOfflineAccount("RegistryHunter")
|
||||||
|
|
||||||
// ignore resource leak warning we own c conn so we should not close it here
|
|
||||||
_, err = server.ServeLogin(c, server.WithOtherAccount(acc), server.WithCompatibleVersions(CompatibleProtocolVersion), server.WithRawStatus(Status))
|
_, err = server.ServeLogin(c, server.WithOtherAccount(acc), server.WithCompatibleVersions(CompatibleProtocolVersion), server.WithRawStatus(Status))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@@ -150,10 +146,7 @@ func handleClient(c *proto.Conn) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func Run(args []string) error {
|
||||||
slog.SetLogLoggerLevel(slog.LevelDebug)
|
slog.SetLogLoggerLevel(slog.LevelDebug)
|
||||||
err := server.StartServerWithOnConn(":25566", handleClient)
|
return server.StartServerWithOnConn(":25566", handleClient)
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
package serve_tmcpr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"flag"
|
||||||
|
"io"
|
||||||
|
"log/slog"
|
||||||
|
"math/rand/v2"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/admin-else/strom/mc/event"
|
||||||
|
"github.com/admin-else/strom/mc/proto"
|
||||||
|
"github.com/admin-else/strom/mc/proto/replay"
|
||||||
|
"github.com/admin-else/strom/mc/proto_generated/v1_21_8"
|
||||||
|
"github.com/admin-else/strom/mc/server"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Server struct {
|
||||||
|
*proto.Conn
|
||||||
|
*event.Timer
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
cmd = flag.NewFlagSet("serve-tmcpr", flag.ContinueOnError)
|
||||||
|
tmcprFile = cmd.String("file", "/tmp/world.tmcpr", "Path to .tmcpr replay file")
|
||||||
|
bindAddr = cmd.String("bind", ":25566", "Address to listen on")
|
||||||
|
)
|
||||||
|
|
||||||
|
func Limbo(c *proto.Conn, tmcprPath string) (err error) {
|
||||||
|
s := &Server{Conn: c}
|
||||||
|
|
||||||
|
_, err = server.ServeLogin(s.Conn, server.WithoutOnlineMode())
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = server.ServeConfig(s.Conn)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := os.Open(tmcprPath)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
r := replay.NewReader(f)
|
||||||
|
r.Version = c.ProtocolVersion
|
||||||
|
|
||||||
|
start := time.Now()
|
||||||
|
|
||||||
|
for {
|
||||||
|
var packetData []byte
|
||||||
|
var timestamp uint32
|
||||||
|
packetData, timestamp, _, err = r.ReadRaw()
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, io.EOF) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
time.Sleep(time.Duration(timestamp)*time.Millisecond - time.Since(start))
|
||||||
|
err = s.SendRaw(packetData)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s.Timer = &event.Timer{}
|
||||||
|
s.Timer.Every(time.Second*15, s.SendKeepAlive)
|
||||||
|
s.Timer.Start(c.Loop)
|
||||||
|
|
||||||
|
return s.StartConn()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) SendKeepAlive() error {
|
||||||
|
return s.Send(&v1_21_8.PlayToClientPacketKeepAlive{KeepAliveId: rand.Int64()})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Run(args []string) error {
|
||||||
|
slog.SetLogLoggerLevel(slog.LevelDebug)
|
||||||
|
|
||||||
|
err := cmd.Parse(args)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if *tmcprFile == "" {
|
||||||
|
slog.Error("no file specified")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return server.StartServerWithOnConn(*bindAddr, func(c *proto.Conn) error {
|
||||||
|
return Limbo(c, *tmcprFile)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,180 @@
|
|||||||
|
package serve_world
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"math/rand/v2"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/admin-else/strom/mc/event"
|
||||||
|
"github.com/admin-else/strom/mc/level"
|
||||||
|
"github.com/admin-else/strom/mc/level/anvil"
|
||||||
|
"github.com/admin-else/strom/mc/nbt"
|
||||||
|
"github.com/admin-else/strom/mc/proto"
|
||||||
|
"github.com/admin-else/strom/mc/proto_generated/v1_21_8"
|
||||||
|
"github.com/admin-else/strom/mc/server"
|
||||||
|
"github.com/admin-else/strom/mc/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Server struct {
|
||||||
|
*proto.Conn
|
||||||
|
*event.Timer
|
||||||
|
|
||||||
|
reconnectCalled bool
|
||||||
|
}
|
||||||
|
|
||||||
|
var FullLight [][]uint8
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
light := make([]uint8, 2048)
|
||||||
|
for i := range light {
|
||||||
|
light[i] = 0xFF
|
||||||
|
}
|
||||||
|
|
||||||
|
sectionsPerChunk := 384 / 16
|
||||||
|
FullLight = make([][]uint8, sectionsPerChunk)
|
||||||
|
for i := range sectionsPerChunk {
|
||||||
|
FullLight[i] = light
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetChunkFromMca(x, z int32, version string) (b []byte, err error) {
|
||||||
|
xmca := util.FloorDiv(x, anvil.ChunksWidthRegionFile)
|
||||||
|
zmca := util.FloorDiv(z, anvil.ChunksWidthRegionFile)
|
||||||
|
x = x % anvil.ChunksWidthRegionFile
|
||||||
|
if x < 0 {
|
||||||
|
x += anvil.ChunksWidthRegionFile
|
||||||
|
}
|
||||||
|
z = z % anvil.ChunksWidthRegionFile
|
||||||
|
if z < 0 {
|
||||||
|
z += anvil.ChunksWidthRegionFile
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := os.Open(fmt.Sprintf("./.devres/anvil_testdata/securechattestworld/region/r.%v.%v.mca", xmca, zmca))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
mca, err := anvil.ReadChunkHeader(f)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var n *nbt.Tag
|
||||||
|
n, err = mca.ChunkAt(f, x, z)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if n == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var chunk anvil.Chunk
|
||||||
|
err = nbt.Format.Decode(n.Value, &chunk)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var packetChunk *level.Chunk
|
||||||
|
packetChunk, err = chunk.ToStorage(version)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
buffer := bytes.NewBuffer(nil)
|
||||||
|
err = packetChunk.WriteChunkData(buffer)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
b = buffer.Bytes()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func Limbo(c *proto.Conn) (err error) {
|
||||||
|
s := &Server{
|
||||||
|
Conn: c,
|
||||||
|
}
|
||||||
|
_, err = server.ServeLogin(s.Conn, server.WithoutOnlineMode())
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = server.ServeConfig(s.Conn)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
login := &v1_21_8.PlayToClientPacketLogin{EntityId: 73, IsHardcore: false, WorldNames: []string{"minecraft:overworld", "minecraft:the_end", "minecraft:the_nether"}, MaxPlayers: 100, ViewDistance: 10, SimulationDistance: 10, ReducedDebugInfo: false, EnableRespawnScreen: true, DoLimitedCrafting: false, WorldState: v1_21_8.PlayToClientSpawnInfo{Dimension: 0, Name: "minecraft:overworld", HashedSeed: -8365436745629239539, Gamemode: "survival", PreviousGamemode: 0xff, IsDebug: false, IsFlat: false, Death: nil, PortalCooldown: 0, SeaLevel: 63}, EnforcesSecureChat: false}
|
||||||
|
err = s.Send(login)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = s.Send(&v1_21_8.PlayToClientPacketPosition{
|
||||||
|
TeleportId: 0,
|
||||||
|
X: 0.5,
|
||||||
|
Y: 100,
|
||||||
|
Z: 0.5,
|
||||||
|
Dx: 0,
|
||||||
|
Dy: 0,
|
||||||
|
Dz: 0,
|
||||||
|
Yaw: 0,
|
||||||
|
Pitch: 0,
|
||||||
|
Flags: v1_21_8.PlayToClientPositionUpdateRelatives{},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = s.Send(&v1_21_8.PlayToClientPacketUpdateViewPosition{
|
||||||
|
ChunkX: 0,
|
||||||
|
ChunkZ: 0,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = s.Send(&v1_21_8.PlayToClientPacketGameStateChange{
|
||||||
|
Reason: "level_chunks_load_start",
|
||||||
|
})
|
||||||
|
|
||||||
|
radius := int32(10)
|
||||||
|
|
||||||
|
for x := -radius; x < radius; x++ {
|
||||||
|
for z := -radius; z < radius; z++ {
|
||||||
|
var chunkData []byte
|
||||||
|
chunkData, err = GetChunkFromMca(x, z, c.Version)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if chunkData == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
OLDpacket := &v1_21_8.PlayToClientPacketMapChunk{}
|
||||||
|
OLDpacket.X = x
|
||||||
|
OLDpacket.Z = z
|
||||||
|
OLDpacket.ChunkData = v1_21_8.ByteArray{Val: chunkData}
|
||||||
|
OLDpacket.BlockLight = FullLight
|
||||||
|
OLDpacket.BlockLightMask = []int64{(1 << 24) - 1}
|
||||||
|
err = s.Send(OLDpacket)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s.Timer = &event.Timer{}
|
||||||
|
s.Timer.Every(time.Second*15, s.SendKeepAlive)
|
||||||
|
s.Timer.Start(c.Loop)
|
||||||
|
|
||||||
|
return s.StartConn()
|
||||||
|
}
|
||||||
|
func (s *Server) SendKeepAlive() (err error) {
|
||||||
|
err = s.Send(&v1_21_8.PlayToClientPacketKeepAlive{KeepAliveId: rand.Int64()})
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func Run(args []string) error {
|
||||||
|
slog.SetLogLoggerLevel(slog.LevelDebug)
|
||||||
|
return server.StartServerWithOnConn(":25566", Limbo)
|
||||||
|
}
|
||||||
@@ -6,11 +6,11 @@ import (
|
|||||||
"log/slog"
|
"log/slog"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/admin-else/strom/client"
|
"github.com/admin-else/strom/mc/client"
|
||||||
"github.com/admin-else/strom/event"
|
"github.com/admin-else/strom/mc/event"
|
||||||
"github.com/admin-else/strom/proto"
|
"github.com/admin-else/strom/mc/proto"
|
||||||
"github.com/admin-else/strom/proto_base"
|
"github.com/admin-else/strom/mc/proto_base"
|
||||||
"github.com/admin-else/strom/proto_generated/v1_21_8"
|
"github.com/admin-else/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,9 +1,9 @@
|
|||||||
module github.com/admin-else/strom
|
module github.com/admin-else/strom
|
||||||
|
|
||||||
go 1.25
|
go 1.26.2
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/go-viper/mapstructure/v2 v2.4.0
|
github.com/go-viper/mapstructure/v2 v2.4.0 // FIXME: this should be replaced with my version
|
||||||
github.com/google/go-cmp v0.7.0
|
github.com/google/go-cmp v0.7.0
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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=
|
||||||
|
|||||||
@@ -1,80 +0,0 @@
|
|||||||
package anvil
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"github.com/admin-else/strom/data"
|
|
||||||
"github.com/admin-else/strom/level"
|
|
||||||
"github.com/admin-else/strom/util"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Chunk struct {
|
|
||||||
DataVersion int32
|
|
||||||
Heightmaps map[string][]int64
|
|
||||||
InhabitedTime int64
|
|
||||||
XPos, YPos, ZPos int32
|
|
||||||
Sections []Section
|
|
||||||
}
|
|
||||||
|
|
||||||
const McPrefix = "minecraft:"
|
|
||||||
|
|
||||||
func (c Chunk) ToStorage(version string) (toChunk *level.Chunk, err error) {
|
|
||||||
f := level.MakeBlockFormat(version)
|
|
||||||
toChunk = new(level.Chunk)
|
|
||||||
|
|
||||||
toChunk.Sections = make([]level.Section, len(c.Sections))
|
|
||||||
for i, s := range c.Sections {
|
|
||||||
pallete := make([]int32, len(s.BlockStates.Palette))
|
|
||||||
for j, p := range s.BlockStates.Palette {
|
|
||||||
pallete[j], err = data.StateIdFromBlocKAndStateMap(version, strings.TrimPrefix(p.Name, McPrefix), p.Properties)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// this is probably fine
|
|
||||||
uintSlice := unsafe.Slice((*uint64)(unsafe.Pointer(unsafe.SliceData(s.BlockStates.Data))), len(s.BlockStates.Data))
|
|
||||||
|
|
||||||
toChunk.Sections[i].Blocks, err = f.Import(uintSlice, util.BpeByNum(float64(len(s.BlockStates.Palette))), pallete)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
toChunk.Sections[i].BlockCount = int16(level.CountStorage(toChunk.Sections[i].Blocks, 0))
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, s := range c.Sections {
|
|
||||||
pallete := make([]int32, len(s.Biomes.Palette))
|
|
||||||
for j, p := range s.Biomes.Palette {
|
|
||||||
var b *data.Biome
|
|
||||||
b, ok := data.LookupBiomeByName(version, strings.TrimPrefix(p, McPrefix))
|
|
||||||
if !ok {
|
|
||||||
err = data.UnknownBiomeNameErr
|
|
||||||
return
|
|
||||||
}
|
|
||||||
pallete[j] = b.Id
|
|
||||||
}
|
|
||||||
// this is probably fine
|
|
||||||
uintSlice := unsafe.Slice((*uint64)(unsafe.Pointer(unsafe.SliceData(s.Biomes.Data))), len(s.Biomes.Data))
|
|
||||||
|
|
||||||
toChunk.Sections[i].Biomes, err = f.Import(uintSlice, util.BpeByNum(float64(len(pallete))), pallete)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
type Section struct {
|
|
||||||
Y int8
|
|
||||||
BlockStates struct {
|
|
||||||
Data []int64 `nbt:"data,omitempty"`
|
|
||||||
Palette []struct {
|
|
||||||
Name string
|
|
||||||
Properties map[string]string `nbt:"properties,omitempty"`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Biomes struct {
|
|
||||||
Data []int64 `nbt:"data,omitempty"`
|
|
||||||
Palette []string
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
package anvil_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/admin-else/strom/level/anvil"
|
|
||||||
"github.com/admin-else/strom/util"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestLevel(t *testing.T) {
|
|
||||||
f, err := os.Open("./testdata/testworld/level.dat")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
l, err := anvil.ReadLevelData(f)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
t.Log(l)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRegionFile(t *testing.T) {
|
|
||||||
f, err := os.Open("./testdata/testworld/region/r.0.0.mca")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
ch, err := anvil.ReadChunkHeader(f)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
n, err := ch.ChunkAt(f, 0, 0)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println(string(util.MustT(json.MarshalIndent(n, "", " "))))
|
|
||||||
fmt.Println("Chunk loaded successfully")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestConverToPacket(t *testing.T) {
|
|
||||||
f, err := os.Open("./level/anvil/testdata/r.0.0.mca")
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
}
|
|
||||||
Vendored
BIN
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.
-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
|
|
||||||
}
|
|
||||||
-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
|
|
||||||
}
|
|
||||||
-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
|
|
||||||
}
|
|
||||||
-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
|
|
||||||
}
|
|
||||||
-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
|
|
||||||
}
|
|
||||||
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.
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.
BIN
Binary file not shown.
BIN
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.
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.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
|
Before Width: | Height: | Size: 12 KiB |
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.
BIN
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