85 Commits
Author SHA1 Message Date
adman 69e9857bad Merge remote-tracking branch 'gitlab/master'
Go / build (push) Successful in 1m9s
2026-08-17 18:12:52 +02:00
adman d0aec490cc remove binary test files 2026-08-17 18:08:49 +02:00
adman a01cb08672 Merge remote-tracking branch 'origin/master' 2026-08-17 18:08:08 +02:00
adman 326affe364 add version flag to packet inspector 2026-08-17 18:07:51 +02:00
OpenCode 1b2552492b remove binary testdata and fuzz corpus; restore regression tests
Go / build (push) Successful in 39s
2026-08-06 15:54:37 +02:00
OpenCode 44ec148be4 remove .gitattributes and LFS, drop large replay testdata
Go / build (push) Successful in 1m1s
2026-08-06 15:52:07 +02:00
OpenCode 78442c5c68 migrate: move module path to git.anygate.cloud/anygatecloud/strom
Go / build (push) Failing after 9s
2026-08-06 15:43:20 +02:00
OpenCode ac12f5c93b chore: sync all proto_generated versions
Go / build (push) Failing after 50s
2026-08-06 15:20:42 +02:00
OpenCode e58499ed09 fix: correct bitfield decoder using mask extraction, fix command_node
- Switch bitfield decoder from shift-based to mask-based extraction (var>>pos & mask)
  fixes non-power-of-2 bitfields like 6-bit command_node flags
- Fix MultiTypeFix to handle CaseExprTypeOnlyNumber/OnlyBool variants
- Update minecraft-data command_node bitfield order for all 1.21.8+ versions
  to LSB-first convention (matches reverse encoder/decoder)
- Implement SwitchDecoder for extraNodeData via compareTo visitor
- DeclareCommands now decodes successfully (was EOF)
- Add 6 regression tests (entity_equipment, metadata, recipes, commands)
2026-08-05 22:29:08 +02:00
OpenCode f4c2762881 fix: update 26.2 SlotDisplay mapper with 3 new types from decompiled source
- Add with_any_potion (id 2), only_with_component (id 3), dyed (id 7)
- Shift existing IDs: item 2→4, item_stack 3→5, tag 4→6,
  smithing_trim 5→8, with_remainder 6→9, composite 7→10
- RecipeBookAdd now decodes successfully (was index not found)
- Verified against decompiled client jar via SlotDisplays.bootstrap()
2026-08-05 21:19:07 +02:00
OpenCode 53d366ae53 fix: implement IDSet (registryEntryHolderSet) decoder/encoder
- Replace old broken RegistryEntryHolderSetDecoder with correct varint
  discriminator: id != 0 returns raw ID, id == 0 decodes string base
- Add RegistryEntryHolderSetEncoder using type switch (string/int32)
- Add NonErroringIndex to proto_base (zero-fallback index lookup)
- DeclareRecipes now fails with index not found (mapper) instead of to do
2026-08-05 19:53:46 +02:00
OpenCode 56ac8e154e test: add 4 decode regression tests for recently fixed packets
- entity_equipment 1.8 (nbt TAG_End fix)
- entity_equipment 26.2 (topBitSetTerminatedArray fix)
- entity_metadata 1.8 (entityMetadataLoop mergeCompareTo)
- spawn_entity_living 1.8 (entityMetadata mergeCompareTo)

These ensure packets that were previously broken stay working.
2026-08-05 19:36:38 +02:00
OpenCode ac1b298989 fix: implement topBitSetTerminatedArray, fix nbt TAG_End, fix entity_equipment
- Implement topBitSetTerminatedArray decoder/encoder with MSB continuation
- Fix nbt.Tag.Decode to handle TAG_End at root level (fixes 1.8 slot NBT)
- Remove 228 now-passing decode failure tests, keep 10 still failing
- EntityEquipment now works correctly across all versions
2026-08-05 19:30:18 +02:00
OpenCode 1d8f70c7fd test: fix decode failure tests - remove 200 now-passing, fix format strings
Generator fixes resolved most decode failures. 200 tests removed
(packets now decode correctly), 38 still test known failures.
Also fixed bytes.NewBuffer->bytes.NewReader and malformed format
strings in remaining old tests.
2026-08-05 19:05:03 +02:00
OpenCode 40b584dec4 test: add 26.2 replay decode failure tests from live capture
29 tests generated from live 26.2 clientbound capture via tcpflow.
6076 packets captured, 35 decode failures (0.58%), 0 serverbound failures.
2026-08-05 19:01:37 +02:00
OpenCode eb83786f9a fix: resolve parameterized $compareTo types and fix array count type
- VisitNameAndData: return any instead of named struct when mergeCompareTo
  resolves $compareTo, fixing field type mismatches for switch/container types
- ContainerCompareTo: traverse anon/bitfield fields for compareTo resolution,
  fixes entityMetadata 'field not found' errors
- Array.Count: change from string to any to handle numeric count values
- ArrayEncoder: fix nil check to correctly write length prefix for arrays
- ArrayDecoder: use fmt.Sprint for safe Count conversion
2026-08-05 18:33:39 +02:00
OpenCode fa70ea36c6 fix: print_replay test generation & raw_to_tmcpr config state
- Fix SaveUnCodeAbleAsTest using wrong package name (1.8.9 vs v1_8)
- Fix test template using bytes.Buffer instead of bytes.NewReader
- Tests now fail when decode SUCCEEDS (bug is fixed)
- Add config state support to raw_to_tmcpr with -config flag
- Fix mca_to_tmcpr -world flag being ignored
2026-08-05 17:43:26 +02:00
OpenCode 7c978e1a7e fix: raw_to_tmcpr Configuration state + mca_to_tmcpr -world flag
- Add -config flag and Configuration state support to raw_to_tmcpr
  (required for Minecraft 1.20.2+ protocol >= 764)
- Add 'config' as valid initial state option
- Fix mca_to_tmcpr -world flag being ignored (was hardcoded dev path)
- Add TMCPR replay test data from live 1.8 capture
2026-08-05 17:36:49 +02:00
OpenCode 44bfa2280c test: add TMCPR replay test data from live 1.8 capture 2026-08-05 17:36:38 +02:00
OpenCode a54b066012 fix: preserve original hostname in handshake (DNS resolves to IP) 2026-08-05 16:06:08 +02:00
OpenCode 4b278b7ee3 fix: multiple bugfixes
- Fix data race: use atomic.Bool for running in server.go
- Fix busy loop in timer.go: use time.After when idle
- Fix division by zero in print_replay
- Fix silently swallowed errors in proto_generator
- Fix suppressed error in native_encoder
- Delete unused registry_data_hunter
2026-08-05 14:50:10 +02:00
OpenCode 0ed96837bd feat: add 1.16.5 protocol support
- Add 1.16.5 minecraft-data
- Add v1_16_5 proto_generated code
- Add OnSuccess1_16_5 handler in client/login.go
- Update proto_generator to include 1.16.5
- Update download_minecraft_data.sh
2026-08-05 11:13:58 +02:00
Admin Else c911f95665 strom: add 1.14.4 protocol support, extend legacy range to 1.14.4
- Add minecraft-data for 1.14.4 (protocol 498)
- Generate proto_generated/v1_14_4
- Extend legacy RegisterUntil range from 1.12.2 to 1.14.4
- Update download_minecraft_data.sh to use admin-else fork
  and include all supported versions
2026-08-05 10:29:07 +02:00
Admin Else f645f3d78d strom: consolidate identical handlers to single v1_8-based versions
For types identical across all versions (Compress, Disconnect, Status,
Pong), use a single handler at v1_8 registered to 26.2 instead of
per-version duplicates. For types that differ (Encrypt has extra
ShouldAuthenticate field in newer versions, Success has different
Uuid type), keep dual-range registrations.
2026-08-04 22:31:06 +02:00
Admin Else 1001c809e5 strom: fix RegisterUntilLow to panic on reverse range, use dual-range registrations
- RegisterUntilLow now panics if startIndex > endIndex (programmer
  must address version incompatibility explicitly, not silently skip)
- Replace per-version RegisterCritical handlers with dual-range
  RegisterUntil: modern range (v1_21_8->latest) + legacy range (v1_8->1.12.2)
- Remove v1_12_2 handler duplicates (covered by upward range from v1_8)
- Move legacy chat handling into chat.Module.SendMessage (protocol<764
  uses v1_8.PlayToServerPacketChat instead of v1_21_11)
- Skip chat signing init for pre-1.19 versions
2026-08-04 22:16:34 +02:00
Admin Else 1e6ce06fb0 strom: add 1.12.2 protocol support and legacy login/status handlers
- Add minecraft-data for 1.12.2 (protocol 340)
- Generate proto_generated/v1_12_2
- Add v1_12_2 legacy handlers to client.Login (OnDisconnect,
  OnCompress, OnSuccess) and client.Status (OnStatus, OnPong)
- Same pattern as v1_8: RegisterUntilLatest only registers
  upward, so legacy versions need explicit RegisterCritical handlers
2026-08-04 22:01:06 +02:00
Admin Else acb5fe8d90 port messenger to more versions 2026-08-04 19:28:43 +02:00
Admin Else d9bca7e517 strom: fix generator for pointer-receiver Encode, use nbt.Tag for named NBT
- Delete raw_capture subcommand (use tcpflow + raw-to-tmcpr instead)
- Fix DefaultEncoder to use temp variable for non-addressable value encoding,
  eliminating manual post-generation patches
- Map nbt and optionalNbt types to nbt.Tag (named NBT) instead of nbt.Anon
  (anonymous NBT) — 1.8 uses named NBT with root tag names
2026-08-04 16:40:47 +02:00
Admin Else b162d0d401 strom: add 1.8 protocol support, raw-to-tmcpr, disable raw-capture
- Add minecraft-data for version 1.8 (protocol 47)
- Generate proto_generated/v1_8 from 1.8 protocol.json
- Fix generator compatibility: missing CompareTo natives (i16, i32, u16, u32,
  i64, string, nbt, optionalNbt), parameterized type placeholder handling ()
- Add raw-to-tmcpr subcommand: converts raw tcpflow captures to TMCPR
  with zlib decompression and state-aware compression threshold tracking
- Extend print-replay: 1.8 state transitions (success->Play, login_start->Play,
  handshake/status initial states, set_protocol NextState detection)
- Comment out broken raw-capture subcommand (use tcpflow instead)
- Fix nbt.Anon Encode/Decode on value receivers
2026-08-04 16:10:15 +02:00
Admin Else 3273244c42 format 2026-08-02 20:21:15 +02:00
adman f579645b88 client: add StatusRawWithVersion for version-aware status pings 2026-07-31 19:34:16 +02:00
adman 33754d7c71 server: guard against nil KnownPacks in ServeConfig 2026-07-31 15:49:05 +02:00
adman 8bd54a04f2 client: add ConnectWithConn, WithConn, WithDialer, LoginRawAddr
- ConnectWithConn wraps an existing net.Conn into a versioned proto.Conn
- DialFunc is a context-aware dial function type
- WithConn uses a pre-established net.Conn (version required, no DNS)
- WithDialer replaces net.Dialer with a custom dial function
- LoginRawAddr accepts an explicit handshake host address
- Login() wired to support all three connection modes
2026-07-31 14:30:25 +02:00
adman 492dae8e83 Add WithIgnoreConfig option to skip CONFIGURATION auto-completion 2026-07-31 12:10:32 +02:00
adman d9543de951 add check for bad servers 2026-07-30 02:40:43 +02:00
adman f1f5374a5c more context 2026-07-30 02:36:37 +02:00
adman 0d6b6e1ab9 docs: add godoc comments to all exported functions and methods 2026-07-30 00:20:55 +02:00
adman 56c97a2b3f docs: add API reference for all user-facing packages 2026-07-30 00:10:57 +02:00
adman 9063a0328d fix: make RegisterCritical variadic for API consistency with RegisterCriticalUntilLatest 2026-07-30 00:05:39 +02:00
adman 84b28d4c12 enforce MaxPacketSize constraint for raw and compressed packets 2026-07-28 16:37:45 +02:00
adman 30cfc507ba less pollution 2026-07-27 15:05:50 +02:00
adman d84618c116 update minecraft data to fix bundle delimiter 2026-07-23 22:32:59 +02:00
adman 5a55520126 migrate proto_generated to be runable from repo root 2026-07-23 22:26:23 +02:00
adman 8ac5e00c8e regenerate protocol 2026-07-23 22:23:22 +02:00
adman d46c678b89 make mincraft data sync scripts executable from anywhere 2026-07-23 22:20:53 +02:00
adman 0b714edcf5 Sync minecraft-data 2026-07-23 22:14:19 +02:00
adman 45151add64 Remove proto_base.Void 2026-07-23 22:11:29 +02:00
adman b90830881f Fix 26.2 login_success protocol handling and add convertibility tests
- Server sends v26_2 login_success with SessionId for protocol >= 776

