Merge remote-tracking branch 'origin/master'
This commit is contained in:
+4
-4
@@ -19,8 +19,8 @@ import (
|
||||
"github.com/admin-else/strom/cmd/strom/packet_inspector"
|
||||
"github.com/admin-else/strom/cmd/strom/print_nbt"
|
||||
"github.com/admin-else/strom/cmd/strom/print_replay"
|
||||
"github.com/admin-else/strom/cmd/strom/raw_capture"
|
||||
"github.com/admin-else/strom/cmd/strom/registry_data_hunter"
|
||||
// "github.com/admin-else/strom/cmd/strom/raw_capture" // removed: use tcpflow instead for raw TCP capture
|
||||
"github.com/admin-else/strom/cmd/strom/raw_to_tmcpr"
|
||||
"github.com/admin-else/strom/cmd/strom/serve_tmcpr"
|
||||
"github.com/admin-else/strom/cmd/strom/serve_world"
|
||||
"github.com/admin-else/strom/cmd/strom/status"
|
||||
@@ -61,8 +61,8 @@ var subcommands = map[string]func(args []string) error{
|
||||
"extract-mca": extract_mca.Run,
|
||||
"data": data.Run,
|
||||
"print-replay": print_replay.Run,
|
||||
"raw-capture": raw_capture.Run,
|
||||
"registry-data-hunter": registry_data_hunter.Run,
|
||||
// "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,
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/admin-else/strom/mc/level"
|
||||
"github.com/admin-else/strom/mc/level/anvil"
|
||||
@@ -51,7 +52,7 @@ func GetChunkFromMca(x, z int32, version string) (b []byte, err error) {
|
||||
z += anvil.ChunksWidthRegionFile
|
||||
}
|
||||
|
||||
f, err := os.Open(fmt.Sprintf("./.devres/anvil_testdata/securechattestworld/region/r.%v.%v.mca", xmca, zmca))
|
||||
f, err := os.Open(filepath.Join(*worldPath, "region", fmt.Sprintf("r.%v.%v.mca", xmca, zmca)))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
+47
-17
@@ -16,6 +16,7 @@ import (
|
||||
"github.com/admin-else/strom/mc/event"
|
||||
"github.com/admin-else/strom/mc/proto"
|
||||
"github.com/admin-else/strom/mc/proto_generated/v1_21_11"
|
||||
"github.com/admin-else/strom/mc/proto_generated/v1_8"
|
||||
)
|
||||
|
||||
func stror(a, b string) string {
|
||||
@@ -29,12 +30,14 @@ 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
|
||||
chat *chat.Module
|
||||
world *world.Module
|
||||
legacy bool
|
||||
}
|
||||
|
||||
func (m *Messenger) OnChat(e *v1_21_11.PlayToClientPacketPlayerChat) (err error) {
|
||||
@@ -52,15 +55,27 @@ func (m *Messenger) OnChatSystem(e *v1_21_11.PlayToClientPacketSystemChat) (err
|
||||
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.chat.SendMessage(strings.TrimSpace(strings.TrimPrefix(input, ">say")))
|
||||
return m.sendChat(strings.TrimSpace(strings.TrimPrefix(input, ">say")))
|
||||
}
|
||||
if strings.HasPrefix(input, ">getblock ") {
|
||||
if !m.legacy && strings.HasPrefix(input, ">getblock ") {
|
||||
return m.handleGetBlock(strings.TrimSpace(strings.TrimPrefix(input, ">getblock")))
|
||||
}
|
||||
return m.chat.SendMessage(input)
|
||||
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) {
|
||||
@@ -105,30 +120,45 @@ func Run(args []string) (err error) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
c, err := client.Login(*connectToFlag, acc)
|
||||
c, err := client.Login(*connectToFlag, acc, client.WithVersion(*versionFlag))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
chatMod, err := chat.Start(c, acc)
|
||||
if err != nil {
|
||||
return
|
||||
isLegacy := c.ProtocolVersion < 764
|
||||
|
||||
var chatMod *chat.Module
|
||||
if !isLegacy {
|
||||
chatMod, err = chat.Start(c, acc)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Shareable world storage. Other bots can subscribe to the same World.
|
||||
w := world.NewWorld(c.Version, -64, 384)
|
||||
|
||||
m := &Messenger{
|
||||
Conn: c,
|
||||
chat: chatMod,
|
||||
world: world.Start(c, w),
|
||||
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)
|
||||
m.RegisterUntilLatest(m.OnChat, m.OnChatUnsigned, m.OnChatSystem)
|
||||
keepalive.Start(m.Conn)
|
||||
|
||||
if isLegacy {
|
||||
m.Register(m.OnChatLegacy)
|
||||
} else {
|
||||
m.RegisterUntilLatest(m.OnChat, m.OnChatUnsigned, m.OnChatSystem)
|
||||
}
|
||||
|
||||
if !isLegacy {
|
||||
keepalive.Start(m.Conn)
|
||||
}
|
||||
|
||||
return m.StartConn()
|
||||
}
|
||||
|
||||
@@ -4,15 +4,15 @@ import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
generated "github.com/admin-else/strom/mc/proto_generated/%v"
|
||||
generated "github.com/admin-else/strom/mc/proto_generated/%v"
|
||||
)
|
||||
|
||||
// Error: %v
|
||||
func TestFailedPacket%10X(t *testing.T) {
|
||||
p := &generated.%v{}
|
||||
b := bytes.NewBuffer(%#v)
|
||||
p := &generated.%v{}
|
||||
b := bytes.NewReader(%#v)
|
||||
err := p.Decode(b)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
if err == nil {
|
||||
t.Fatalf("expected decode error but got nil")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@ import (
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/admin-else/strom/mc/data"
|
||||
"github.com/admin-else/strom/mc/proto"
|
||||
"github.com/admin-else/strom/mc/proto_base"
|
||||
)
|
||||
@@ -25,7 +24,7 @@ var (
|
||||
FileFlag = cmd.String("file", "", "The tmcpr file to print")
|
||||
ProtocolVersion = cmd.Int("protocol", -1, "The protocol version")
|
||||
CreateTestsPath = cmd.String("create-tests", "", "")
|
||||
StateFlag = cmd.String("state", "login", "Initial state: login, config, play")
|
||||
StateFlag = cmd.String("state", "login", "Initial state: handshake, login, config, status, play")
|
||||
DirectionFlag = cmd.String("direction", "clientbound", "Packet direction: clientbound (ToClient) or serverbound (ToServer)")
|
||||
)
|
||||
|
||||
@@ -46,11 +45,8 @@ func SaveUnCodeAbleAsTest(packet *proto.UnCodablePacket) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
ret, err := data.LookUpVersionByProtocolVersion(packet.Info.ProtocolVersion)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
goPackage := "v" + strings.ReplaceAll(ret.MinecraftVersion, ".", "_")
|
||||
typeStr := reflect.TypeOf(packet.Info.Type).String()
|
||||
goPackage := typeStr[1:strings.LastIndex(typeStr, ".")]
|
||||
typeName := reflect.TypeOf(packet.Info.Type).Elem().Name()
|
||||
|
||||
src := fmt.Sprintf(TestSrcF, goPackage, packet.Err, h, typeName, packet.Data)
|
||||
@@ -106,22 +102,6 @@ func UnpackPacket(r io.Reader) (packet proto_base.EncodeDecodeAble, i proto_base
|
||||
packet = &proto.UnCodablePacket{Err: proto.BadPacketIdErr, Data: packetData, Info: i}
|
||||
return
|
||||
}
|
||||
switch direction {
|
||||
case proto_base.ToClient:
|
||||
switch i.Name {
|
||||
case "success":
|
||||
state = proto_base.Configuration
|
||||
case "finish_configuration":
|
||||
state = proto_base.Play
|
||||
}
|
||||
case proto_base.ToServer:
|
||||
switch i.Name {
|
||||
case "login_acknowledged":
|
||||
state = proto_base.Configuration
|
||||
case "finish_configuration":
|
||||
state = proto_base.Play
|
||||
}
|
||||
}
|
||||
packet = reflect.New(reflect.TypeOf(i.Type).Elem()).Interface().(proto_base.EncodeDecodeAble)
|
||||
err = packet.Decode(b)
|
||||
if err != nil {
|
||||
@@ -132,9 +112,40 @@ func UnpackPacket(r io.Reader) (packet proto_base.EncodeDecodeAble, i proto_base
|
||||
packet = &proto.UnCodablePacket{Err: proto.PacketNotFullyDecodedErr, Data: packetData[indexafterid:], Info: i}
|
||||
return
|
||||
}
|
||||
trackState(packet, i)
|
||||
return
|
||||
}
|
||||
|
||||
func trackState(packet proto_base.EncodeDecodeAble, i proto_base.PacketInfo) {
|
||||
switch i.Name {
|
||||
case "set_protocol":
|
||||
v := reflect.ValueOf(packet).Elem()
|
||||
nsField := v.FieldByName("NextState")
|
||||
if nsField.IsValid() && nsField.Kind() == reflect.Int32 {
|
||||
switch nsField.Int() {
|
||||
case 1:
|
||||
state = proto_base.Status
|
||||
case 2:
|
||||
state = proto_base.Login
|
||||
}
|
||||
}
|
||||
case "login_start":
|
||||
if int32(*ProtocolVersion) <= 47 {
|
||||
state = proto_base.Play
|
||||
}
|
||||
case "success":
|
||||
if int32(*ProtocolVersion) >= 764 {
|
||||
state = proto_base.Configuration
|
||||
} else {
|
||||
state = proto_base.Play
|
||||
}
|
||||
case "finish_configuration":
|
||||
state = proto_base.Play
|
||||
case "login_acknowledged":
|
||||
state = proto_base.Configuration
|
||||
}
|
||||
}
|
||||
|
||||
func Run(args []string) (err error) {
|
||||
err = cmd.Parse(args)
|
||||
if err != nil {
|
||||
@@ -147,12 +158,16 @@ func Run(args []string) (err error) {
|
||||
return fmt.Errorf("no file specified")
|
||||
}
|
||||
switch *StateFlag {
|
||||
case "handshake":
|
||||
state = proto_base.Handshaking
|
||||
case "login":
|
||||
state = proto_base.Login
|
||||
case "config":
|
||||
state = proto_base.Configuration
|
||||
case "play":
|
||||
state = proto_base.Play
|
||||
case "status":
|
||||
state = proto_base.Status
|
||||
default:
|
||||
return fmt.Errorf("unknown state: %s", *StateFlag)
|
||||
}
|
||||
@@ -193,6 +208,10 @@ func Run(args []string) (err error) {
|
||||
}
|
||||
totalPackets++
|
||||
}
|
||||
fmt.Printf("Total packets: %d, failed packets: %d, %%failed: %.2f%%\n", totalPackets, failedPackets, float64(failedPackets)/float64(totalPackets)*100)
|
||||
if totalPackets > 0 {
|
||||
fmt.Printf("Total packets: %d, failed packets: %d, %%failed: %.2f%%\n", totalPackets, failedPackets, float64(failedPackets)/float64(totalPackets)*100)
|
||||
} else {
|
||||
fmt.Printf("Total packets: %d, failed packets: %d\n", totalPackets, failedPackets)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,18 @@
|
||||
package failed_packets_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
generated "github.com/admin-else/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 "github.com/admin-else/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 "github.com/admin-else/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 "github.com/admin-else/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 "github.com/admin-else/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 "github.com/admin-else/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 "github.com/admin-else/strom/mc/proto_generated/v26_2"
|
||||
)
|
||||
|
||||
// Error: unexpected EOF
|
||||
func TestFailedPacketFE935CF2E584C649(t *testing.T) {
|
||||
p := &generated.PlayToClientPacketUpdateTime{}
|
||||
b := bytes.NewReader([]byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1c, 0x1a, 0x0})
|
||||
err := p.Decode(b)
|
||||
if err == nil {
|
||||
t.Fatalf("expected decode error but got nil")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package failed_packets_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
generated "github.com/admin-else/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 "github.com/admin-else/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 "github.com/admin-else/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 "github.com/admin-else/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 "github.com/admin-else/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 "github.com/admin-else/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,253 +0,0 @@
|
||||
package raw_capture
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/admin-else/strom/mc/api"
|
||||
"github.com/admin-else/strom/mc/client"
|
||||
"github.com/admin-else/strom/mc/data"
|
||||
"github.com/admin-else/strom/mc/event"
|
||||
"github.com/admin-else/strom/mc/proto"
|
||||
"github.com/admin-else/strom/mc/proto/replay"
|
||||
"github.com/admin-else/strom/mc/proto_base"
|
||||
"github.com/admin-else/strom/mc/proto_generated/v1_21_11"
|
||||
"github.com/admin-else/strom/mc/server"
|
||||
"github.com/admin-else/strom/mc/text"
|
||||
)
|
||||
|
||||
var Cmd = flag.NewFlagSet("raw-capture", flag.ContinueOnError)
|
||||
|
||||
var OfflineNameFlag = Cmd.String("name", "raw_capture", "offline player name")
|
||||
var Token = Cmd.String("token", "", "token to use")
|
||||
|
||||
var TargetAddr = Cmd.String("addr", "127.0.0.1:25565", "address to connect to")
|
||||
var ListenAddr = Cmd.String("listen", "127.0.0.1:25566", "address to listen on")
|
||||
var ClientboundFile = Cmd.String("clientbound-file", "clientbound.tmcpr", "file to write clientbound packets to")
|
||||
var ServerboundFile = Cmd.String("serverbound-file", "serverbound.tmcpr", "file to write serverbound packets to")
|
||||
var Version = Cmd.String("version", "", "client protocol version (e.g. 26.1); if empty, detect from server status")
|
||||
|
||||
var StatusResponse = server.StatusResponse{
|
||||
Version: server.StatusResponseVersion{
|
||||
Name: text.Pretty("STROM raw-capture"),
|
||||
Protocol: 0,
|
||||
},
|
||||
Players: server.StatusResponsePlayers{},
|
||||
Description: text.Pretty("STROM raw-capture proxy"),
|
||||
Favicon: "",
|
||||
EnforcesSecureChat: false,
|
||||
}
|
||||
|
||||
type Proxy struct {
|
||||
Client, Servee *proto.Conn
|
||||
|
||||
clientboundFile *os.File
|
||||
serverboundFile *os.File
|
||||
|
||||
clientboundWriter *replay.Writer
|
||||
serverboundWriter *replay.Writer
|
||||
|
||||
startTime time.Time
|
||||
}
|
||||
|
||||
func (p *Proxy) Start() (err error) {
|
||||
p.startTime = time.Now()
|
||||
|
||||
p.Client.RegisterCritical(p.OnAnything)
|
||||
p.Servee.RegisterCritical(p.OnAnything)
|
||||
|
||||
p.Client.RegisterCriticalUntilLatest(p.OnCompress)
|
||||
p.Servee.RegisterCriticalUntilLatest(p.OnHandshake)
|
||||
p.Servee.RegisterCriticalUntilLatest(p.OnLoginAcknowledged)
|
||||
p.Servee.RegisterCriticalUntilLatest(p.OnFinishConfiguration)
|
||||
|
||||
errChan := make(chan error)
|
||||
go func() {
|
||||
errChan <- p.Client.StartConn()
|
||||
}()
|
||||
go func() {
|
||||
errChan <- p.Servee.StartConn()
|
||||
}()
|
||||
err = <-errChan
|
||||
return
|
||||
}
|
||||
|
||||
func (p *Proxy) timestamp() uint32 {
|
||||
return uint32(time.Since(p.startTime).Milliseconds())
|
||||
}
|
||||
|
||||
func (p *Proxy) OnAnything(e event.Anything) (err error) {
|
||||
packet, ok := e.Val.(proto_base.EncodeDecodeAble)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
packetInfo, ok := proto.LookupPacketInfoByType(packet)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
if packetInfo.Name == "encryption_begin" {
|
||||
return fmt.Errorf("encryption requested by remote server; raw capture cannot continue")
|
||||
}
|
||||
|
||||
switch packetInfo.Direction {
|
||||
case proto_base.ToServer:
|
||||
err = p.serverboundWriter.WritePacket(packet, p.timestamp())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = p.Client.Send(packet)
|
||||
case proto_base.ToClient:
|
||||
err = p.clientboundWriter.WritePacket(packet, p.timestamp())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = p.Servee.Send(packet)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (p *Proxy) OnCompress(packet *v1_21_11.LoginToClientPacketCompress) (err error) {
|
||||
err = p.clientboundWriter.WritePacket(packet, p.timestamp())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = p.Servee.Send(packet)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
p.Servee.SetCompressionThreshold(packet.Threshold)
|
||||
p.Client.SetCompressionThreshold(packet.Threshold)
|
||||
return event.DontForwardErr
|
||||
}
|
||||
|
||||
func (p *Proxy) OnHandshake(packet *v1_21_11.HandshakingToServerPacketSetProtocol) (err error) {
|
||||
state := proto_base.State(packet.NextState)
|
||||
p.Servee.SetState(state)
|
||||
return event.DontForwardErr
|
||||
}
|
||||
|
||||
func (p *Proxy) OnLoginAcknowledged(packet *v1_21_11.LoginToServerPacketLoginAcknowledged) (err error) {
|
||||
err = p.serverboundWriter.WritePacket(packet, p.timestamp())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = p.Client.Send(packet)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
p.Servee.SetState(proto_base.Configuration)
|
||||
p.Client.SetState(proto_base.Configuration)
|
||||
return event.DontForwardErr
|
||||
}
|
||||
|
||||
func (p *Proxy) OnFinishConfiguration(packet *v1_21_11.ConfigurationToServerPacketFinishConfiguration) (err error) {
|
||||
err = p.serverboundWriter.WritePacket(packet, p.timestamp())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = p.Client.Send(packet)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
p.Servee.SetState(proto_base.Play)
|
||||
p.Client.SetState(proto_base.Play)
|
||||
return event.DontForwardErr
|
||||
}
|
||||
|
||||
func Run(args []string) (err error) {
|
||||
err = Cmd.Parse(args)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
clientboundFile, err := os.Create(*ClientboundFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("create clientbound file: %w", err)
|
||||
}
|
||||
defer clientboundFile.Close()
|
||||
|
||||
serverboundFile, err := os.Create(*ServerboundFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("create serverbound file: %w", err)
|
||||
}
|
||||
defer serverboundFile.Close()
|
||||
|
||||
resolvedAddr, _, _, err := client.DoDNSChecked(nil, *TargetAddr, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var targetVersion string
|
||||
var targetProtocol int32
|
||||
if *Version == "" {
|
||||
status, err := client.Status(nil, resolvedAddr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to detect server version: %w", err)
|
||||
}
|
||||
targetProtocol = status.Version.Protocol
|
||||
versionInfo, err := data.LookUpVersionByProtocolVersion(targetProtocol)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unknown protocol version %d: %w", targetProtocol, err)
|
||||
}
|
||||
targetVersion = versionInfo.MinecraftVersion
|
||||
} else {
|
||||
versionInfo, err := data.LookUpVersionByName(*Version)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unknown version %s: %w", *Version, err)
|
||||
}
|
||||
targetVersion = versionInfo.MinecraftVersion
|
||||
targetProtocol = versionInfo.Version
|
||||
}
|
||||
|
||||
StatusResponse.Version.Name = text.Pretty("STROM raw-capture " + targetVersion)
|
||||
StatusResponse.Version.Protocol = targetProtocol
|
||||
StatusResponse.Description = text.Pretty(fmt.Sprintf("Raw capture proxy forwards to %s (%s)", resolvedAddr, targetVersion))
|
||||
|
||||
slog.Info("Starting raw capture", "listen", *ListenAddr, "target", resolvedAddr, "version", targetVersion, "protocol", targetProtocol, "clientbound", *ClientboundFile, "serverbound", *ServerboundFile)
|
||||
|
||||
err = server.StartServerWithOnConn(*ListenAddr, func(serveeConn *proto.Conn) (err error) {
|
||||
proxy := &Proxy{
|
||||
Servee: serveeConn,
|
||||
clientboundFile: clientboundFile,
|
||||
serverboundFile: serverboundFile,
|
||||
clientboundWriter: replay.NewWriter(clientboundFile),
|
||||
serverboundWriter: replay.NewWriter(serverboundFile),
|
||||
}
|
||||
|
||||
acc := api.NewOfflineAccount(*OfflineNameFlag)
|
||||
if *Token != "" {
|
||||
acc, err = api.NewAccountFromYGG(*Token)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
_, err = server.ServeLogin(serveeConn, server.WithOtherAccount(acc), server.WithStatus(StatusResponse), server.WithoutOnlineMode(), server.WithoutEncryption())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
c, err := client.Connect(nil, resolvedAddr, targetVersion)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
err = client.LoginRaw(c, acc)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
proxy.Client = c
|
||||
proxy.Client.AlwaysDecode = true
|
||||
proxy.Servee.AlwaysDecode = true
|
||||
|
||||
return proxy.Start()
|
||||
})
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,256 @@
|
||||
package raw_to_tmcpr
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/zlib"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/admin-else/strom/mc/proto/replay"
|
||||
"github.com/admin-else/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
|
||||
}
|
||||
@@ -1,152 +0,0 @@
|
||||
package registry_data_hunter
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"log/slog"
|
||||
"os"
|
||||
|
||||
"github.com/admin-else/strom/mc/api"
|
||||
"github.com/admin-else/strom/mc/client"
|
||||
"github.com/admin-else/strom/mc/data"
|
||||
"github.com/admin-else/strom/mc/event"
|
||||
"github.com/admin-else/strom/mc/nbt"
|
||||
"github.com/admin-else/strom/mc/proto"
|
||||
"github.com/admin-else/strom/mc/proto_base"
|
||||
"github.com/admin-else/strom/mc/proto_generated/v1_21_8"
|
||||
"github.com/admin-else/strom/mc/server"
|
||||
"github.com/admin-else/strom/mc/text"
|
||||
)
|
||||
|
||||
func wontFail[T any](v T, err error) T {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
var (
|
||||
Version = "1.21.11"
|
||||
SaveAs = "data/registry/" + Version + ".nbt"
|
||||
|
||||
CompatibleProtocolVersion = wontFail(data.LookUpVersionByName(Version)).Version
|
||||
FinishedConfigErr = errors.New("finished configuration")
|
||||
)
|
||||
|
||||
var Status = wontFail(json.Marshal(server.StatusResponse{
|
||||
Version: server.StatusResponseVersion{
|
||||
Name: nil,
|
||||
Protocol: CompatibleProtocolVersion,
|
||||
},
|
||||
Description: text.Pretty("Registry data hunter"),
|
||||
}))
|
||||
|
||||
type Proxy struct {
|
||||
Client, Servee *proto.Conn
|
||||
Data *nbt.Tag
|
||||
}
|
||||
|
||||
func (p *Proxy) OnAnything(e event.Anything) (err error) {
|
||||
packet, ok := e.Val.(proto_base.EncodeDecodeAble)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
packetInfo, ok := proto.LookupPacketInfoByType(packet)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
switch packetInfo.Direction {
|
||||
case proto_base.ToServer:
|
||||
err = p.Client.Send(packet)
|
||||
case proto_base.ToClient:
|
||||
err = p.Servee.Send(packet)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (p *Proxy) OnFinishConfiguration(_ *v1_21_8.ConfigurationToClientPacketFinishConfiguration) (err error) {
|
||||
err = FinishedConfigErr
|
||||
return
|
||||
}
|
||||
|
||||
func (p *Proxy) OnSelectKnownPacks(data *v1_21_8.ConfigurationToClientPacketCommonSelectKnownPacks) (err error) {
|
||||
var entries []any
|
||||
for _, entry := range data.Packs {
|
||||
e := map[string]any{"Namespace": entry.Namespace, "Id": entry.Id, "Version": entry.Version}
|
||||
entries = append(entries, e)
|
||||
}
|
||||
p.Data.Value.(map[string]any)["KnownPacks"] = entries
|
||||
return
|
||||
}
|
||||
|
||||
func (p *Proxy) OnData(data *v1_21_8.ConfigurationToClientPacketRegistryData) (err error) {
|
||||
d := p.Data.Value.(map[string]any)["Registries"].([]any)
|
||||
var entries []any
|
||||
for _, entry := range data.Entries {
|
||||
if entry.Value != nil {
|
||||
entries = append(entries, map[string]any{"TagType": entry.Key, "Value": entry.Value.Value})
|
||||
} else {
|
||||
entries = append(entries, map[string]any{"TagType": entry.Key})
|
||||
}
|
||||
}
|
||||
d = append(d, map[string]any{"Id": data.Id, "Entries": entries})
|
||||
p.Data.Value.(map[string]any)["Registries"] = d
|
||||
return
|
||||
}
|
||||
|
||||
func (p *Proxy) OnTags(data *v1_21_8.ConfigurationToClientPacketTags) (err error) {
|
||||
var tags []any
|
||||
for _, tag := range data.Tags {
|
||||
var entries []any
|
||||
for _, entry := range tag.Tags.Val {
|
||||
entries = append(entries, map[string]any{"TagName": entry.TagName, "Entries": entry.Entries})
|
||||
}
|
||||
tags = append(tags, map[string]any{"TagType": tag.TagType, "Entries": entries})
|
||||
}
|
||||
p.Data.Value.(map[string]any)["Tags"] = tags
|
||||
return
|
||||
}
|
||||
|
||||
func handleClient(c *proto.Conn) (err error) {
|
||||
acc := api.NewOfflineAccount("RegistryHunter")
|
||||
|
||||
_, err = server.ServeLogin(c, server.WithOtherAccount(acc), server.WithCompatibleVersions(CompatibleProtocolVersion), server.WithRawStatus(Status))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
sockPuppet, err := client.Login(":25565", acc)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer sockPuppet.Close()
|
||||
errChan := make(chan error)
|
||||
p := &Proxy{Client: sockPuppet, Servee: c}
|
||||
p.Data.Value = map[string]any{"Registries": []any{}}
|
||||
p.Client.RegisterCritical(p.OnAnything)
|
||||
p.Servee.RegisterCritical(p.OnAnything)
|
||||
p.Client.RegisterCriticalUntilLatest(p.OnData)
|
||||
p.Client.RegisterCriticalUntilLatest(p.OnTags)
|
||||
p.Client.RegisterCriticalUntilLatest(p.OnSelectKnownPacks)
|
||||
p.Client.RegisterCriticalUntilLatest(p.OnFinishConfiguration)
|
||||
|
||||
go func() {
|
||||
errChan <- p.Client.StartConn()
|
||||
}()
|
||||
go func() {
|
||||
errChan <- p.Servee.StartConn()
|
||||
}()
|
||||
err = <-errChan
|
||||
f, err := os.Create(SaveAs)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
err = nbt.WriteUnstructuredFile(f, p.Data)
|
||||
slog.Info("saved", "file", SaveAs)
|
||||
return
|
||||
}
|
||||
|
||||
func Run(args []string) error {
|
||||
slog.SetLogLoggerLevel(slog.LevelDebug)
|
||||
return server.StartServerWithOnConn(":25566", handleClient)
|
||||
}
|
||||
Reference in New Issue
Block a user