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
This commit is contained in:
@@ -6,6 +6,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/admin-else/strom/mc/level"
|
"github.com/admin-else/strom/mc/level"
|
||||||
"github.com/admin-else/strom/mc/level/anvil"
|
"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
|
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 {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,8 +20,9 @@ var ClientboundIn = Cmd.String("clientbound", "", "raw tcpflow file for clientbo
|
|||||||
var ServerboundIn = Cmd.String("serverbound", "", "raw tcpflow file for serverbound (client->server) 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 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 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, status, play")
|
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, 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 {
|
type captureReader struct {
|
||||||
r io.Reader
|
r io.Reader
|
||||||
@@ -32,6 +33,7 @@ type captureReader struct {
|
|||||||
haveStartTime bool
|
haveStartTime bool
|
||||||
direction proto_base.Direction
|
direction proto_base.Direction
|
||||||
pendingThreshold int32
|
pendingThreshold int32
|
||||||
|
useConfig bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cr *captureReader) setStartTime() {
|
func (cr *captureReader) setStartTime() {
|
||||||
@@ -123,13 +125,21 @@ func (cr *captureReader) trackState(packetBytes []byte) {
|
|||||||
cr.threshold = threshold
|
cr.threshold = threshold
|
||||||
}
|
}
|
||||||
} else if packetId == 0x02 {
|
} else if packetId == 0x02 {
|
||||||
cr.state = proto_base.Play
|
if cr.useConfig {
|
||||||
|
cr.state = proto_base.Configuration
|
||||||
|
} else {
|
||||||
|
cr.state = proto_base.Play
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if cr.direction == proto_base.ToServer && packetId == 0 {
|
} else if cr.direction == proto_base.ToServer && packetId == 0 {
|
||||||
if cr.pendingThreshold >= 0 {
|
if cr.pendingThreshold >= 0 {
|
||||||
cr.threshold = cr.pendingThreshold
|
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.Status:
|
||||||
case proto_base.Play:
|
case proto_base.Play:
|
||||||
}
|
}
|
||||||
@@ -172,12 +182,14 @@ func Run(args []string) (err error) {
|
|||||||
return proto_base.Handshaking, nil
|
return proto_base.Handshaking, nil
|
||||||
case "login":
|
case "login":
|
||||||
return proto_base.Login, nil
|
return proto_base.Login, nil
|
||||||
|
case "config":
|
||||||
|
return proto_base.Configuration, nil
|
||||||
case "play":
|
case "play":
|
||||||
return proto_base.Play, nil
|
return proto_base.Play, nil
|
||||||
case "status":
|
case "status":
|
||||||
return proto_base.Status, nil
|
return proto_base.Status, nil
|
||||||
default:
|
default:
|
||||||
return 0, fmt.Errorf("unknown state: %s (valid: handshake, login, status, play)", name)
|
return 0, fmt.Errorf("unknown state: %s (valid: handshake, login, config, status, play)", name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,7 +200,7 @@ func Run(args []string) (err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
compressionThreshold, err = processFile(*ClientboundIn, *ClientboundOut, state, proto_base.ToClient, -1)
|
compressionThreshold, err = processFile(*ClientboundIn, *ClientboundOut, state, proto_base.ToClient, -1, *UseConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("clientbound: %w", err)
|
return fmt.Errorf("clientbound: %w", err)
|
||||||
}
|
}
|
||||||
@@ -199,7 +211,7 @@ func Run(args []string) (err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_, err = processFile(*ServerboundIn, *ServerboundOut, state, proto_base.ToServer, compressionThreshold)
|
_, err = processFile(*ServerboundIn, *ServerboundOut, state, proto_base.ToServer, compressionThreshold, *UseConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("serverbound: %w", err)
|
return fmt.Errorf("serverbound: %w", err)
|
||||||
}
|
}
|
||||||
@@ -208,7 +220,7 @@ func Run(args []string) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func processFile(inPath, outPath string, initial proto_base.State, dir proto_base.Direction, initialThreshold int32) (finalThreshold int32, err error) {
|
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)
|
in, err := os.Open(inPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return initialThreshold, fmt.Errorf("open input: %w", err)
|
return initialThreshold, fmt.Errorf("open input: %w", err)
|
||||||
@@ -228,6 +240,7 @@ func processFile(inPath, outPath string, initial proto_base.State, dir proto_bas
|
|||||||
threshold: -1,
|
threshold: -1,
|
||||||
direction: dir,
|
direction: dir,
|
||||||
pendingThreshold: initialThreshold,
|
pendingThreshold: initialThreshold,
|
||||||
|
useConfig: useConfig,
|
||||||
}
|
}
|
||||||
if dir == proto_base.ToClient {
|
if dir == proto_base.ToClient {
|
||||||
cr.threshold = initialThreshold
|
cr.threshold = initialThreshold
|
||||||
|
|||||||
@@ -0,0 +1,160 @@
|
|||||||
|
package proto_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/admin-else/strom/mc/data"
|
||||||
|
"github.com/admin-else/strom/mc/proto"
|
||||||
|
"github.com/admin-else/strom/mc/proto_base"
|
||||||
|
"github.com/admin-else/strom/mc/proto_generated"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPacketRoundTripAllVersions(t *testing.T) {
|
||||||
|
results := map[string][4]int{}
|
||||||
|
for _, version := range proto_generated.SupportedVersions {
|
||||||
|
protoVer := data.MustLookupProtocolVersion(version)
|
||||||
|
total, encFailed, decFailed, ok := 0, 0, 0, 0
|
||||||
|
for _, pin := range proto_generated.Packets {
|
||||||
|
if pin.ProtocolVersion != int32(protoVer) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
total++
|
||||||
|
b, err := proto.SimplePacketToBytes(pin.Type)
|
||||||
|
if err != nil {
|
||||||
|
if err == proto_base.ToDoError || strings.Contains(err.Error(), "unsupported") || strings.Contains(err.Error(), "index not found") || strings.Contains(err.Error(), "bad type") {
|
||||||
|
encFailed++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
t.Errorf("%s/%s: encode error: %v", version, pin.Name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
decoded, err := proto.SimpleBytesToPacket(b, int32(protoVer), pin.Direction, pin.State)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s/%s: decode error: %v", version, pin.Name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if unc, ok := decoded.(*proto.UnCodablePacket); ok {
|
||||||
|
if unc.Err == proto_base.ToDoError {
|
||||||
|
decFailed++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
t.Errorf("%s/%s: uncodable: %v", version, pin.Name, unc.Err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
b2, err := proto.SimplePacketToBytes(decoded)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s/%s: re-encode error: %v", version, pin.Name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if len(b) != len(b2) {
|
||||||
|
t.Errorf("%s/%s: byte length mismatch %d != %d", version, pin.Name, len(b), len(b2))
|
||||||
|
}
|
||||||
|
ok++
|
||||||
|
}
|
||||||
|
results[version] = [4]int{total, encFailed, decFailed, ok}
|
||||||
|
}
|
||||||
|
fmt.Println()
|
||||||
|
for _, version := range proto_generated.SupportedVersions {
|
||||||
|
r := results[version]
|
||||||
|
fmt.Printf("%-8s %3d pkt enc-skip:%-3d dec-skip:%-3d ok:%-3d\n", version, r[0], r[1], r[2], r[3])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSmartConvertCheck(t *testing.T) {
|
||||||
|
panicCount := 0
|
||||||
|
notConvertible := 0
|
||||||
|
convertible := 0
|
||||||
|
totalPairs := 0
|
||||||
|
for _, fromV := range proto_generated.SupportedVersions {
|
||||||
|
for _, toV := range proto_generated.SupportedVersions {
|
||||||
|
if fromV == toV {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fromVer := data.MustLookupProtocolVersion(fromV)
|
||||||
|
toVer := data.MustLookupProtocolVersion(toV)
|
||||||
|
for _, pinFrom := range proto_generated.Packets {
|
||||||
|
if pinFrom.ProtocolVersion != int32(fromVer) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
pinTo, found := proto.LookupPacketInfoByNameProtocolVersionStateAndDirection(
|
||||||
|
pinFrom.Name, int32(toVer), pinFrom.State, pinFrom.Direction,
|
||||||
|
)
|
||||||
|
if !found {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
totalPairs++
|
||||||
|
func() {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
panicCount++
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
fromT := typeInfo(pinFrom.Type)
|
||||||
|
toT := typeInfo(pinTo.Type)
|
||||||
|
if proto.SmartConvertibleTo(fromT, toT) {
|
||||||
|
convertible++
|
||||||
|
} else {
|
||||||
|
notConvertible++
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Printf("smart convert: %d pairs, %d convertible, %d not, %d panics\n", totalPairs, convertible, notConvertible, panicCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
func typeInfo(t proto_base.EncodeDecodeAble) reflect.Type {
|
||||||
|
rt := reflect.TypeOf(t)
|
||||||
|
for rt.Kind() == reflect.Ptr {
|
||||||
|
rt = rt.Elem()
|
||||||
|
}
|
||||||
|
return rt
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCrossVersionDecode(t *testing.T) {
|
||||||
|
errors := 0
|
||||||
|
panics := 0
|
||||||
|
total := 0
|
||||||
|
for _, toV := range proto_generated.SupportedVersions {
|
||||||
|
toVer := data.MustLookupProtocolVersion(toV)
|
||||||
|
for _, pin := range proto_generated.Packets {
|
||||||
|
if pin.ProtocolVersion == int32(toVer) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
_, found := proto.LookupPacketInfoByNameProtocolVersionStateAndDirection(
|
||||||
|
pin.Name, int32(toVer), pin.State, pin.Direction,
|
||||||
|
)
|
||||||
|
if !found {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
b, err := proto.SimplePacketToBytes(pin.Type)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
total++
|
||||||
|
func() {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
panics++
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
decoded, err := proto.SimpleBytesToPacket(b, int32(toVer), pin.Direction, pin.State)
|
||||||
|
if err != nil {
|
||||||
|
errors++
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, ok := decoded.(*proto.UnCodablePacket); ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, err = proto.SimplePacketToBytes(decoded)
|
||||||
|
if err != nil {
|
||||||
|
errors++
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Printf("cross-version decode: %d attempts, %d errors, %d panics\n", total, errors, panics)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user