- Client adds v26_2 login_success handler and scopes v1_21_8 handler to <= 26.1

- Fix WithOtherAccount guard in login server

- Add smart-convert assumption tests for login server/client

- Improve translatePacketVersion error messages
2026-07-23 17:00:13 +02:00
adman 94301a4a50 Add 26.2 protocol support to strom 2026-07-23 15:06:49 +02:00
adman aa5d15a4dc Regenerate 26.1 protocol with corrected play packet IDs (clientbound + serverbound) 2026-07-23 00:09:43 +02:00
adman d388e2c350 add 26.1 support, raw-capture subcommand, and local minecraft-data copy script 2026-07-22 23:22:14 +02:00
adman 126ff5bca0 improve packet replay things 2026-07-20 12:56:19 +02:00
adman a347aedfc2 untrack big testdata 2026-07-16 22:56:44 +02:00
adman e324a95727 cmd take account with env var 2026-07-16 22:16:04 +02:00
adman 7f878f40a1 add world module for in-memory block storage and integrate getblock command in messenger 2026-07-16 16:05:42 +02:00
adman 7baa9990cb yes222 2026-07-16 14:02:28 +02:00
adman 5c5c391aa6 yes 2026-07-16 01:13:31 +02:00
adman b966925212 move data stuff 2026-07-11 23:08:03 +02:00
adman efd7049859 biome map stuff 2026-07-11 22:08:29 +02:00
adman 3ef9446f50 biome depth stuff 2026-07-06 11:49:45 +02:00
adman 68612969c9 fix spline test 2026-07-05 22:32:39 +02:00
adman b4712ff5c6 spline 2026-07-05 20:53:59 +02:00
adman eab050e291 fixes 2026-07-05 10:49:45 +02:00
adman a8b090227b mc gen start 2026-07-05 10:25:43 +02:00
adman 201e0dfa44 fix moving pains 2026-06-27 14:10:31 +02:00
adman 666f250b3e mc root source folder cleanup 2026-06-27 13:54:54 +02:00
adman b2784574bd move tools to strom cli 2026-06-27 13:06:05 +02:00
adman c0b1501f23 add packet spy json logging 2026-06-17 11:45:54 +02:00
adman 4de76d8023 regenerate proto 2026-06-16 23:08:00 +02:00
adman f08684fb67 fix bug in entity metadata encoding 2026-06-16 23:07:33 +02:00
adman 9e4f9409c5 add TestEntityMetadata 2026-06-16 23:06:58 +02:00
adman 4d3fbed957 add SimplePacketToBytesLenPrefix 2026-06-16 23:06:42 +02:00
adman b338888b58 add SimpleBytesToPacket utility function for decoding packets 2026-06-16 22:29:26 +02:00
adman 4956c1d874 add OnFinishLogin handling to packet inspector 2026-06-16 22:28:01 +02:00
adman d4fe362d9f add MustLookupProtocolVersion utility function for testing purposes 2026-06-16 22:27:41 +02:00
adman 5d8ff870df add true-forward mode to packet inspector 2026-06-02 21:06:41 +02:00
adman 58c9a60236 add optional encryption flag to login server and update ServeLogin behavior 2026-05-30 15:53:35 +02:00
adman 3e5b141ec7 add ToolSum function for build info and integrate it into Version outputs 2026-05-29 01:27:35 +02:00
adman efeb5ecda6 add file comment generation and hashing for reproducible output in proto_generator 2026-05-29 01:14:56 +02:00
adman 624e8ad4de add SimplePacketToBytes utility function for encoding packets 2026-05-28 23:59:54 +02:00
adman 764cfd8c4d lpvec3d 2026-05-14 10:42:38 +02:00
adman d43ff7f83d add seek-back handling in entity metadata decoding 2026-05-14 00:06:45 +02:00
adman a39999b344 update Decode function signatures to use io.ReadSeeker instead of io.Reader 2026-05-13 23:54:06 +02:00
adman 8ee48e1a45 add EntityMetadataLoop handling 2026-05-13 23:21:31 +02:00
adman bf6affeb97 remove unused protodef_types file from proto_generator 2026-05-13 22:20:32 +02:00
678 changed files with 1717262 additions and 54835 deletions
-6
View File
@@ -1,6 +0,0 @@
**/*_test.go export-ignore
**/testdata export-ignore
*.zip filter=lfs diff=lfs merge=lfs -text
*.tmcpr filter=lfs diff=lfs merge=lfs -text
*.mcpr filter=lfs diff=lfs merge=lfs -text
*.mca filter=lfs diff=lfs merge=lfs -text
+28
View File
@@ -0,0 +1,28 @@
name: Go
on:
push:
branches: ["master"]
pull_request:
branches: ["master"]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: "1.26.2"
- name: Build
run: go build -v ./...
- name: Vet
run: go vet -v ./...
- name: Test
run: go test -v ./...
+2 -2
View File
@@ -1,5 +1,5 @@
.devres/ .devres/
.idea/ .idea/
.failed_packets/ .failed_packets/
tools/.*/ .vscode/proto_generator
.vscode/ proto_generator
-10
View File
@@ -1,10 +0,0 @@
package api
import "github.com/admin-else/strom/crypto"
func NewOfflineAccount(name string) *Account {
return &Account{
Name: name,
Uuid: crypto.FromOfflinePlayer(name),
}
}
-43
View File
@@ -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
View File
@@ -1,126 +0,0 @@
package client
import (
"context"
"errors"
"net"
"strconv"
"strings"
"time"
)
type DNSCheckFunc func(addr string) error
// DoDns resolves a DNS name to an IP address
// It should resolve a minecraft compatible address into something net.Dial
func DoDns(in string) (out string, host string, portN uint16, err error) {
return DoDNSChecked(in, nil)
}
// DoDNSChecked resolves a DNS name to an IP address
// It should resolve a minecraft compatible address into something net.Dial
// It also optionally accepts a function to "check" the address
func DoDNSChecked(in string, f DNSCheckFunc) (connectTo string, host string, portN uint16, err error) {
// https://mcsrc.dev/1/26.1-pre-1/net/minecraft/client/multiplayer/resolver/ServerAddress#L34
host, port, err := net.SplitHostPort(in)
if err != nil {
var addrErr *net.AddrError
if errors.As(err, &addrErr) && addrErr.Err == "missing port in address" {
err = nil
host = in
port = "25565"
} else {
return
}
}
porti, err := strconv.Atoi(port)
if err != nil {
return
}
if f == nil {
f = func(addr string) error { return nil }
}
err = f(host)
if err != nil {
return
}
portN = uint16(porti)
outIP, err := net.ResolveIPAddr("ip", host)
if err != nil {
return
}
err = f(outIP.String())
if err != nil {
return
}
connectTo = outIP.String() + ":" + port
if portN != 25565 {
return
}
ctx, cancel := context.WithTimeout(context.Background(), time.Second) // otherwise this takes 10+ seconds
defer cancel()
r := &net.Resolver{}
_, srvs, _ := r.LookupSRV(ctx, "minecraft", "tcp", host)
if len(srvs) == 0 {
return
}
host = strings.TrimSuffix(srvs[0].Target, ".")
portN = srvs[0].Port
outIP, err = net.ResolveIPAddr("ip", host)
if err != nil {
return
}
err = f(outIP.String())
if err != nil {
return
}
connectTo = outIP.String() + ":" + port
return
}
// DoDnsSimple resolves a DNS name to an IP address
// It should resolve a minecraft compatible address into something net.Dial
// It does not check the address
func DoDnsSimple(in string) (out string, err error) {
host, port, err := net.SplitHostPort(in)
if err != nil {
var addrErr *net.AddrError
if errors.As(err, &addrErr) && addrErr.Err == "missing port in address" {
err = nil
host = in
port = "25565"
} else {
return
}
}
porti, err := strconv.Atoi(port)
if err != nil {
return
}
portN := uint16(porti)
_, err = net.ResolveIPAddr("ip", host)
if err != nil {
return
}
out = host + ":" + strconv.Itoa(int(portN))
if portN != 25565 {
return
}
ctx, cancel := context.WithTimeout(context.Background(), time.Second) // otherwise this takes 10+ seconds
defer cancel()
r := &net.Resolver{}
_, srvs, _ := r.LookupSRV(ctx, "minecraft", "tcp", host)
if len(srvs) == 0 {
return
}
host = strings.TrimSuffix(srvs[0].Target, ".")
portN = srvs[0].Port
out = host + ":" + strconv.Itoa(int(portN))
return
}
-179
View File
@@ -1,179 +0,0 @@
package client
import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/json"
"errors"
"github.com/admin-else/strom/api"
"github.com/admin-else/strom/crypto"
"github.com/admin-else/strom/event"
"github.com/admin-else/strom/proto"
"github.com/admin-else/strom/proto_base"
"github.com/admin-else/strom/text"
"github.com/admin-else/strom/proto_generated/v1_21_8"
)
type LoginClient struct {
*proto.Conn
Account *api.Account
ServerHost string
ServerPort uint16
GivenAccount *v1_21_8.LoginToClientPacketSuccess
}
var (
NoTokenErr = errors.New("the account has no token so we cant join online servers")
BadPublicKeyTypeErr = errors.New("the public key is not rsa")
FailedToLoginErr = errors.New("failed to login")
)
type KickedDuringLoginErr struct {
text.Component
}
func (k KickedDuringLoginErr) Error() string {
return k.String()
}
func (s *LoginClient) OnDefault(event event.Unhandled) (err error) {
if _, ok := event.Val.(proto_base.EncodeDecodeAble); !ok {
return
}
s.Log.Warn("unexpected packet during login client", "packet", event.Val)
return
}
func (s *LoginClient) OnClose(_ event.Close) (err error) {
return
}
func (s *LoginClient) OnCompress(compress *v1_21_8.LoginToClientPacketCompress) (err error) {
s.SetCompressionThreshold(compress.Threshold)
return
}
func (s *LoginClient) OnEncrypt(packet *v1_21_8.LoginToClientPacketEncryptionBegin) (err error) {
sharedSecret := make([]byte, 16)
_, _ = rand.Read(sharedSecret) //never fails
if packet.ShouldAuthenticate {
if s.Account.Ygg == "" {
err = NoTokenErr
return
}
serverId := crypto.AuthDigest([]byte(packet.ServerId), sharedSecret, packet.PublicKey)
err = s.Account.JoinServer(serverId)
if err != nil {
return
}
}
pubAny, err := x509.ParsePKIXPublicKey(packet.PublicKey)
if err != nil {
return
}
pub, ok := pubAny.(*rsa.PublicKey)
if !ok {
err = BadPublicKeyTypeErr
return
}
verifyTokenEnc, err := rsa.EncryptPKCS1v15(rand.Reader, pub, packet.VerifyToken)
if err != nil {
return
}
sharedSecretEnc, err := rsa.EncryptPKCS1v15(rand.Reader, pub, sharedSecret)
if err != nil {
return
}
err = s.Send(&v1_21_8.LoginToServerPacketEncryptionBegin{SharedSecret: sharedSecretEnc, VerifyToken: verifyTokenEnc})
if err != nil {
return
}
err = s.SetSecret(sharedSecret)
return
}
func (s *LoginClient) OnDisconnect(packet *v1_21_8.LoginToClientPacketDisconnect) (err error) {
var reason text.Component
err = json.Unmarshal([]byte(packet.Reason), &reason)
if err != nil {
return
}
err = KickedDuringLoginErr{reason}
return
}
func (s *LoginClient) OnSuccess(success *v1_21_8.LoginToClientPacketSuccess) (err error) {
s.GivenAccount = success
err = s.Send(&v1_21_8.LoginToServerPacketLoginAcknowledged{})
if err != nil {
return
}
s.SetState(proto_base.Configuration)
err = event.HandlerDoneErr{}
return
}
func (s *LoginClient) OnStart() (err error) {
return
}
func LoginRaw(c *proto.Conn, account *api.Account) (err error) {
lc := &LoginClient{
Conn: c,
Account: account,
}
lc.RegisterCritical(lc.OnDefault)
lc.RegisterCritical(lc.OnClose)
lc.RegisterCriticalUntilLatest(lc.OnCompress)
lc.RegisterCriticalUntilLatest(lc.OnEncrypt)
lc.RegisterCriticalUntilLatest(lc.OnDisconnect)
lc.RegisterCriticalUntilLatest(lc.OnSuccess)
p, err := MakeHandshakePacket(lc.Conn, proto_base.Login)
if err != nil {
return
}
err = lc.Send(p)
if err != nil {
return
}
lc.SetState(proto_base.Login)
err = lc.Send(&v1_21_8.LoginToServerPacketLoginStart{Username: lc.Account.Name, PlayerUUID: lc.Account.Uuid})
err = lc.StartConn()
if err != nil {
err = errors.Join(FailedToLoginErr, err)
}
return
}
func LoginNoSrv(connectTo string, account *api.Account) (c *proto.Conn, err error) {
c, err = Connect(connectTo)
if err != nil {
return
}
err = LoginRaw(c, account)
return
}
func Login(connectTo string, account *api.Account) (c *proto.Conn, err error) {
connectTo, err = DoDnsSimple(connectTo)
if err != nil {
return
}
c, err = Connect(connectTo)
if err != nil {
return
}
err = LoginRaw(c, account)
return
}
+3 -2
View File
@@ -4,9 +4,10 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"flag" "flag"
"log/slog"
"os" "os"
"github.com/admin-else/strom/api" "git.anygate.cloud/anygatecloud/strom/mc/api"
) )
var cmd = flag.NewFlagSet("api", flag.ContinueOnError) var cmd = flag.NewFlagSet("api", flag.ContinueOnError)
@@ -52,7 +53,7 @@ func Run(args []string) (err error) {
return return
} }
} else { } else {
err = PleaseProvideArgumentErr slog.Info("account", "name", a.Name, "uuid", a.Uuid)
return return
} }
err = json.NewEncoder(os.Stdout).Encode(data) err = json.NewEncoder(os.Stdout).Encode(data)
+11
View File
@@ -0,0 +1,11 @@
package cmd_util
import "git.anygate.cloud/anygatecloud/strom/mc/api"
func Account(s string) (a *api.Account, err error) {
if len(s) > 16 {
return api.NewAccountFromYGG(s)
}
a = api.NewOfflineAccount(s)
return
}
+7 -7
View File
@@ -7,9 +7,9 @@ import (
"os" "os"
"slices" "slices"
"github.com/admin-else/strom/data" mcdata "git.anygate.cloud/anygatecloud/strom/mc/data"
"github.com/admin-else/strom/proto_generated" "git.anygate.cloud/anygatecloud/strom/mc/proto_generated"
"github.com/admin-else/strom/util" "git.anygate.cloud/anygatecloud/strom/mc/util"
) )
// strom data 1.21.11 blocks id 0 name => air // strom data 1.21.11 blocks id 0 name => air
@@ -39,11 +39,11 @@ func Run(args []string) (err error) {
case 0: // print available versions case 0: // print available versions
dataValues = proto_generated.SupportedVersions dataValues = proto_generated.SupportedVersions
case 1: // print available data for version case 1: // print available data for version
dataValues = slices.Collect(maps.Keys(data.Paths.Data[args[0]])) dataValues = slices.Collect(maps.Keys(mcdata.Paths.Data[args[0]]))
case 2: case 2:
err = data.LoadVersionedJson(args[0], args[1], &dataValues) err = mcdata.LoadVersionedJson(args[0], args[1], &dataValues)
case 3: case 3:
err = data.LoadVersionedJson(args[0], args[1], &dataValues) err = mcdata.LoadVersionedJson(args[0], args[1], &dataValues)
if err != nil { if err != nil {
return return
} }
@@ -68,7 +68,7 @@ func Run(args []string) (err error) {
} }
dataValues = result dataValues = result
case 4, 5: case 4, 5:
err = data.LoadVersionedJson(args[0], args[1], &dataValues) err = mcdata.LoadVersionedJson(args[0], args[1], &dataValues)
if err != nil { if err != nil {
return return
} }
+2 -2
View File
@@ -6,8 +6,8 @@ import (
"log/slog" "log/slog"
"os" "os"
"github.com/admin-else/strom/level/anvil" "git.anygate.cloud/anygatecloud/strom/mc/level/anvil"
"github.com/admin-else/strom/nbt" "git.anygate.cloud/anygatecloud/strom/mc/nbt"
) )
var ( var (
+40 -19
View File
@@ -1,39 +1,53 @@
package main package main
import ( import (
"debug/buildinfo"
"errors" "errors"
"fmt" "fmt"
"log/slog" "log/slog"
"maps" "maps"
"os" "os"
"runtime/debug"
"slices" "slices"
"github.com/admin-else/strom/cmd/strom/api" "git.anygate.cloud/anygatecloud/strom/cmd/strom/api"
"github.com/admin-else/strom/cmd/strom/data" "git.anygate.cloud/anygatecloud/strom/cmd/strom/data"
"github.com/admin-else/strom/cmd/strom/extract_mca" "git.anygate.cloud/anygatecloud/strom/cmd/strom/extract_mca"
"github.com/admin-else/strom/cmd/strom/offline_uuid" "git.anygate.cloud/anygatecloud/strom/cmd/strom/mca_to_tmcpr"
"github.com/admin-else/strom/cmd/strom/packet_info" "git.anygate.cloud/anygatecloud/strom/cmd/strom/messenger"
"github.com/admin-else/strom/cmd/strom/packet_inspector" "git.anygate.cloud/anygatecloud/strom/cmd/strom/offline_uuid"
"github.com/admin-else/strom/cmd/strom/print_nbt" "git.anygate.cloud/anygatecloud/strom/cmd/strom/packet_info"
"github.com/admin-else/strom/cmd/strom/print_replay" "git.anygate.cloud/anygatecloud/strom/cmd/strom/packet_inspector"
"github.com/admin-else/strom/cmd/strom/status" "git.anygate.cloud/anygatecloud/strom/cmd/strom/print_nbt"
"git.anygate.cloud/anygatecloud/strom/cmd/strom/print_replay"
// "git.anygate.cloud/anygatecloud/strom/cmd/strom/raw_capture" // removed: use tcpflow instead for raw TCP capture
"git.anygate.cloud/anygatecloud/strom/cmd/strom/raw_to_tmcpr"
"git.anygate.cloud/anygatecloud/strom/cmd/strom/serve_tmcpr"
"git.anygate.cloud/anygatecloud/strom/cmd/strom/serve_world"
"git.anygate.cloud/anygatecloud/strom/cmd/strom/status"
) )
var ExpectedASubcommandErr = errors.New("expected a subcommand") var ExpectedASubcommandErr = errors.New("expected a subcommand")
var UnknownSubcommandErr = errors.New("unknown subcommand try 'strom help' for a list of subcommands") var UnknownSubcommandErr = errors.New("unknown subcommand try 'strom help' for a list of subcommands")
func Version(args []string) (err error) { func ToolVersion() string {
selfPath, err := os.Executable() info, ok := debug.ReadBuildInfo()
if err != nil { if !ok {
return return "devel"
} }
info, err := buildinfo.ReadFile(selfPath) return info.Main.Version
if err != nil { }
return
func ToolSum() string {
info, ok := debug.ReadBuildInfo()
if !ok {
return ""
} }
fmt.Println("strom cli ", info.Main.Version, " (", info.Main.Sum, ")") return info.Main.Sum
return }
func Version(args []string) error {
fmt.Println("strom cli ", ToolVersion(), " (", ToolSum(), ")")
return nil
} }
var subcommands = map[string]func(args []string) error{ var subcommands = map[string]func(args []string) error{
@@ -47,6 +61,12 @@ var subcommands = map[string]func(args []string) error{
"extract-mca": extract_mca.Run, "extract-mca": extract_mca.Run,
"data": data.Run, "data": data.Run,
"print-replay": print_replay.Run, "print-replay": print_replay.Run,
// "raw-capture": raw_capture.Run, // removed: use tcpflow instead for raw TCP capture, then raw-to-tmcpr
"raw-to-tmcpr": raw_to_tmcpr.Run,
"serve-world": serve_world.Run,
"mca-to-tmcpr": mca_to_tmcpr.Run,
"serve-tmcpr": serve_tmcpr.Run,
"messenger": messenger.Run,
} }
func Help(args []string) (err error) { func Help(args []string) (err error) {
@@ -59,6 +79,7 @@ func Help(args []string) (err error) {
func mainE() (err error) { func mainE() (err error) {
subcommands["help"] = Help subcommands["help"] = Help
args := os.Args args := os.Args
//args = []string{"TEST", "messenger", "-addr", "localhost"}
if len(args) < 2 { if len(args) < 2 {
err = ExpectedASubcommandErr err = ExpectedASubcommandErr
+212
View File
@@ -0,0 +1,212 @@
package mca_to_tmcpr
import (
"bytes"
"flag"
"fmt"
"log/slog"
"os"
"path/filepath"
"git.anygate.cloud/anygatecloud/strom/mc/level"
"git.anygate.cloud/anygatecloud/strom/mc/level/anvil"
"git.anygate.cloud/anygatecloud/strom/mc/nbt"
"git.anygate.cloud/anygatecloud/strom/mc/proto/replay"
"git.anygate.cloud/anygatecloud/strom/mc/proto_generated/v1_21_11"
"git.anygate.cloud/anygatecloud/strom/mc/util"
)
var (
cmd = flag.NewFlagSet("mca-to-tmcpr", flag.ContinueOnError)
worldPath = cmd.String("world", "", "Path to world directory")
output = cmd.String("output", "", "Output .tmcpr file path")
radius = cmd.Int("radius", 14, "Chunk radius around spawn")
spawnX = cmd.Float64("spawn-x", 0.5, "Spawn X coordinate")
spawnY = cmd.Float64("spawn-y", 100, "Spawn Y coordinate")
spawnZ = cmd.Float64("spawn-z", 0.5, "Spawn Z coordinate")
)
var FullLight [][]uint8
func init() {
light := make([]uint8, 2048)
for i := range light {
light[i] = 0xFF
}
sectionsPerChunk := 384 / 16
FullLight = make([][]uint8, sectionsPerChunk)
for i := range sectionsPerChunk {
FullLight[i] = light
}
}
func GetChunkFromMca(x, z int32, version string) (b []byte, err error) {
xmca := util.FloorDiv(x, anvil.ChunksWidthRegionFile)
zmca := util.FloorDiv(z, anvil.ChunksWidthRegionFile)
x = x % anvil.ChunksWidthRegionFile
if x < 0 {
x += anvil.ChunksWidthRegionFile
}
z = z % anvil.ChunksWidthRegionFile
if z < 0 {
z += anvil.ChunksWidthRegionFile
}
f, err := os.Open(filepath.Join(*worldPath, "region", fmt.Sprintf("r.%v.%v.mca", xmca, zmca)))
if err != nil {
return
}
defer f.Close()
mca, err := anvil.ReadChunkHeader(f)
if err != nil {
return
}
var n *nbt.Tag
n, err = mca.ChunkAt(f, x, z)
if err != nil {
return
}
if n == nil {
return
}
var chunk anvil.Chunk
err = nbt.Format.Decode(n.Value, &chunk)
if err != nil {
return
}
var packetChunk *level.Chunk
packetChunk, err = chunk.ToStorage(version)
if err != nil {
return
}
buffer := bytes.NewBuffer(nil)
err = packetChunk.WriteChunkData(buffer)
if err != nil {
return
}
b = buffer.Bytes()
return
}
func Run(args []string) error {
slog.SetLogLoggerLevel(slog.LevelDebug)
err := cmd.Parse(args)
if err != nil {
return err
}
if *worldPath == "" {
slog.Error("no world path specified")
return nil
}
if *output == "" {
slog.Error("no output path specified")
return nil
}
outFile, err := os.Create(*output)
if err != nil {
slog.Error("failed to create output file", "error", err)
return err
}
defer outFile.Close()
w := replay.NewWriter(outFile)
err = w.WritePacket(&v1_21_11.PlayToClientPacketLogin{
EntityId: 73,
IsHardcore: false,
WorldNames: []string{"minecraft:overworld", "minecraft:the_end", "minecraft:the_nether"},
MaxPlayers: 100,
ViewDistance: 10,
SimulationDistance: 10,
ReducedDebugInfo: false,
EnableRespawnScreen: true,
DoLimitedCrafting: false,
WorldState: v1_21_11.PlayToClientSpawnInfo{
Dimension: 0,
Name: "minecraft:overworld",
HashedSeed: -8365436745629239539,
Gamemode: "survival",
PreviousGamemode: 0xff,
IsDebug: false,
IsFlat: false,
Death: nil,
PortalCooldown: 0,
SeaLevel: 63,
},
EnforcesSecureChat: false,
}, 0)
if err != nil {
slog.Error("failed to write login packet", "error", err)
return err
}
err = w.WritePacket(&v1_21_11.PlayToClientPacketPosition{
TeleportId: 0,
X: *spawnX,
Y: *spawnY,
Z: *spawnZ,
Dx: 0,
Dy: 0,
Dz: 0,
Yaw: 0,
Pitch: 0,
Flags: v1_21_11.PlayToClientPositionUpdateRelatives{},
}, 0)
if err != nil {
slog.Error("failed to write position packet", "error", err)
return err
}
chunkX := int32(*spawnX) >> 4
chunkZ := int32(*spawnZ) >> 4
err = w.WritePacket(&v1_21_11.PlayToClientPacketUpdateViewPosition{
ChunkX: chunkX,
ChunkZ: chunkZ,
}, 0)
if err != nil {
slog.Error("failed to write view position packet", "error", err)
return err
}
err = w.WritePacket(&v1_21_11.PlayToClientPacketGameStateChange{
Reason: "level_chunks_load_start",
}, 0)
if err != nil {
slog.Error("failed to write game state packet", "error", err)
return err
}
radius32 := int32(*radius)
for x := -radius32; x < radius32; x++ {
for z := -radius32; z < radius32; z++ {
var chunkData []byte
chunkData, err = GetChunkFromMca(x, z, "1.21.11")
if err != nil {
return err
}
if chunkData == nil {
continue
}
OLDpacket := &v1_21_11.PlayToClientPacketMapChunk{}
OLDpacket.X = x
OLDpacket.Z = z
OLDpacket.ChunkData = v1_21_11.ByteArray{Val: chunkData}
OLDpacket.BlockLight = FullLight
OLDpacket.BlockLightMask = []int64{(1 << 24) - 1}
err = w.WritePacket(OLDpacket, 0)
if err != nil {
return err
}
}
}
slog.Info("conversion complete", "output", *output)
return nil
}
+164
View File
@@ -0,0 +1,164 @@
package messenger
import (
"flag"
"fmt"
"os"
"strconv"
"strings"
"git.anygate.cloud/anygatecloud/strom/cmd/strom/cmd_util"
"git.anygate.cloud/anygatecloud/strom/mc/bot/chat"
"git.anygate.cloud/anygatecloud/strom/mc/bot/keepalive"
"git.anygate.cloud/anygatecloud/strom/mc/bot/world"
"git.anygate.cloud/anygatecloud/strom/mc/client"
"git.anygate.cloud/anygatecloud/strom/mc/data"
"git.anygate.cloud/anygatecloud/strom/mc/event"
"git.anygate.cloud/anygatecloud/strom/mc/proto"
"git.anygate.cloud/anygatecloud/strom/mc/proto_generated/v1_21_11"
"git.anygate.cloud/anygatecloud/strom/mc/proto_generated/v1_8"
)
func stror(a, b string) string {
if a != "" {
return a
}
return b
}
var (
cmd = flag.NewFlagSet("status", flag.ContinueOnError)
connectToFlag = cmd.String("addr", "", "server address to connect to")
accFlag = cmd.String("acc", stror(os.Getenv("STROM_ACC"), "strom_messenger"), "the name to use if longer will be treated as ygg token")
versionFlag = cmd.String("version", "26.2", "minecraft version to use (e.g. 1.8, 1.21.11)")
)
type Messenger struct {
*proto.Conn
chat *chat.Module
world *world.Module
legacy bool
}
func (m *Messenger) OnChat(e *v1_21_11.PlayToClientPacketPlayerChat) (err error) {
m.Log.Info("chat", "author", e.SenderUuid, "m", e.PlainMessage)
return
}
func (m *Messenger) OnChatUnsigned(e *v1_21_11.PlayToClientPacketProfilelessChat) (err error) {
m.Log.Info("chatP", "author", e.Name, "m", e.Message)
return
}
func (m *Messenger) OnChatSystem(e *v1_21_11.PlayToClientPacketSystemChat) (err error) {
m.Log.Info("chatS", "m", e.Content)
return
}
func (m *Messenger) OnChatLegacy(e *v1_8.PlayToClientPacketChat) (err error) {
m.Log.Info("chat", "m", e.Message)
return
}
func (m *Messenger) OnStdin(e event.Stdin) (err error) {
input := strings.TrimSpace(e.Val)
if strings.HasPrefix(input, ">say ") {
return m.sendChat(strings.TrimSpace(strings.TrimPrefix(input, ">say")))
}
if !m.legacy && strings.HasPrefix(input, ">getblock ") {
return m.handleGetBlock(strings.TrimSpace(strings.TrimPrefix(input, ">getblock")))
}
return m.sendChat(input)
}
func (m *Messenger) sendChat(message string) (err error) {
if m.legacy {
return m.Send(&v1_8.PlayToServerPacketChat{Message: message})
}
return m.chat.SendMessage(message)
}
func (m *Messenger) handleGetBlock(args string) (err error) {
fields := strings.Fields(args)
if len(fields) != 3 {
m.Log.Info("getblock", "error", "expected >getblock <x> <y> <z>")
return nil
}
coords := make([]int64, 3)
for i, f := range fields {
coords[i], err = strconv.ParseInt(f, 10, 32)
if err != nil {
m.Log.Info("getblock", "error", fmt.Sprintf("bad coordinate %q", f))
return nil
}
}
stateId, err := m.world.GetBlock(int32(coords[0]), int32(coords[1]), int32(coords[2]))
if err != nil {
m.Log.Info("getblock", "error", err.Error())
return nil
}
block, ok := data.LookupBlockByStateId(m.Version, stateId)
if !ok {
m.Log.Info("getblock", "stateId", stateId, "name", "unknown")
return nil
}
m.Log.Info("getblock", "x", coords[0], "y", coords[1], "z", coords[2], "name", block.Name, "stateId", stateId)
return nil
}
func Run(args []string) (err error) {
err = cmd.Parse(args)
if err != nil {
return
}
acc, err := cmd_util.Account(*accFlag)
if err != nil {
return
}
c, err := client.Login(*connectToFlag, acc, client.WithVersion(*versionFlag))
if err != nil {
return
}
defer c.Close()
isLegacy := c.ProtocolVersion < 764
var chatMod *chat.Module
if !isLegacy {
chatMod, err = chat.Start(c, acc)
if err != nil {
return
}
}
m := &Messenger{
Conn: c,
chat: chatMod,
legacy: isLegacy,
}
if !isLegacy {
w := world.NewWorld(c.Version, -64, 384)
m.world = world.Start(c, w)
}
event.StartListingStdin(m.Loop)
m.Register(m.OnStdin)
if isLegacy {
m.Register(m.OnChatLegacy)
} else {
m.RegisterUntilLatest(m.OnChat, m.OnChatUnsigned, m.OnChatSystem)
}
if !isLegacy {
keepalive.Start(m.Conn)
}
return m.StartConn()
}
+1 -1
View File
@@ -4,7 +4,7 @@ import (
"flag" "flag"
"fmt" "fmt"
"github.com/admin-else/strom/crypto" "git.anygate.cloud/anygatecloud/strom/mc/crypto"
) )
var cmd = flag.NewFlagSet("offline-uuid", flag.ContinueOnError) var cmd = flag.NewFlagSet("offline-uuid", flag.ContinueOnError)
+4 -4
View File
@@ -5,10 +5,10 @@ import (
"fmt" "fmt"
"strconv" "strconv"
"github.com/admin-else/strom/data" "git.anygate.cloud/anygatecloud/strom/mc/data"
"github.com/admin-else/strom/proto" "git.anygate.cloud/anygatecloud/strom/mc/proto"
"github.com/admin-else/strom/proto_base" "git.anygate.cloud/anygatecloud/strom/mc/proto_base"
"github.com/admin-else/strom/proto_generated" "git.anygate.cloud/anygatecloud/strom/mc/proto_generated"
) )
var cmd = flag.NewFlagSet("packet-info", flag.ContinueOnError) var cmd = flag.NewFlagSet("packet-info", flag.ContinueOnError)
+94 -19
View File
@@ -1,18 +1,21 @@
package packet_inspector package packet_inspector
import ( import (
"encoding/json"
"flag" "flag"
"log/slog" "log/slog"
"os"
"time"
"github.com/admin-else/strom/api" "git.anygate.cloud/anygatecloud/strom/mc/api"
"github.com/admin-else/strom/client" "git.anygate.cloud/anygatecloud/strom/mc/client"
"github.com/admin-else/strom/event" "git.anygate.cloud/anygatecloud/strom/mc/event"
"github.com/admin-else/strom/nbt" "git.anygate.cloud/anygatecloud/strom/mc/nbt"
"github.com/admin-else/strom/proto" "git.anygate.cloud/anygatecloud/strom/mc/proto"
"github.com/admin-else/strom/proto_base" "git.anygate.cloud/anygatecloud/strom/mc/proto_base"
"github.com/admin-else/strom/proto_generated/v1_21_11" "git.anygate.cloud/anygatecloud/strom/mc/proto_generated/v1_21_11"
"github.com/admin-else/strom/server" "git.anygate.cloud/anygatecloud/strom/mc/server"
"github.com/admin-else/strom/text" "git.anygate.cloud/anygatecloud/strom/mc/text"
) )
var Cmd = flag.NewFlagSet("packet-inspector", flag.ContinueOnError) var Cmd = flag.NewFlagSet("packet-inspector", flag.ContinueOnError)
@@ -22,6 +25,8 @@ var Token = Cmd.String("token", "", "token to use")
var TargetAddr = Cmd.String("addr", "127.0.0.1:25565", "address to connect to") var TargetAddr = Cmd.String("addr", "127.0.0.1:25565", "address to connect to")
var ListenAddr = Cmd.String("listen", "127.0.0.1:25566", "address to listen on") var ListenAddr = Cmd.String("listen", "127.0.0.1:25566", "address to listen on")
var TrueForward = Cmd.Bool("true-forward", false, "forward packets without modification -name and custom server status wont work")
var Version = Cmd.String("protocol-version", "", "protocol version to use; empty for auto")
type Proxy struct { type Proxy struct {
Client, Servee *proto.Conn Client, Servee *proto.Conn
@@ -33,6 +38,10 @@ func (p *Proxy) Start() (err error) {
p.Servee.RegisterCritical(p.OnAnything) p.Servee.RegisterCritical(p.OnAnything)
p.Servee.RegisterCriticalUntilLatest(p.OnFinishConfiguration) p.Servee.RegisterCriticalUntilLatest(p.OnFinishConfiguration)
p.Servee.RegisterCriticalUntilLatest(p.OnFinishLogin)
p.Servee.RegisterCriticalUntilLatest(p.OnHandshake)
//p.Servee.RegisterCriticalUntilLatest(p.OnCompress)
p.Client.RegisterCriticalUntilLatest(p.OnCompress)
errChan := make(chan error) errChan := make(chan error)
go func() { go func() {
@@ -45,6 +54,22 @@ func (p *Proxy) Start() (err error) {
return return
} }
type PacketLogEvent struct {
Time time.Time
Packet proto_base.EncodeDecodeAble
Info PacketInfoNoTypeJson
ClientAddr string
}
type PacketInfoNoTypeJson struct {
Type proto_base.EncodeDecodeAble `json:"-"`
Name string
Direction proto_base.Direction
State proto_base.State
PacketId int32
ProtocolVersion int32
}
func (p *Proxy) OnAnything(e event.Anything) (err error) { func (p *Proxy) OnAnything(e event.Anything) (err error) {
packet, ok := e.Val.(proto_base.EncodeDecodeAble) packet, ok := e.Val.(proto_base.EncodeDecodeAble)
if !ok { if !ok {
@@ -54,6 +79,19 @@ func (p *Proxy) OnAnything(e event.Anything) (err error) {
if !ok { if !ok {
return return
} }
jsonBytes, err := json.Marshal(PacketLogEvent{
Time: time.Now(),
Packet: packet,
ClientAddr: p.Client.RemoteAddr().String(),
Info: PacketInfoNoTypeJson(packetInfo),
})
if err != nil {
return
}
// :pray:
_, _ = os.Stdout.Write(jsonBytes)
_, _ = os.Stdout.Write([]byte("\n"))
switch packetInfo.Direction { switch packetInfo.Direction {
case proto_base.ToServer: case proto_base.ToServer:
err = p.Client.Send(packet) err = p.Client.Send(packet)
@@ -74,6 +112,40 @@ func (p *Proxy) OnFinishConfiguration(packet *v1_21_11.ConfigurationToServerPack
return return
} }
func (p *Proxy) OnFinishLogin(packet *v1_21_11.LoginToServerPacketLoginAcknowledged) (err error) {
err = p.Client.Send(packet)
if err != nil {
return
}
p.Servee.SetState(proto_base.Configuration)
p.Client.SetState(proto_base.Configuration)
err = event.DontForwardErr
return
}
func (p *Proxy) OnHandshake(packet *v1_21_11.HandshakingToServerPacketSetProtocol) (err error) {
err = p.Client.Send(packet)
state := proto_base.State(packet.NextState)
if err != nil {
return
}
p.Servee.SetState(state)
p.Client.SetState(state)
err = event.DontForwardErr
return
}
func (p *Proxy) OnCompress(packet *v1_21_11.LoginToClientPacketCompress) (err error) {
err = p.Servee.Send(packet)
if err != nil {
return
}
p.Servee.SetCompressionThreshold(packet.Threshold)
p.Client.SetCompressionThreshold(packet.Threshold)
err = event.DontForwardErr
return
}
var StatusResponse = server.StatusResponse{ var StatusResponse = server.StatusResponse{
Version: server.StatusResponseVersion{ Version: server.StatusResponseVersion{
Name: text.Pretty("STROM"), Name: text.Pretty("STROM"),
@@ -90,38 +162,41 @@ func Run(args []string) (err error) {
if err != nil { if err != nil {
return return
} }
slog.SetLogLoggerLevel(slog.LevelDebug)
slog.Info("Starting packet inspector", "listen", *ListenAddr) slog.Info("Starting packet inspector", "listen", *ListenAddr)
err = server.StartServerWithOnConn(*ListenAddr, func(serveeConn *proto.Conn) (err error) { err = server.StartServerWithOnConn(*ListenAddr, func(serveeConn *proto.Conn) (err error) {
serveeConn.DebugPrintPackets = []string{""}
p := &Proxy{Servee: serveeConn, Client: nil} p := &Proxy{Servee: serveeConn, Client: nil}
var acc = api.NewOfflineAccount(*OfflineNameFlag) var acc = api.NewOfflineAccount(*OfflineNameFlag)
if !*TrueForward {
if *Token != "" { if *Token != "" {
acc, err = api.NewAccountFromYGG(*Token) acc, err = api.NewAccountFromYGG(*Token)
if err != nil { if err != nil {
return return
} }
} }
_, err = server.ServeLogin(serveeConn, server.WithOtherAccount(acc), server.WithStatus(StatusResponse), server.WithoutOnlineMode()) _, err = server.ServeLogin(serveeConn, server.WithOtherAccount(acc), server.WithStatus(StatusResponse), server.WithoutOnlineMode(), server.WithoutEncryption())
if err != nil { if err != nil {
return return
} }
}
*TargetAddr, err = client.DoDnsSimple(*TargetAddr) *TargetAddr, _, _, err = client.DoDNSChecked(nil, *TargetAddr, nil)
if err != nil { if err != nil {
return return
} }
c, err := client.Connect(*TargetAddr) c, err := client.Connect(nil, *TargetAddr, *Version)
if err != nil {
return
}
c.DebugPrintPackets = []string{""}
err = client.LoginRaw(c, acc)
if err != nil { if err != nil {
return return
} }
defer c.Close() defer c.Close()
if !*TrueForward {
err = client.LoginRaw(c, acc)
if err != nil {
return
}
}
p.Client = c p.Client = c
p.Client.AlwaysDecode = true
p.Servee.AlwaysDecode = true
err = p.Start() err = p.Start()
return return
}) })
+1 -1
View File
@@ -7,7 +7,7 @@ import (
"fmt" "fmt"
"os" "os"
"github.com/admin-else/strom/nbt" "git.anygate.cloud/anygatecloud/strom/mc/nbt"
) )
var ( var (
+4 -4
View File
@@ -4,15 +4,15 @@ import (
"bytes" "bytes"
"testing" "testing"
generated "github.com/admin-else/strom/proto_generated/%v" generated "git.anygate.cloud/anygatecloud/strom/mc/proto_generated/%v"
) )
// Error: %v // Error: %v
func TestFailedPacket%10X(t *testing.T) { func TestFailedPacket%10X(t *testing.T) {
p := &generated.%v{} p := &generated.%v{}
b := bytes.NewBuffer(%#v) b := bytes.NewReader(%#v)
err := p.Decode(b) err := p.Decode(b)
if err != nil { if err == nil {
t.Fatal(err) t.Fatalf("expected decode error but got nil")
} }
} }
+88 -34
View File
@@ -8,15 +8,15 @@ import (
"errors" "errors"
"flag" "flag"
"fmt" "fmt"
"go/format"
"io" "io"
"log/slog" "log/slog"
"os" "os"
"reflect" "reflect"
"strings" "strings"
"github.com/admin-else/strom/data" "git.anygate.cloud/anygatecloud/strom/mc/proto"
"github.com/admin-else/strom/proto" "git.anygate.cloud/anygatecloud/strom/mc/proto_base"
"github.com/admin-else/strom/proto_base"
) )
var ( var (
@@ -24,40 +24,47 @@ var (
FileFlag = cmd.String("file", "", "The tmcpr file to print") FileFlag = cmd.String("file", "", "The tmcpr file to print")
ProtocolVersion = cmd.Int("protocol", -1, "The protocol version") ProtocolVersion = cmd.Int("protocol", -1, "The protocol version")
CreateTestsPath = cmd.String("create-tests", "", "") CreateTestsPath = cmd.String("create-tests", "", "")
StateFlag = cmd.String("state", "login", "Initial state: login, config, play") StateFlag = cmd.String("state", "login", "Initial state: handshake, login, config, status, play")
DirectionFlag = cmd.String("direction", "clientbound", "Packet direction: clientbound (ToClient) or serverbound (ToServer)")
) )
// TODO: maybe use the proto replay api
//go:embed failed_packet.go.tmpl //go:embed failed_packet.go.tmpl
var TestSrcF string var TestSrcF string
func SaveUnCodeAbleAsTest(packet *proto.UnCodablePacket) { func SaveUnCodeAbleAsTest(packet *proto.UnCodablePacket) (err error) {
if packet.Info.Type == nil {
slog.Warn("skipping test for unknown packet id", "packet_id", packet.Info.PacketId, "state", packet.Info.State, "error", packet.Err)
return
}
hUntrimmed := sha256.Sum256(packet.Data) hUntrimmed := sha256.Sum256(packet.Data)
h := hUntrimmed[:8] h := hUntrimmed[:8]
f, err := os.Create(fmt.Sprintf(*CreateTestsPath+"/%v_%10x_test.go", len(packet.Data), h)) if err = os.MkdirAll(*CreateTestsPath, 0755); err != nil {
if err != nil { return
panic(err)
} }
ret, err := data.LookUpVersionByProtocolVersion(packet.Info.ProtocolVersion) typeStr := reflect.TypeOf(packet.Info.Type).String()
if err != nil { goPackage := typeStr[1:strings.LastIndex(typeStr, ".")]
panic(err) typeName := reflect.TypeOf(packet.Info.Type).Elem().Name()
}
goPackage := "v" + strings.ReplaceAll(ret.MinecraftVersion, ".", "_")
//goland:noinspection GoUnhandledErrorResult src := fmt.Sprintf(TestSrcF, goPackage, packet.Err, h, typeName, packet.Data)
defer f.Close() formatted, formatErr := format.Source([]byte(src))
_, err = fmt.Fprintf(f, TestSrcF, goPackage, packet.Err, h, reflect.TypeOf(packet.Info.Type).Elem().Name(), packet.Data) if formatErr != nil {
if err != nil { formatted = []byte(src)
panic(err)
} }
slog.Info("Packet failed to parse", "error", packet.Err, "saved", f.Name(), "data", fmt.Sprintf("%q", packet.Data))
filePath := fmt.Sprintf(*CreateTestsPath+"/%v_%10x_test.go", len(packet.Data), h)
if err = os.WriteFile(filePath, formatted, 0644); err != nil {
return
}
slog.Info("Packet failed to parse", "error", packet.Err, "saved", filePath, "data", fmt.Sprintf("%q", packet.Data))
return
} }
var state = proto_base.Login var state = proto_base.Login
var direction = proto_base.ToClient
func UnpackPacket(r io.Reader) (packet proto_base.EncodeDecodeAble, err error) { func UnpackPacket(r io.Reader) (packet proto_base.EncodeDecodeAble, i proto_base.PacketInfo, err error) {
var timestamp, length uint32 var timestamp, length uint32
err = binary.Read(r, binary.BigEndian, &timestamp) err = binary.Read(r, binary.BigEndian, &timestamp)
if err != nil { if err != nil {
@@ -87,7 +94,8 @@ func UnpackPacket(r io.Reader) (packet proto_base.EncodeDecodeAble, err error) {
// indexafterid represents how many bytes we've read so far // indexafterid represents how many bytes we've read so far
indexafterid := int(b.Size()) - b.Len() indexafterid := int(b.Size()) - b.Len()
i, ok := proto.LookUpTypeByPacketInfo(proto_base.ToClient, state, packetId, int32(*ProtocolVersion)) var ok bool
i, ok = proto.LookUpTypeByPacketInfo(direction, state, packetId, int32(*ProtocolVersion))
if !ok { if !ok {
packet = &proto.UnCodablePacket{Err: proto.BadPacketIdErr, Data: packetData, Info: i} packet = &proto.UnCodablePacket{Err: proto.BadPacketIdErr, Data: packetData, Info: i}
return return
@@ -98,13 +106,42 @@ func UnpackPacket(r io.Reader) (packet proto_base.EncodeDecodeAble, err error) {
packet = &proto.UnCodablePacket{Err: err, Data: packetData[indexafterid:], Info: i} packet = &proto.UnCodablePacket{Err: err, Data: packetData[indexafterid:], Info: i}
return return
} }
if b.Len() != 0 {
packet = &proto.UnCodablePacket{Err: proto.PacketNotFullyDecodedErr, Data: packetData[indexafterid:], Info: i}
return
}
trackState(packet, i)
return
}
func trackState(packet proto_base.EncodeDecodeAble, i proto_base.PacketInfo) {
switch i.Name { switch i.Name {
case "success": case "set_protocol":
state = proto_base.Configuration v := reflect.ValueOf(packet).Elem()
case "finish_configuration": nsField := v.FieldByName("NextState")
if nsField.IsValid() && nsField.Kind() == reflect.Int32 {
switch nsField.Int() {
case 1:
state = proto_base.Status
case 2:
state = proto_base.Login
}
}
case "login_start":
if int32(*ProtocolVersion) <= 47 {
state = proto_base.Play state = proto_base.Play
} }
return case "success":
if int32(*ProtocolVersion) >= 764 {
state = proto_base.Configuration
} else {
state = proto_base.Play
}
case "finish_configuration":
state = proto_base.Play
case "login_acknowledged":
state = proto_base.Configuration
}
} }
func Run(args []string) (err error) { func Run(args []string) (err error) {
@@ -119,15 +156,27 @@ func Run(args []string) (err error) {
return fmt.Errorf("no file specified") return fmt.Errorf("no file specified")
} }
switch *StateFlag { switch *StateFlag {
case "handshake":
state = proto_base.Handshaking
case "login": case "login":
state = proto_base.Login state = proto_base.Login
case "config": case "config":
state = proto_base.Configuration state = proto_base.Configuration
case "play": case "play":
state = proto_base.Play state = proto_base.Play
case "status":
state = proto_base.Status
default: default:
return fmt.Errorf("unknown state: %s", *StateFlag) return fmt.Errorf("unknown state: %s", *StateFlag)
} }
switch *DirectionFlag {
case "clientbound", "to-client":
direction = proto_base.ToClient
case "serverbound", "to-server":
direction = proto_base.ToServer
default:
return fmt.Errorf("unknown direction: %s", *DirectionFlag)
}
f, err := os.Open(*FileFlag) f, err := os.Open(*FileFlag)
if err != nil { if err != nil {
return return
@@ -137,25 +186,30 @@ func Run(args []string) (err error) {
var totalPackets, failedPackets int var totalPackets, failedPackets int
for { for {
var packet proto_base.EncodeDecodeAble var packet proto_base.EncodeDecodeAble
packet, err = UnpackPacket(f) var info proto_base.PacketInfo
packet, info, err = UnpackPacket(f)
unCodablePacket, ok := packet.(*proto.UnCodablePacket) unCodablePacket, ok := packet.(*proto.UnCodablePacket)
if ok { if ok {
slog.Error("failed to decode packet", "error", err, "packet_name", info.Name)
if *CreateTestsPath != "" { if *CreateTestsPath != "" {
SaveUnCodeAbleAsTest(unCodablePacket) err = SaveUnCodeAbleAsTest(unCodablePacket)
if err != nil {
slog.Error("failed to save uncodable packet as test", "error", err)
}
} }
failedPackets++ failedPackets++
} }
if err != nil {
fmt.Println(err)
} else {
fmt.Printf("Packet: %#v\n", packet) fmt.Printf("Packet: %#v\n", packet)
} if errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF) {
if errors.Is(err, io.EOF) {
break break
} }
totalPackets++ totalPackets++
} }
if totalPackets > 0 {
fmt.Printf("Total packets: %d, failed packets: %d, %%failed: %.2f%%\n", totalPackets, failedPackets, float64(failedPackets)/float64(totalPackets)*100) fmt.Printf("Total packets: %d, failed packets: %d, %%failed: %.2f%%\n", totalPackets, failedPackets, float64(failedPackets)/float64(totalPackets)*100)
} else {
fmt.Printf("Total packets: %d, failed packets: %d\n", totalPackets, failedPackets)
}
return return
} }
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:2d90ee976987678593a6b1d09136bb9b4fd131390796ea6e413376a6ddc63226
size 75035693
File diff suppressed because one or more lines are too long
@@ -0,0 +1,18 @@
package failed_packets_test
import (
"bytes"
"testing"
generated "git.anygate.cloud/anygatecloud/strom/mc/proto_generated/v26_2"
)
// Error: unexpected EOF
func TestFailedPacket0F8B37C94207F7BF(t *testing.T) {
p := &generated.PlayToClientPacketUpdateTime{}
b := bytes.NewReader([]byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1c, 0x42, 0x0})
err := p.Decode(b)
if err == nil {
t.Fatalf("expected decode error but got nil")
}
}
@@ -0,0 +1,18 @@
package failed_packets_test
import (
"bytes"
"testing"
generated "git.anygate.cloud/anygatecloud/strom/mc/proto_generated/v26_2"
)
// Error: unexpected EOF
func TestFailedPacket6C9AD835672C5C51(t *testing.T) {
p := &generated.PlayToClientPacketUpdateTime{}
b := bytes.NewReader([]byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1b, 0xde, 0x0})
err := p.Decode(b)
if err == nil {
t.Fatalf("expected decode error but got nil")
}
}
@@ -0,0 +1,18 @@
package failed_packets_test
import (
"bytes"
"testing"
generated "git.anygate.cloud/anygatecloud/strom/mc/proto_generated/v26_2"
)
// Error: unexpected EOF
func TestFailedPacketA5D61E1C0F23E7FA(t *testing.T) {
p := &generated.PlayToClientPacketUpdateTime{}
b := bytes.NewReader([]byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1c, 0x56, 0x0})
err := p.Decode(b)
if err == nil {
t.Fatalf("expected decode error but got nil")
}
}
@@ -0,0 +1,18 @@
package failed_packets_test
import (
"bytes"
"testing"
generated "git.anygate.cloud/anygatecloud/strom/mc/proto_generated/v26_2"
)
// Error: unexpected EOF
func TestFailedPacketCAA58159889F7E8A(t *testing.T) {
p := &generated.PlayToClientPacketUpdateTime{}
b := bytes.NewReader([]byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1c, 0x2e, 0x0})
err := p.Decode(b)
if err == nil {
t.Fatalf("expected decode error but got nil")
}
}
@@ -0,0 +1,18 @@
package failed_packets_test
import (
"bytes"
"testing"
generated "git.anygate.cloud/anygatecloud/strom/mc/proto_generated/v26_2"
)
// Error: unexpected EOF
func TestFailedPacketD18FC4C0B2DF2972(t *testing.T) {
p := &generated.PlayToClientPacketUpdateTime{}
b := bytes.NewReader([]byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1b, 0xf2, 0x0})
err := p.Decode(b)
if err == nil {
t.Fatalf("expected decode error but got nil")
}
}
@@ -0,0 +1,18 @@
package failed_packets_test
import (
"bytes"
"testing"
generated "git.anygate.cloud/anygatecloud/strom/mc/proto_generated/v26_2"
)
// Error: unexpected EOF
func TestFailedPacketE81E82016DB04F28(t *testing.T) {
p := &generated.PlayToClientPacketUpdateTime{}
b := bytes.NewReader([]byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1c, 0x6, 0x0})
err := p.Decode(b)
if err == nil {
t.Fatalf("expected decode error but got nil")
}
}
@@ -0,0 +1,18 @@
package failed_packets_test
import (
"bytes"
"testing"
generated "git.anygate.cloud/anygatecloud/strom/mc/proto_generated/v26_2"
)
// Error: unexpected EOF
func TestFailedPacketFE935CF2E584C649(t *testing.T) {
p := &generated.PlayToClientPacketUpdateTime{}
b := bytes.NewReader([]byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1c, 0x1a, 0x0})
err := p.Decode(b)
if err == nil {
t.Fatalf("expected decode error but got nil")
}
}
-1
View File
@@ -1 +0,0 @@
[{"realTimestamp":9845034,"value":{"position":{"x":-31.946051078575692,"y":93.0,"z":29.523387113769626,"yaw":-627.44653,"pitch":37.573395,"roll":0.0}}},{"realTimestamp":9845138,"value":{"position":{"x":-31.946051078575692,"y":93.0,"z":29.523387113769626,"yaw":-627.44653,"pitch":37.573395,"roll":0.0}}},{"realTimestamp":9844902,"value":{"position":{"x":-31.946051078575692,"y":93.0,"z":29.523387113769626,"yaw":-627.44653,"pitch":37.573395,"roll":0.0}}},{"realTimestamp":9908896,"value":{"position":{"x":-1.6053634059630886,"y":91.672484820985,"z":-10.594536888795163,"yaw":-1151.6353,"pitch":16.142092,"roll":0.0}}},{"realTimestamp":9844750,"value":{"position":{"x":-31.946051078575692,"y":93.0,"z":29.523387113769626,"yaw":-627.44653,"pitch":37.573395,"roll":0.0}}},{"realTimestamp":9853735,"value":{"position":{"x":-31.05268144454021,"y":88.0,"z":-25.371850030366133,"yaw":-624.37897,"pitch":16.105839,"roll":0.0}}},{"realTimestamp":9845690,"value":{"position":{"x":-31.946051078575692,"y":93.0,"z":29.523387113769626,"yaw":-627.44653,"pitch":37.573395,"roll":0.0}}},{"realTimestamp":9938194,"value":{"position":{"x":39.88206777447001,"y":103.87657142499006,"z":58.09267383764874,"yaw":-1781.1023,"pitch":37.354122,"roll":0.0}}},{"realTimestamp":9846991,"value":{"position":{"x":-31.946051078575692,"y":93.0,"z":29.523387113769626,"yaw":-628.46875,"pitch":36.04,"roll":0.0}}},{"realTimestamp":9908638,"value":{"position":{"x":-1.5870303355717672,"y":91.672484820985,"z":-11.359450846926011,"yaw":-1142.6902,"pitch":20.997843,"roll":0.0}}},{"realTimestamp":9845490,"value":{"position":{"x":-31.946051078575692,"y":93.0,"z":29.523387113769626,"yaw":-627.44653,"pitch":37.573395,"roll":0.0}}},{"realTimestamp":9845990,"value":{"position":{"x":-31.946051078575692,"y":93.0,"z":29.523387113769626,"yaw":-627.44653,"pitch":37.573395,"roll":0.0}}}]
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d972b9b5f9d962199fe262eccef32a4b94504283fae97f9963702e7ab1550129
size 437123
-1
View File
@@ -1 +0,0 @@
{"singleplayer":false,"serverName":"protect-test-server.anygate.cloud","customServerName":"proxied test server","duration":3368,"date":1776531741320,"mcversion":"1.21.11","fileFormat":"MCPR","fileFormatVersion":14,"protocol":774,"generator":"ReplayMod v1.21.11-2.6.26","selfId":-1,"players":[]}
-1
View File
@@ -1 +0,0 @@
{"requiredMods":[{"modID":"viafabricplus","modName":"ViaFabricPlus","modVersion":"4.4.4"}]}
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:cef58ec0e3d1d25e34c2effc5c09241ee4c1b39c085ed9e11b7097dfd5c2d033
size 3429878
@@ -1 +0,0 @@
234002168
-1
View File
@@ -1 +0,0 @@
{"singleplayer":false,"serverName":"protect-beta.anygate.cloud","customServerName":"protect-beta.anygate.cloud","duration":10198341,"date":1776535673280,"mcversion":"1.21.11","fileFormat":"MCPR","fileFormatVersion":14,"protocol":774,"generator":"ReplayMod v1.21.11-2.6.26","selfId":-1,"players":["fe2d6f64-7ab0-3373-a526-af097abbcb39"]}
-1
View File
@@ -1 +0,0 @@
{"requiredMods":[{"modID":"viafabricplus","modName":"ViaFabricPlus","modVersion":"4.4.4"}]}
-3
View File
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:be007aa681f7959708e89e39e05390e228753291823a18274068d310a50aa78b
size 526306397
-1
View File
@@ -1 +0,0 @@
270566013
@@ -0,0 +1,18 @@
package failed_packets_test
import (
"bytes"
"testing"
generated "git.anygate.cloud/anygatecloud/strom/mc/proto_generated/v26_2"
)
// Decode regression: recipe_book_add (26.2) - SlotDisplay type fix
func TestRegressionPacket25D845B5D0023D22(t *testing.T) {
p := &generated.PlayToClientPacketRecipeBookAdd{}
b := bytes.NewReader([]byte{0x1, 0xbe, 0x2, 0x1, 0x2, 0x2, 0x4, 0x6, 0x10, 0x6d, 0x69, 0x6e, 0x65, 0x63, 0x72, 0x61, 0x66, 0x74, 0x3a, 0x70, 0x6c, 0x61, 0x6e, 0x6b, 0x73, 0x6, 0x10, 0x6d, 0x69, 0x6e, 0x65, 0x63, 0x72, 0x61, 0x66, 0x74, 0x3a, 0x70, 0x6c, 0x61, 0x6e, 0x6b, 0x73, 0x6, 0x10, 0x6d, 0x69, 0x6e, 0x65, 0x63, 0x72, 0x61, 0x66, 0x74, 0x3a, 0x70, 0x6c, 0x61, 0x6e, 0x6b, 0x73, 0x6, 0x10, 0x6d, 0x69, 0x6e, 0x65, 0x63, 0x72, 0x61, 0x66, 0x74, 0x3a, 0x70, 0x6c, 0x61, 0x6e, 0x6b, 0x73, 0x5, 0xe8, 0x2, 0x1, 0x0, 0x0, 0x4, 0xe8, 0x2, 0x0, 0x3, 0x1, 0x4, 0x0, 0x10, 0x6d, 0x69, 0x6e, 0x65, 0x63, 0x72, 0x61, 0x66, 0x74, 0x3a, 0x70, 0x6c, 0x61, 0x6e, 0x6b, 0x73, 0x0, 0x10, 0x6d, 0x69, 0x6e, 0x65, 0x63, 0x72, 0x61, 0x66, 0x74, 0x3a, 0x70, 0x6c, 0x61, 0x6e, 0x6b, 0x73, 0x0, 0x10, 0x6d, 0x69, 0x6e, 0x65, 0x63, 0x72, 0x61, 0x66, 0x74, 0x3a, 0x70, 0x6c, 0x61, 0x6e, 0x6b, 0x73, 0x0, 0x10, 0x6d, 0x69, 0x6e, 0x65, 0x63, 0x72, 0x61, 0x66, 0x74, 0x3a, 0x70, 0x6c, 0x61, 0x6e, 0x6b, 0x73, 0x2, 0x1})
err := p.Decode(b)
if err != nil {
t.Fatalf("regression: decode failed: %v", err)
}
}
@@ -0,0 +1,17 @@
package failed_packets_test
import (
"bytes"
"testing"
generated "git.anygate.cloud/anygatecloud/strom/mc/proto_generated/v26_2"
)
// Decode regression: declare_commands (26.2) - bitfield + extraNodeData fix
func TestRegressionPacket78C731784E6E834A(t *testing.T) {
p := &generated.PlayToClientPacketDeclareCommands{}
b := bytes.NewReader([]byte{0x1a, 0x0, 0xa, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0x1, 0x1, 0xb, 0x2, 0x6d, 0x65, 0x5, 0x1, 0xc, 0x4, 0x68, 0x65, 0x6c, 0x70, 0x5, 0x1, 0xd, 0x4, 0x6c, 0x69, 0x73, 0x74, 0x1, 0x1, 0xe, 0x3, 0x6d, 0x73, 0x67, 0x9, 0x0, 0x4, 0x4, 0x74, 0x65, 0x6c, 0x6c, 0x9, 0x0, 0x4, 0x1, 0x77, 0x1, 0x2, 0xf, 0x10, 0x6, 0x72, 0x61, 0x6e, 0x64, 0x6f, 0x6d, 0x1, 0x1, 0x11, 0x7, 0x74, 0x65, 0x61, 0x6d, 0x6d, 0x73, 0x67, 0x9, 0x0, 0x8, 0x2, 0x74, 0x6d, 0x1, 0x1, 0x12, 0x7, 0x74, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x6, 0x0, 0x6, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x14, 0x6, 0x0, 0x7, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5, 0x2, 0x5, 0x0, 0x5, 0x75, 0x75, 0x69, 0x64, 0x73, 0x2, 0x1, 0x13, 0x7, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x6, 0x2, 0x1, 0x1, 0x14, 0x5, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x1, 0x1, 0x15, 0x4, 0x72, 0x6f, 0x6c, 0x6c, 0x6, 0x0, 0x7, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x14, 0x16, 0x2, 0x16, 0x17, 0x9, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x18, 0x14, 0x6d, 0x69, 0x6e, 0x65, 0x63, 0x72, 0x61, 0x66, 0x74, 0x3a, 0x61, 0x73, 0x6b, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x6, 0x0, 0x7, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x14, 0x6, 0x0, 0x5, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x27, 0x6, 0x0, 0x5, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x27, 0x1, 0x1, 0x18, 0x3, 0x61, 0x64, 0x64, 0x1, 0x1, 0x19, 0x3, 0x73, 0x65, 0x74, 0x6, 0x0, 0x5, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3, 0x0, 0x6, 0x0, 0x5, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3, 0x0, 0x0})
err := p.Decode(b)
if err != nil {
t.Fatalf("regression: decode failed: %v", err)
}
}
@@ -0,0 +1,18 @@
package failed_packets_test
import (
"bytes"
"testing"
generated "git.anygate.cloud/anygatecloud/strom/mc/proto_generated/v1_8"
)
// Decode regression: spawn_entity_living (1.8) - entityMetadata mergeCompareTo
func TestRegressionPacket2B76AF8B937B1E8F(t *testing.T) {
p := &generated.PlayToClientPacketSpawnEntityLiving{}
b := bytes.NewReader([]byte{0x4c, 0x65, 0xff, 0xff, 0xed, 0x66, 0x0, 0x0, 0x7, 0xe0, 0x0, 0x0, 0x29, 0xe6, 0x91, 0x0, 0x91, 0x0, 0x0, 0xfd, 0x8d, 0x0, 0x0, 0x0, 0x0, 0x21, 0x1, 0x2c, 0x82, 0x0, 0x12, 0x4, 0x3, 0x0, 0x4, 0x0, 0x66, 0x41, 0x20, 0x0, 0x0, 0x47, 0x0, 0x0, 0x0, 0x0, 0x8, 0x0, 0x9, 0x0, 0xc, 0x0, 0xf, 0x0, 0x7f})
err := p.Decode(b)
if err != nil {
t.Fatalf("regression: decode failed: %v", err)
}
}
@@ -0,0 +1,18 @@
package failed_packets_test
import (
"bytes"
"testing"
generated "git.anygate.cloud/anygatecloud/strom/mc/proto_generated/v1_8"
)
// Decode regression: entity_equipment (1.8) - nbt TAG_End fix
func TestRegressionPacket0C20C77EA68496BD(t *testing.T) {
p := &generated.PlayToClientPacketEntityEquipment{}
b := bytes.NewReader([]byte{0x89, 0x16, 0x0, 0x0, 0x1, 0x5, 0x1, 0x0, 0x0, 0x0})
err := p.Decode(b)
if err != nil {
t.Fatalf("regression: decode failed: %v", err)
}
}
@@ -0,0 +1,18 @@
package failed_packets_test
import (
"bytes"
"testing"
generated "git.anygate.cloud/anygatecloud/strom/mc/proto_generated/v1_8"
)
// Decode regression: entity_metadata (1.8) - entityMetadataLoop mergeCompareTo
func TestRegressionPacketDF2DF88561529208(t *testing.T) {
p := &generated.PlayToClientPacketEntityMetadata{}
b := bytes.NewReader([]byte{0x69, 0x0, 0x0, 0x21, 0x1, 0x2c, 0x82, 0x0, 0x3, 0x0, 0x4, 0x0, 0x66, 0x40, 0x80, 0x0, 0x0, 0x47, 0x0, 0x0, 0x0, 0x0, 0x8, 0x0, 0x9, 0x0, 0xc, 0x0, 0xf, 0x0, 0x7f})
err := p.Decode(b)
if err != nil {
t.Fatalf("regression: decode failed: %v", err)
}
}
@@ -0,0 +1,18 @@
package failed_packets_test
import (
"bytes"
"testing"
generated "git.anygate.cloud/anygatecloud/strom/mc/proto_generated/v26_2"
)
// Decode regression: entity_equipment (26.2) - topBitSetTerminatedArray
func TestRegressionPacket1D0089BBE86A1648(t *testing.T) {
p := &generated.PlayToClientPacketEntityEquipment{}
b := bytes.NewReader([]byte{0x81, 0x2, 0x0, 0x1, 0x9a, 0x7, 0x0, 0x0})
err := p.Decode(b)
if err != nil {
t.Fatalf("regression: decode failed: %v", err)
}
}
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:0da58f5cdd65ef241df8c61f9e633af7317faa77034183d4fb648b039c1e14ed
size 842
-1
View File
@@ -1 +0,0 @@
{"singleplayer":false,"serverName":"protect-test-server.anygate.cloud","customServerName":"proxied test server","duration":476,"date":1776803431635,"mcversion":"1.21.11","fileFormat":"MCPR","fileFormatVersion":14,"protocol":774,"generator":"ReplayMod v1.21.11-2.6.26","selfId":-1,"players":[]}
-1
View File
@@ -1 +0,0 @@
{"requiredMods":[{"modID":"viafabricplus","modName":"ViaFabricPlus","modVersion":"4.4.4"}]}
-3
View File
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:52fa049f97918289e018fa4c3bf5ff789d4f99f44f590ad875ce1f65e5fa9563
size 37
@@ -1 +0,0 @@
1105715802
+256
View File
@@ -0,0 +1,256 @@
package raw_to_tmcpr
import (
"bytes"
"compress/zlib"
"errors"
"flag"
"fmt"
"io"
"os"
"time"
"git.anygate.cloud/anygatecloud/strom/mc/proto/replay"
"git.anygate.cloud/anygatecloud/strom/mc/proto_base"
)
var Cmd = flag.NewFlagSet("raw-to-tmcpr", flag.ContinueOnError)
var ClientboundIn = Cmd.String("clientbound", "", "raw tcpflow file for clientbound (server->client) direction")
var ServerboundIn = Cmd.String("serverbound", "", "raw tcpflow file for serverbound (client->server) direction")
var ClientboundOut = Cmd.String("clientbound-out", "clientbound.tmcpr", "output TMCPR file for clientbound")
var ServerboundOut = Cmd.String("serverbound-out", "serverbound.tmcpr", "output TMCPR file for serverbound")
var ClientboundState = Cmd.String("clientbound-state", "login", "initial state for clientbound: handshake, login, config, status, play")
var ServerboundState = Cmd.String("serverbound-state", "handshake", "initial state for serverbound: handshake, login, config, status, play")
var UseConfig = Cmd.Bool("config", false, "use Configuration state transitions (for protocol >= 764, Minecraft 1.20.2+)")
type captureReader struct {
r io.Reader
out *replay.Writer
state proto_base.State
threshold int32
startTime time.Time
haveStartTime bool
direction proto_base.Direction
pendingThreshold int32
useConfig bool
}
func (cr *captureReader) setStartTime() {
if !cr.haveStartTime {
cr.startTime = time.Now()
cr.haveStartTime = true
}
}
func (cr *captureReader) timestamp() uint32 {
return uint32(time.Since(cr.startTime).Milliseconds())
}
func readPacketBytes(r io.Reader, threshold int32) ([]byte, error) {
packetLen, err := proto_base.DecodeVarInt(r)
if err != nil {
return nil, fmt.Errorf("read packet length: %w", err)
}
rawBytes := make([]byte, packetLen)
_, err = io.ReadFull(r, rawBytes)
if err != nil {
return nil, fmt.Errorf("read packet data: %w", err)
}
if threshold >= 0 {
buf := bytes.NewReader(rawBytes)
uncompressedLen, err := proto_base.DecodeVarInt(buf)
if err != nil {
return nil, fmt.Errorf("read uncompressed length: %w", err)
}
if uncompressedLen == 0 {
return io.ReadAll(buf)
} else {
zReader, err := zlib.NewReader(buf)
if err != nil {
return nil, fmt.Errorf("zlib reader: %w", err)
}
defer zReader.Close()
decompressed, err := io.ReadAll(zReader)
if err != nil {
return nil, fmt.Errorf("decompress: %w", err)
}
return decompressed, nil
}
}
return rawBytes, nil
}
func (cr *captureReader) trackState(packetBytes []byte) {
buf := bytes.NewReader(packetBytes)
packetId, err := proto_base.DecodeVarInt(buf)
if err != nil {
return
}
switch cr.state {
case proto_base.Handshaking:
if cr.direction == proto_base.ToServer && packetId == 0 {
_, err = proto_base.DecodeVarInt(buf) // protocol version
if err != nil {
return
}
_, err = proto_base.DecodeString(buf) // server address
if err != nil {
return
}
portBytes := make([]byte, 2)
_, err = io.ReadFull(buf, portBytes)
if err != nil {
return
}
nextState, err := proto_base.DecodeVarInt(buf)
if err != nil {
return
}
if nextState == 2 {
cr.state = proto_base.Login
} else if nextState == 1 {
cr.state = proto_base.Status
}
}
case proto_base.Login:
if cr.direction == proto_base.ToClient {
if packetId == 0x03 {
threshold, err := proto_base.DecodeVarInt(buf)
if err == nil {
cr.threshold = threshold
}
} else if packetId == 0x02 {
if cr.useConfig {
cr.state = proto_base.Configuration
} else {
cr.state = proto_base.Play
}
}
} else if cr.direction == proto_base.ToServer && packetId == 0 {
if cr.pendingThreshold >= 0 {
cr.threshold = cr.pendingThreshold
}
}
case proto_base.Configuration:
if cr.direction == proto_base.ToClient && packetId == 0x02 {
cr.state = proto_base.Play
}
case proto_base.Status:
case proto_base.Play:
}
}
func (cr *captureReader) process() (count int, err error) {
for {
packetBytes, err := readPacketBytes(cr.r, cr.threshold)
if err != nil {
if errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF) {
return count, nil
}
return count, err
}
cr.setStartTime()
cr.trackState(packetBytes)
err = cr.out.WriteRaw(packetBytes, cr.timestamp())
if err != nil {
return count, fmt.Errorf("write TMCPR: %w", err)
}
count++
}
}
func Run(args []string) (err error) {
err = Cmd.Parse(args)
if err != nil {
return
}
if *ClientboundIn == "" && *ServerboundIn == "" {
return fmt.Errorf("at least one of -clientbound or -serverbound must be specified")
}
parseState := func(name string) (proto_base.State, error) {
switch name {
case "handshake":
return proto_base.Handshaking, nil
case "login":
return proto_base.Login, nil
case "config":
return proto_base.Configuration, nil
case "play":
return proto_base.Play, nil
case "status":
return proto_base.Status, nil
default:
return 0, fmt.Errorf("unknown state: %s (valid: handshake, login, config, status, play)", name)
}
}
var compressionThreshold int32 = -1
if *ClientboundIn != "" {
state, err := parseState(*ClientboundState)
if err != nil {
return err
}
compressionThreshold, err = processFile(*ClientboundIn, *ClientboundOut, state, proto_base.ToClient, -1, *UseConfig)
if err != nil {
return fmt.Errorf("clientbound: %w", err)
}
}
if *ServerboundIn != "" {
state, err := parseState(*ServerboundState)
if err != nil {
return err
}
_, err = processFile(*ServerboundIn, *ServerboundOut, state, proto_base.ToServer, compressionThreshold, *UseConfig)
if err != nil {
return fmt.Errorf("serverbound: %w", err)
}
}
return
}
func processFile(inPath, outPath string, initial proto_base.State, dir proto_base.Direction, initialThreshold int32, useConfig bool) (finalThreshold int32, err error) {
in, err := os.Open(inPath)
if err != nil {
return initialThreshold, fmt.Errorf("open input: %w", err)
}
defer in.Close()
out, err := os.Create(outPath)
if err != nil {
return initialThreshold, fmt.Errorf("create output: %w", err)
}
defer out.Close()
cr := &captureReader{
r: in,
out: replay.NewWriter(out),
state: initial,
threshold: -1,
direction: dir,
pendingThreshold: initialThreshold,
useConfig: useConfig,
}
if dir == proto_base.ToClient {
cr.threshold = initialThreshold
}
count, err := cr.process()
if err != nil {
return cr.threshold, err
}
fmt.Printf("%s: %d packets written to %s (threshold=%d)\n", inPath, count, outPath, cr.threshold)
return cr.threshold, nil
}
+97
View File
@@ -0,0 +1,97 @@
package serve_tmcpr
import (
"errors"
"flag"
"io"
"log/slog"
"math/rand/v2"
"os"
"time"
"git.anygate.cloud/anygatecloud/strom/mc/event"
"git.anygate.cloud/anygatecloud/strom/mc/proto"
"git.anygate.cloud/anygatecloud/strom/mc/proto/replay"
"git.anygate.cloud/anygatecloud/strom/mc/proto_generated/v1_21_8"
"git.anygate.cloud/anygatecloud/strom/mc/server"
)
type Server struct {
*proto.Conn
*event.Timer
}
var (
cmd = flag.NewFlagSet("serve-tmcpr", flag.ContinueOnError)
tmcprFile = cmd.String("file", "/tmp/world.tmcpr", "Path to .tmcpr replay file")
bindAddr = cmd.String("bind", ":25566", "Address to listen on")
)
func Limbo(c *proto.Conn, tmcprPath string) (err error) {
s := &Server{Conn: c}
_, err = server.ServeLogin(s.Conn, server.WithoutOnlineMode())
if err != nil {
return
}
err = server.ServeConfig(s.Conn)
if err != nil {
return
}
f, err := os.Open(tmcprPath)
if err != nil {
return
}
defer f.Close()
r := replay.NewReader(f)
r.Version = c.ProtocolVersion
start := time.Now()
for {
var packetData []byte
var timestamp uint32
packetData, timestamp, _, err = r.ReadRaw()
if err != nil {
if errors.Is(err, io.EOF) {
break
}
return err
}
time.Sleep(time.Duration(timestamp)*time.Millisecond - time.Since(start))
err = s.SendRaw(packetData)
if err != nil {
return err
}
}
s.Timer = &event.Timer{}
s.Timer.Every(time.Second*15, s.SendKeepAlive)
s.Timer.Start(c.Loop)
return s.StartConn()
}
func (s *Server) SendKeepAlive() error {
return s.Send(&v1_21_8.PlayToClientPacketKeepAlive{KeepAliveId: rand.Int64()})
}
func Run(args []string) error {
slog.SetLogLoggerLevel(slog.LevelDebug)
err := cmd.Parse(args)
if err != nil {
return err
}
if *tmcprFile == "" {
slog.Error("no file specified")
return nil
}
return server.StartServerWithOnConn(*bindAddr, func(c *proto.Conn) error {
return Limbo(c, *tmcprFile)
})
}
@@ -1,4 +1,4 @@
package main package serve_world
import ( import (
"bytes" "bytes"
@@ -8,14 +8,14 @@ import (
"os" "os"
"time" "time"
"github.com/admin-else/strom/event" "git.anygate.cloud/anygatecloud/strom/mc/event"
"github.com/admin-else/strom/level" "git.anygate.cloud/anygatecloud/strom/mc/level"
"github.com/admin-else/strom/level/anvil" "git.anygate.cloud/anygatecloud/strom/mc/level/anvil"
"github.com/admin-else/strom/nbt" "git.anygate.cloud/anygatecloud/strom/mc/nbt"
"github.com/admin-else/strom/proto" "git.anygate.cloud/anygatecloud/strom/mc/proto"
"github.com/admin-else/strom/proto_generated/v1_21_8" "git.anygate.cloud/anygatecloud/strom/mc/proto_generated/v1_21_8"
"github.com/admin-else/strom/server" "git.anygate.cloud/anygatecloud/strom/mc/server"
"github.com/admin-else/strom/util" "git.anygate.cloud/anygatecloud/strom/mc/util"
) )
type Server struct { type Server struct {
@@ -28,7 +28,7 @@ type Server struct {
var FullLight [][]uint8 var FullLight [][]uint8
func init() { func init() {
light := make([]uint8, 2048) // 4096 values 2 per uint8 light := make([]uint8, 2048)
for i := range light { for i := range light {
light[i] = 0xFF light[i] = 0xFF
} }
@@ -52,7 +52,7 @@ func GetChunkFromMca(x, z int32, version string) (b []byte, err error) {
z += anvil.ChunksWidthRegionFile z += anvil.ChunksWidthRegionFile
} }
f, err := os.Open(fmt.Sprintf("./level/anvil/testdata/securechattestworld/region/r.%v.%v.mca", xmca, zmca)) f, err := os.Open(fmt.Sprintf("./.devres/anvil_testdata/securechattestworld/region/r.%v.%v.mca", xmca, zmca))
if err != nil { if err != nil {
return return
} }
@@ -95,13 +95,11 @@ func Limbo(c *proto.Conn) (err error) {
s := &Server{ s := &Server{
Conn: c, Conn: c,
} }
//s.DebugPrintPackets = []string{""}
_, err = server.ServeLogin(s.Conn, server.WithoutOnlineMode()) _, err = server.ServeLogin(s.Conn, server.WithoutOnlineMode())
if err != nil { if err != nil {
return return
} }
//goland:noinspection GoResourceLeak captures the conn object which should NOT be closed
err = server.ServeConfig(s.Conn) err = server.ServeConfig(s.Conn)
if err != nil { if err != nil {
return return
@@ -176,10 +174,7 @@ func (s *Server) SendKeepAlive() (err error) {
return return
} }
func main() { func Run(args []string) error {
slog.SetLogLoggerLevel(slog.LevelDebug) slog.SetLogLoggerLevel(slog.LevelDebug)
err := server.StartServerWithOnConn(":25566", Limbo) return server.StartServerWithOnConn(":25566", Limbo)
if err != nil {
panic(err)
}
} }
+7 -7
View File
@@ -6,11 +6,11 @@ import (
"log/slog" "log/slog"
"time" "time"
"github.com/admin-else/strom/client" "git.anygate.cloud/anygatecloud/strom/mc/client"
"github.com/admin-else/strom/event" "git.anygate.cloud/anygatecloud/strom/mc/event"
"github.com/admin-else/strom/proto" "git.anygate.cloud/anygatecloud/strom/mc/proto"
"github.com/admin-else/strom/proto_base" "git.anygate.cloud/anygatecloud/strom/mc/proto_base"
"github.com/admin-else/strom/proto_generated/v1_21_8" "git.anygate.cloud/anygatecloud/strom/mc/proto_generated/v1_21_8"
) )
var cmd = flag.NewFlagSet("status", flag.ContinueOnError) var cmd = flag.NewFlagSet("status", flag.ContinueOnError)
@@ -46,7 +46,7 @@ func (s *StatusClient) OnPong(p *v1_21_8.StatusToClientPacketPing) (err error) {
// So ignore the resource leak warning. And maybe attach a warn ignore comment. // So ignore the resource leak warning. And maybe attach a warn ignore comment.
// It does not resolve SRV records. // It does not resolve SRV records.
func StatusRaw(addr string) (s *StatusClient, err error) { func StatusRaw(addr string) (s *StatusClient, err error) {
c, err := client.ConnectVersionLess(addr) c, err := client.ConnectVersionLess(nil, addr)
if err != nil { if err != nil {
return return
} }
@@ -82,7 +82,7 @@ func Run(args []string) (err error) {
return return
} }
if *srvFlag { if *srvFlag {
*addrFlag, err = client.DoDnsSimple(*addrFlag) *addrFlag, _, _, err = client.DoDNSChecked(nil, *addrFlag, nil)
if err != nil { if err != nil {
return return
} }
+1 -1
View File
@@ -1,4 +1,4 @@
module github.com/admin-else/strom module git.anygate.cloud/anygatecloud/strom
go 1.26.2 go 1.26.2
-2
View File
@@ -2,7 +2,5 @@ github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9L
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large Load Diff
Binary file not shown.
File diff suppressed because it is too large Load Diff
-3
View File
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:14cd0df55488907baf26a6e8da9548d614d044a15259a526bb1f0be44a712764
size 10113024
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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
}
@@ -1,17 +0,0 @@
{
"minecraft:recipes/decorations/crafting_table": {
"criteria": {
"unlock_right_away": "2026-04-23 22:08:36 +0200"
},
"done": true
},
"minecraft:adventure/adventuring_time": {
"criteria": {
"minecraft:taiga": "2026-04-23 22:10:55 +0200",
"minecraft:grove": "2026-04-23 22:09:03 +0200",
"minecraft:stony_shore": "2026-04-23 22:08:37 +0200"
},
"done": false
},
"DataVersion": 4671
}
@@ -1,17 +0,0 @@
{
"minecraft:recipes/decorations/crafting_table": {
"criteria": {
"unlock_right_away": "2026-05-08 20:07:02 +0200"
},
"done": true
},
"minecraft:adventure/adventuring_time": {
"criteria": {
"minecraft:forest": "2026-05-08 20:07:06 +0200",
"minecraft:taiga": "2026-05-08 20:07:05 +0200",
"minecraft:grove": "2026-05-08 20:07:03 +0200"
},
"done": false
},
"DataVersion": 4671
}
@@ -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
}
@@ -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
}
@@ -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
}
@@ -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
}
@@ -1 +0,0 @@
https://github.com/cabaletta/baritone
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

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