Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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
|
||||
**/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:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Checkout with LFS
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
lfs: true
|
||||
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: '1.25'
|
||||
go-version: '1.26.2'
|
||||
|
||||
- name: Build
|
||||
run: go build -v ./...
|
||||
|
||||
- name: Vet
|
||||
run: go vet -v ./...
|
||||
|
||||
- name: Test
|
||||
run: go test -v ./...
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
.devres/
|
||||
.idea/
|
||||
.failed_packets/
|
||||
tools/.*/
|
||||
.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"
|
||||
"errors"
|
||||
"flag"
|
||||
"log/slog"
|
||||
"os"
|
||||
|
||||
"github.com/admin-else/strom/api"
|
||||
"github.com/admin-else/strom/mc/api"
|
||||
)
|
||||
|
||||
var cmd = flag.NewFlagSet("api", flag.ContinueOnError)
|
||||
@@ -52,7 +53,7 @@ func Run(args []string) (err error) {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
err = PleaseProvideArgumentErr
|
||||
slog.Info("account", "name", a.Name, "uuid", a.Uuid)
|
||||
return
|
||||
}
|
||||
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"
|
||||
"os"
|
||||
|
||||
"github.com/admin-else/strom/level/anvil"
|
||||
"github.com/admin-else/strom/nbt"
|
||||
"github.com/admin-else/strom/mc/level/anvil"
|
||||
"github.com/admin-else/strom/mc/nbt"
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
+38
-17
@@ -1,46 +1,72 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"debug/buildinfo"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"maps"
|
||||
"os"
|
||||
"runtime/debug"
|
||||
"slices"
|
||||
|
||||
"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/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/packet_info"
|
||||
"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_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"
|
||||
)
|
||||
|
||||
var ExpectedASubcommandErr = errors.New("expected a subcommand")
|
||||
var UnknownSubcommandErr = errors.New("unknown subcommand try 'strom help' for a list of subcommands")
|
||||
|
||||
func Version(args []string) (err error) {
|
||||
selfPath, err := os.Executable()
|
||||
if err != nil {
|
||||
return
|
||||
func ToolVersion() string {
|
||||
info, ok := debug.ReadBuildInfo()
|
||||
if !ok {
|
||||
return "devel"
|
||||
}
|
||||
info, err := buildinfo.ReadFile(selfPath)
|
||||
if err != nil {
|
||||
return
|
||||
return info.Main.Version
|
||||
}
|
||||
fmt.Println("strom cli ", info.Main.Version, " (", info.Main.Sum, ")")
|
||||
return
|
||||
|
||||
func ToolSum() string {
|
||||
info, ok := debug.ReadBuildInfo()
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
return info.Main.Sum
|
||||
}
|
||||
|
||||
func Version(args []string) error {
|
||||
fmt.Println("strom cli ", ToolVersion(), " (", ToolSum(), ")")
|
||||
return nil
|
||||
}
|
||||
|
||||
var subcommands = map[string]func(args []string) error{
|
||||
"offline-uuid": offline_uuid.Run,
|
||||
"packet-info": packet_info.Run,
|
||||
"print-nbt": print_nbt.Run,
|
||||
"status": status.Run,
|
||||
"packet-spy": packet_inspector.Run,
|
||||
"api": api.Run,
|
||||
"version": Version,
|
||||
"extract-mca": extract_mca.Run,
|
||||
"data": data.Run,
|
||||
"print-replay": print_replay.Run,
|
||||
"raw-capture": raw_capture.Run,
|
||||
"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) {
|
||||
@@ -51,14 +77,9 @@ func Help(args []string) (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
|
||||
// tools/cli/main.go:17:5: subcommands refers to Help
|
||||
// tools/cli/main.go:22:6: Help refers to subcommands
|
||||
subcommands["help"] = Help
|
||||
args := os.Args
|
||||
|
||||
// Debug stuff
|
||||
slog.SetLogLoggerLevel(slog.LevelDebug)
|
||||
//args = []string{"TEST", "version"}
|
||||
//args = []string{"TEST", "messenger", "-addr", "localhost"}
|
||||
|
||||
if len(args) < 2 {
|
||||
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"
|
||||
"fmt"
|
||||
|
||||
"github.com/admin-else/strom/crypto"
|
||||
"github.com/admin-else/strom/mc/crypto"
|
||||
)
|
||||
|
||||
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
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"log/slog"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/admin-else/strom/api"
|
||||
"github.com/admin-else/strom/client"
|
||||
"github.com/admin-else/strom/event"
|
||||
"github.com/admin-else/strom/nbt"
|
||||
"github.com/admin-else/strom/proto"
|
||||
"github.com/admin-else/strom/proto_base"
|
||||
"github.com/admin-else/strom/proto_generated/v1_21_11"
|
||||
"github.com/admin-else/strom/server"
|
||||
"github.com/admin-else/strom/text"
|
||||
"github.com/admin-else/strom/mc/api"
|
||||
"github.com/admin-else/strom/mc/client"
|
||||
"github.com/admin-else/strom/mc/event"
|
||||
"github.com/admin-else/strom/mc/nbt"
|
||||
"github.com/admin-else/strom/mc/proto"
|
||||
"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("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 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 {
|
||||
Client, Servee *proto.Conn
|
||||
@@ -33,6 +37,10 @@ func (p *Proxy) Start() (err error) {
|
||||
p.Servee.RegisterCritical(p.OnAnything)
|
||||
|
||||
p.Servee.RegisterCriticalUntilLatest(p.OnFinishConfiguration)
|
||||
p.Servee.RegisterCriticalUntilLatest(p.OnFinishLogin)
|
||||
p.Servee.RegisterCriticalUntilLatest(p.OnHandshake)
|
||||
//p.Servee.RegisterCriticalUntilLatest(p.OnCompress)
|
||||
p.Client.RegisterCriticalUntilLatest(p.OnCompress)
|
||||
|
||||
errChan := make(chan error)
|
||||
go func() {
|
||||
@@ -45,6 +53,22 @@ func (p *Proxy) Start() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
type PacketLogEvent struct {
|
||||
Time time.Time
|
||||
Packet proto_base.EncodeDecodeAble
|
||||
Info PacketInfoNoTypeJson
|
||||
ClientAddr string
|
||||
}
|
||||
|
||||
type PacketInfoNoTypeJson struct {
|
||||
Type proto_base.EncodeDecodeAble `json:"-"`
|
||||
Name string
|
||||
Direction proto_base.Direction
|
||||
State proto_base.State
|
||||
PacketId int32
|
||||
ProtocolVersion int32
|
||||
}
|
||||
|
||||
func (p *Proxy) OnAnything(e event.Anything) (err error) {
|
||||
packet, ok := e.Val.(proto_base.EncodeDecodeAble)
|
||||
if !ok {
|
||||
@@ -54,6 +78,19 @@ func (p *Proxy) OnAnything(e event.Anything) (err error) {
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
jsonBytes, err := json.Marshal(PacketLogEvent{
|
||||
Time: time.Now(),
|
||||
Packet: packet,
|
||||
ClientAddr: p.Client.RemoteAddr().String(),
|
||||
Info: PacketInfoNoTypeJson(packetInfo),
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// :pray:
|
||||
_, _ = os.Stdout.Write(jsonBytes)
|
||||
_, _ = os.Stdout.Write([]byte("\n"))
|
||||
|
||||
switch packetInfo.Direction {
|
||||
case proto_base.ToServer:
|
||||
err = p.Client.Send(packet)
|
||||
@@ -74,6 +111,40 @@ func (p *Proxy) OnFinishConfiguration(packet *v1_21_11.ConfigurationToServerPack
|
||||
return
|
||||
}
|
||||
|
||||
func (p *Proxy) OnFinishLogin(packet *v1_21_11.LoginToServerPacketLoginAcknowledged) (err error) {
|
||||
err = p.Client.Send(packet)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
p.Servee.SetState(proto_base.Configuration)
|
||||
p.Client.SetState(proto_base.Configuration)
|
||||
err = event.DontForwardErr
|
||||
return
|
||||
}
|
||||
|
||||
func (p *Proxy) OnHandshake(packet *v1_21_11.HandshakingToServerPacketSetProtocol) (err error) {
|
||||
err = p.Client.Send(packet)
|
||||
state := proto_base.State(packet.NextState)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
p.Servee.SetState(state)
|
||||
p.Client.SetState(state)
|
||||
err = event.DontForwardErr
|
||||
return
|
||||
}
|
||||
|
||||
func (p *Proxy) OnCompress(packet *v1_21_11.LoginToClientPacketCompress) (err error) {
|
||||
err = p.Servee.Send(packet)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
p.Servee.SetCompressionThreshold(packet.Threshold)
|
||||
p.Client.SetCompressionThreshold(packet.Threshold)
|
||||
err = event.DontForwardErr
|
||||
return
|
||||
}
|
||||
|
||||
var StatusResponse = server.StatusResponse{
|
||||
Version: server.StatusResponseVersion{
|
||||
Name: text.Pretty("STROM"),
|
||||
@@ -90,38 +161,41 @@ func Run(args []string) (err error) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
slog.SetLogLoggerLevel(slog.LevelDebug)
|
||||
slog.Info("Starting packet inspector", "listen", *ListenAddr)
|
||||
err = server.StartServerWithOnConn(*ListenAddr, func(serveeConn *proto.Conn) (err error) {
|
||||
serveeConn.DebugPrintPackets = []string{""}
|
||||
p := &Proxy{Servee: serveeConn, Client: nil}
|
||||
var acc = api.NewOfflineAccount(*OfflineNameFlag)
|
||||
if !*TrueForward {
|
||||
if *Token != "" {
|
||||
acc, err = api.NewAccountFromYGG(*Token)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
_, err = server.ServeLogin(serveeConn, server.WithOtherAccount(acc), server.WithStatus(StatusResponse), server.WithoutOnlineMode())
|
||||
_, err = server.ServeLogin(serveeConn, server.WithOtherAccount(acc), server.WithStatus(StatusResponse), server.WithoutOnlineMode(), server.WithoutEncryption())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
*TargetAddr, err = client.DoDnsSimple(*TargetAddr)
|
||||
*TargetAddr, _, _, err = client.DoDNSChecked(nil, *TargetAddr, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
c, err := client.Connect(*TargetAddr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
c.DebugPrintPackets = []string{""}
|
||||
err = client.LoginRaw(c, acc)
|
||||
c, err := client.Connect(nil, *TargetAddr, "")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer c.Close()
|
||||
if !*TrueForward {
|
||||
err = client.LoginRaw(c, acc)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
p.Client = c
|
||||
p.Client.AlwaysDecode = true
|
||||
p.Servee.AlwaysDecode = true
|
||||
err = p.Start()
|
||||
return
|
||||
})
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/admin-else/strom/nbt"
|
||||
"github.com/admin-else/strom/mc/nbt"
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
+2
-2
@@ -4,12 +4,12 @@ import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/admin-else/strom/proto_generated/v1_21_8"
|
||||
generated "github.com/admin-else/strom/mc/proto_generated/%v"
|
||||
)
|
||||
|
||||
// Error: %v
|
||||
func TestFailedPacket%10X(t *testing.T) {
|
||||
p := v1_21_8.Play%vPacket{}
|
||||
p := &generated.%v{}
|
||||
b := bytes.NewBuffer(%#v)
|
||||
err := p.Decode(b)
|
||||
if err != nil {
|
||||
@@ -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 (
|
||||
"encoding/json"
|
||||
@@ -6,16 +6,16 @@ import (
|
||||
"log/slog"
|
||||
"os"
|
||||
|
||||
"github.com/admin-else/strom/api"
|
||||
"github.com/admin-else/strom/client"
|
||||
"github.com/admin-else/strom/data"
|
||||
"github.com/admin-else/strom/event"
|
||||
"github.com/admin-else/strom/nbt"
|
||||
"github.com/admin-else/strom/proto"
|
||||
"github.com/admin-else/strom/proto_base"
|
||||
"github.com/admin-else/strom/proto_generated/v1_21_8"
|
||||
"github.com/admin-else/strom/server"
|
||||
"github.com/admin-else/strom/text"
|
||||
"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/nbt"
|
||||
"github.com/admin-else/strom/mc/proto"
|
||||
"github.com/admin-else/strom/mc/proto_base"
|
||||
"github.com/admin-else/strom/mc/proto_generated/v1_21_8"
|
||||
"github.com/admin-else/strom/mc/server"
|
||||
"github.com/admin-else/strom/mc/text"
|
||||
)
|
||||
|
||||
func wontFail[T any](v T, err error) T {
|
||||
@@ -25,10 +25,7 @@ func wontFail[T any](v T, err error) T {
|
||||
return v
|
||||
}
|
||||
|
||||
// THIS IS REDUNDANT USE LOGIN PACKET IN MINECRAFT DATA
|
||||
|
||||
var (
|
||||
// Edit these two
|
||||
Version = "1.21.11"
|
||||
SaveAs = "data/registry/" + Version + ".nbt"
|
||||
|
||||
@@ -46,7 +43,7 @@ var Status = wontFail(json.Marshal(server.StatusResponse{
|
||||
|
||||
type Proxy struct {
|
||||
Client, Servee *proto.Conn
|
||||
Data nbt.Tag
|
||||
Data *nbt.Tag
|
||||
}
|
||||
|
||||
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) {
|
||||
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))
|
||||
if err != nil {
|
||||
return
|
||||
@@ -150,10 +146,7 @@ func handleClient(c *proto.Conn) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func main() {
|
||||
func Run(args []string) error {
|
||||
slog.SetLogLoggerLevel(slog.LevelDebug)
|
||||
err := server.StartServerWithOnConn(":25566", handleClient)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return server.StartServerWithOnConn(":25566", handleClient)
|
||||
}
|
||||
@@ -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"
|
||||
"time"
|
||||
|
||||
"github.com/admin-else/strom/client"
|
||||
"github.com/admin-else/strom/event"
|
||||
"github.com/admin-else/strom/proto"
|
||||
"github.com/admin-else/strom/proto_base"
|
||||
"github.com/admin-else/strom/proto_generated/v1_21_8"
|
||||
"github.com/admin-else/strom/mc/client"
|
||||
"github.com/admin-else/strom/mc/event"
|
||||
"github.com/admin-else/strom/mc/proto"
|
||||
"github.com/admin-else/strom/mc/proto_base"
|
||||
"github.com/admin-else/strom/mc/proto_generated/v1_21_8"
|
||||
)
|
||||
|
||||
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.
|
||||
// It does not resolve SRV records.
|
||||
func StatusRaw(addr string) (s *StatusClient, err error) {
|
||||
c, err := client.ConnectVersionLess(addr)
|
||||
c, err := client.ConnectVersionLess(nil, addr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -82,7 +82,7 @@ func Run(args []string) (err error) {
|
||||
return
|
||||
}
|
||||
if *srvFlag {
|
||||
*addrFlag, err = client.DoDnsSimple(*addrFlag)
|
||||
*addrFlag, _, _, err = client.DoDNSChecked(nil, *addrFlag, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
module github.com/admin-else/strom
|
||||
|
||||
go 1.25
|
||||
go 1.26.2
|
||||
|
||||
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/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/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
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/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