mc root source folder cleanup

This commit is contained in:
adman
2026-06-27 13:54:54 +02:00
parent b2784574bd
commit 666f250b3e
480 changed files with 325 additions and 325 deletions
+352
View File
@@ -0,0 +1,352 @@
package proto
import (
"bytes"
"context"
"errors"
"fmt"
"io"
"log/slog"
"reflect"
"slices"
"strings"
"sync"
"github.com/admin-else/strom/mc/data"
"github.com/admin-else/strom/mc/event"
"github.com/admin-else/strom/mc/proto_base"
"github.com/admin-else/strom/mc/proto_generated"
)
const (
BufferNPackets = 100
)
var (
BadPacketTypeErr = errors.New("bad packet type")
BadPacketIdErr = errors.New("bad packet id")
PacketNotFullyDecodedErr = errors.New("packet not fully decoded")
WrongStateErr = errors.New("wrong state")
WrongDirectionErr = errors.New("wrong direction")
CantTranslateErr = errors.New("cant translate packet")
PacketDoesntExistInFutureVersionErr = errors.New("packet doesnt exist in future version")
NoHandlerRegisteredErr = errors.New("no handler registered")
)
// LoginTick is used as a subsite for the old event system because it is very useful in login contexts
type LoginTick struct{}
var LatestVersion = proto_generated.SupportedVersions[len(proto_generated.SupportedVersions)-1]
var EarliestVersion = proto_generated.SupportedVersions[0]
// UnCodablePacket represents a packet that could not be decoded.
type UnCodablePacket struct {
Err error
Data []byte
Info proto_base.PacketInfo
}
func (u *UnCodablePacket) Encode(w io.Writer) (err error) {
_, err = w.Write(u.Data)
return
}
func (u *UnCodablePacket) Decode(r io.ReadSeeker) (err error) {
err = errors.New("how did you even get here")
return
}
type Conn struct {
RawConn
*event.Loop
state proto_base.State
stateMutex sync.RWMutex
Actor proto_base.Actor
Version string
ProtocolVersion int32
DebugPrintPackets []string
asyncPackets bool
asyncPacketsMutex sync.RWMutex
AlwaysDecode bool
Log *slog.Logger
}
func (c *Conn) StartConn() (err error) {
c.RegisterEventSource(c.ActivateReceiveRoutine())
c.RegisterCritical(c.OnLoginTick)
err = c.Loop.StartLoop()
c.Loop = event.NewLoop()
return
}
func (c *Conn) OnLoginTick(_ LoginTick) (err error) {
packet, err := c.Receive()
if err != nil {
return
}
err = c.Fire(packet)
return
}
func (c *Conn) SetState(state proto_base.State) {
c.stateMutex.Lock()
defer c.stateMutex.Unlock()
c.state = state
}
func (c *Conn) State() proto_base.State {
c.stateMutex.RLock()
defer c.stateMutex.RUnlock()
return c.state
}
func (c *Conn) SetAsyncPackets(asyncPackets bool) {
c.asyncPacketsMutex.Lock()
defer c.asyncPacketsMutex.Unlock()
c.asyncPackets = asyncPackets
}
func (c *Conn) GetAsyncPackets() bool {
c.asyncPacketsMutex.RLock()
defer c.asyncPacketsMutex.RUnlock()
return c.asyncPackets
}
func (c *Conn) SetVersion(version string) (err error) {
versionData, err := data.LookUpVersionByName(version)
if err != nil {
return
}
c.ProtocolVersion = versionData.Version
c.Version = version
return
}
func (c *Conn) SetProtocolVersion(version int32) (err error) {
versionData, err := data.LookUpVersionByProtocolVersion(version)
if err != nil {
return
}
c.ProtocolVersion = versionData.Version
c.Version = versionData.MinecraftVersion
return
}
// ActivateReceiveRoutine activates the receive-routine.
// It ensures that the receive-routine is only started once and creates a buffered channel for packet handling.
func (c *Conn) ActivateReceiveRoutine() (chSending <-chan any) {
// This expects you to start a new routine when switching into play state
ch := make(chan any, BufferNPackets)
go func() {
ctx := c.Ctx // this prevents c.Ctx being reassigned
for {
select {
case <-ctx.Done():
return
default:
if !c.GetAsyncPackets() {
ch <- LoginTick{}
} else {
packet, err := c.Receive()
if err != nil {
ch <- err
return
}
ch <- packet
}
}
}
}()
chSending = ch
return
}
func (c *Conn) translatePacketVersion(packet proto_base.EncodeDecodeAble, packetInfo proto_base.PacketInfo) (convertedPacket proto_base.EncodeDecodeAble, packetInfoReal proto_base.PacketInfo, err error) {
packetInfoReal, found := LookupPacketInfoByNameProtocolVersionAndState(packetInfo.Name, c.ProtocolVersion, c.State())
if !found {
err = PacketDoesntExistInFutureVersionErr
return
}
dstType := reflect.TypeOf(packetInfoReal.Type)
if !SmartConvertibleTo(reflect.TypeOf(packetInfo.Type), dstType) {
err = CantTranslateErr
return
}
// can't fail cause pacetinfo.type is proto_base.EncodeDecodeAble
convertedPacket = SmartConvert(reflect.ValueOf(packet), dstType).Interface().(proto_base.EncodeDecodeAble)
return
}
// SimplePacketToBytes converts a packet to bytes and prepends the packet length and packet id.
// Its usefull for generating static data like handshakes or for debugging.
func SimplePacketToBytes(packet proto_base.EncodeDecodeAble) (b []byte, err error) {
var packetBuff = bytes.NewBuffer(nil)
i, ok := LookupPacketInfoByType(packet)
if !ok {
err = BadPacketTypeErr
return
}
err = proto_base.EncodeVarInt(packetBuff, i.PacketId)
if err != nil {
return
}
err = packet.Encode(packetBuff)
if err != nil {
return
}
b = packetBuff.Bytes()
return
}
func SimplePacketToBytesLenPrefix(packet proto_base.EncodeDecodeAble) (b []byte, err error) {
b, err = SimplePacketToBytes(packet)
if err != nil {
return
}
var packetWithLenBuf = bytes.NewBuffer(nil)
err = proto_base.EncodeVarInt(packetWithLenBuf, int32(len(b)))
if err != nil {
return
}
packetWithLenBuf.Write(b)
b = packetWithLenBuf.Bytes()
return
}
// SimpleBytesToPacket converts packet bytes to usable packet data.
func SimpleBytesToPacket(packetBytes []byte, version int32, direction proto_base.Direction, state proto_base.State) (packet proto_base.EncodeDecodeAble, err error) {
b := bytes.NewReader(packetBytes)
id, err := proto_base.DecodeVarInt(b)
if err != nil {
return
}
i, ok := LookUpTypeByPacketInfo(direction, state, id, version)
if !ok {
packet = &UnCodablePacket{Err: BadPacketIdErr, Data: packetBytes, Info: i}
return
}
packet = reflect.New(reflect.TypeOf(i.Type).Elem()).Interface().(proto_base.EncodeDecodeAble)
err = packet.Decode(b)
if err != nil {
packet = &UnCodablePacket{Err: err, Data: packetBytes, Info: i}
err = nil
return
}
if b.Len() != 0 {
err = nil
packet = &UnCodablePacket{Err: PacketNotFullyDecodedErr, Data: packetBytes, Info: i}
return
}
return
}
func (c *Conn) sendRegisteredPacket(packet proto_base.EncodeDecodeAble) (err error) {
i, ok := LookupPacketInfoByTypeAndState(packet, c.State())
if !ok {
err = BadPacketTypeErr
return
}
if i.Direction != c.Actor.SendDirection() {
err = WrongDirectionErr
return
}
if i.State != c.State() {
err = WrongStateErr
return
}
if i.ProtocolVersion != c.ProtocolVersion {
packet, i, err = c.translatePacketVersion(packet, i)
if err != nil {
return
}
}
var packetBuff = bytes.NewBuffer(nil)
err = proto_base.EncodeVarInt(packetBuff, i.PacketId)
if err != nil {
return
}
err = packet.Encode(packetBuff)
if err != nil {
return
}
debugPrint := slog.Default().Enabled(context.Background(), slog.LevelDebug) &&
slices.ContainsFunc(c.DebugPrintPackets, func(s string) bool { return strings.Contains(i.Name, s) })
if debugPrint {
c.Log.Debug("send", "actor", c.Actor, "packet", fmt.Sprintf("%#v", packet))
}
return c.SendRaw(packetBuff.Bytes())
}
func (c *Conn) Send(packet proto_base.EncodeDecodeAble) (err error) {
switch packet := packet.(type) {
case *UnCodablePacket:
err = c.SendRaw(packet.Data)
default:
return c.sendRegisteredPacket(packet)
}
return
}
func (c *Conn) Receive() (packet proto_base.EncodeDecodeAble, err error) {
if c.R == nil {
return
}
packetBytes, err := c.ReceiveRaw()
if err != nil {
return
}
b := bytes.NewReader(packetBytes)
id, err := proto_base.DecodeVarInt(b)
if err != nil {
return
}
i, ok := LookUpTypeByPacketInfo(c.Actor.ReceiveDirection(), c.State(), id, c.ProtocolVersion)
if !ok {
packet = &UnCodablePacket{Err: BadPacketIdErr, Data: packetBytes, Info: i}
return
}
packet = reflect.New(reflect.TypeOf(i.Type).Elem()).Interface().(proto_base.EncodeDecodeAble)
debugPrint := slog.Default().Enabled(context.Background(), slog.LevelDebug) &&
slices.ContainsFunc(c.DebugPrintPackets, func(s string) bool { return strings.Contains(i.Name, s) })
isListenerRegistered := c.IsTypeRegistered(reflect.TypeOf(packet))
if !isListenerRegistered && !debugPrint && !c.AlwaysDecode {
packet = &UnCodablePacket{Err: NoHandlerRegisteredErr, Data: packetBytes, Info: i}
return
}
err = packet.Decode(b)
if err != nil {
packet = &UnCodablePacket{Err: err, Data: packetBytes, Info: i}
err = nil
return
}
if b.Len() != 0 {
err = nil
packet = &UnCodablePacket{Err: PacketNotFullyDecodedErr, Data: packetBytes, Info: i}
return
}
if debugPrint {
c.Log.Debug("recv", "packet", fmt.Sprintf("%#v", packet), "isListenerRegistered", isListenerRegistered, "len", len(packetBytes))
}
return
}
func NewConn() (ret *Conn) {
return NewConnCtx(context.Background())
}
func NewConnCtx(ctx context.Context) (ret *Conn) {
ret = &Conn{}
ret.SetState(proto_base.Handshaking)
ret.SetCompressionThreshold(-1)
err := ret.SetVersion(LatestVersion)
if err != nil {
panic(err)
}
ret.Loop = event.NewLoopCtx(ctx)
ret.Log = slog.Default()
return
}
+116
View File
@@ -0,0 +1,116 @@
package proto_test
import (
"bytes"
"math"
"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/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
)
var nanIgnorer32 = cmp.Comparer(func(x, y float32) bool {
return (math.IsNaN(float64(x)) && math.IsNaN(float64(y))) || x == y
})
var nanIgnorer64 = cmp.Comparer(func(x, y float64) bool {
return (math.IsNaN(x) && math.IsNaN(y)) || x == y
})
func NewStateFuzzer(state proto_base.State, actor proto_base.Actor) func(t *testing.T, inbytes []byte) {
return func(t *testing.T, inbytes []byte) {
b1 := bytes.NewBuffer(inbytes)
b2 := bytes.NewBuffer(nil)
conn := proto.NewConn()
conn.SetState(state)
conn.Actor = actor
conn.R = b1
conn.W = b2
_ = conn.SetVersion("1.21.8") // cant fail
p, err := conn.Receive()
if err != nil {
t.Skip(err)
return
}
if b1.Len() != 0 {
t.Skip("not all bytes read")
return
}
err = conn.Send(p)
if err != nil {
t.Fatal(err)
return
}
b1.Reset()
conn.R = b2
conn.W = b1
p2, err := conn.Receive()
if err != nil {
t.Fatal(err)
return
}
if !cmp.Equal(p, p2, nanIgnorer32, nanIgnorer64, cmpopts.IgnoreFields(proto.UnCodablePacket{}, "Err")) {
t.Error("decoded and encoded decoded data are not equal",
cmp.Diff(p, p2, nanIgnorer32, nanIgnorer64, cmpopts.IgnoreFields(proto.UnCodablePacket{}, "Err")),
)
}
}
}
func FuzzConnHandshakingToServer(f *testing.F) {
f.Fuzz(NewStateFuzzer(proto_base.Handshaking, proto_base.Servee))
}
func FuzzConnHandshakingToClient(f *testing.F) {
f.Fuzz(NewStateFuzzer(proto_base.Handshaking, proto_base.Client))
}
func FuzzConnStatusToServer(f *testing.F) {
f.Fuzz(NewStateFuzzer(proto_base.Status, proto_base.Servee))
}
func FuzzConnStatusToClient(f *testing.F) {
f.Fuzz(NewStateFuzzer(proto_base.Status, proto_base.Client))
}
func FuzzConnLoginToServer(f *testing.F) {
f.Fuzz(NewStateFuzzer(proto_base.Login, proto_base.Servee))
}
func FuzzConnLoginToClient(f *testing.F) {
f.Fuzz(NewStateFuzzer(proto_base.Login, proto_base.Client))
}
func FuzzConnConfigToServer(f *testing.F) {
f.Fuzz(NewStateFuzzer(proto_base.Configuration, proto_base.Servee))
}
func FuzzConnConfigToClient(f *testing.F) {
f.Fuzz(NewStateFuzzer(proto_base.Configuration, proto_base.Client))
}
func FuzzConnPlayToServer(f *testing.F) {
f.Fuzz(NewStateFuzzer(proto_base.Play, proto_base.Servee))
}
func FuzzConnPlayToClient(f *testing.F) {
f.Fuzz(NewStateFuzzer(proto_base.Play, proto_base.Client))
}
func TestEntityMetadata(t *testing.T) {
packetData := []byte{97, 198, 9, 9, 3, 65, 160, 0, 0, 16, 0, 127, 255}
packet, err := proto.SimpleBytesToPacket(packetData, data.MustLookupProtocolVersion("1.21.11"), proto_base.ToClient, proto_base.Play)
if err != nil {
t.Fatal(err)
}
packetData2, err := proto.SimplePacketToBytes(packet)
if err != nil {
t.Fatal(err)
}
if !cmp.Equal(packetData, packetData2) {
t.Error("packet data is not equal", cmp.Diff(packetData, packetData2))
}
}
+1
View File
@@ -0,0 +1 @@
package proto
+92
View File
@@ -0,0 +1,92 @@
package proto
import (
"reflect"
"github.com/admin-else/strom/mc/proto_base"
"github.com/admin-else/strom/mc/proto_generated"
)
type PidEtc struct {
Direction proto_base.Direction
State proto_base.State
PacketId int32
ProtocolVersion int32
}
var pidMap = make(map[PidEtc]*proto_base.PacketInfo)
func init() {
for _, pin := range proto_generated.Packets {
pidMap[PidEtc{pin.Direction, pin.State, pin.PacketId, pin.ProtocolVersion}] = &pin
}
}
func LookUpTypeByPacketInfo(direction proto_base.Direction, state proto_base.State, pid, version int32) (p proto_base.PacketInfo, ok bool) {
pP, ok := pidMap[PidEtc{direction, state, pid, version}]
if !ok {
p = proto_base.PacketInfo{
Type: nil,
Name: "",
Direction: direction,
State: state,
PacketId: pid,
ProtocolVersion: version,
}
return
}
p = *pP
return
}
func LookUpTypeByPacketInfoAndCopyType(direction proto_base.Direction, state proto_base.State, pid, version int32) (p proto_base.EncodeDecodeAble, ok bool) {
i, ok := LookUpTypeByPacketInfo(direction, state, pid, version)
if !ok {
return
}
p = reflect.New(reflect.TypeOf(i.Type).Elem()).Interface().(proto_base.EncodeDecodeAble)
return
}
func LookupPacketInfoByType(packet proto_base.EncodeDecodeAble) (p proto_base.PacketInfo, ok bool) {
undecodedPacket, isUndecoable := packet.(*UnCodablePacket)
if isUndecoable {
p = undecodedPacket.Info
ok = true
return
}
for _, p := range proto_generated.Packets {
if reflect.TypeOf(p.Type) == reflect.TypeOf(packet) {
return p, true
}
}
return
}
func LookupPacketInfoByTypeAndState(packet proto_base.EncodeDecodeAble, state proto_base.State) (p proto_base.PacketInfo, ok bool) {
for _, p := range proto_generated.Packets {
if state == p.State && reflect.TypeOf(p.Type) == reflect.TypeOf(packet) {
return p, true
}
}
return
}
func LookupPacketInfoByNameProtocolVersionAndState(name string, version int32, state proto_base.State) (p proto_base.PacketInfo, ok bool) {
for _, p := range proto_generated.Packets {
if state == p.State && p.Name == name && p.ProtocolVersion == version {
return p, true
}
}
return
}
func LookupPacketInfoByNameProtocolVersionStateAndDirection(name string, version int32, state proto_base.State, direction proto_base.Direction) (p proto_base.PacketInfo, ok bool) {
for _, p := range proto_generated.Packets {
if state == p.State && p.Name == name && p.ProtocolVersion == version && p.Direction == direction {
return p, true
}
}
return
}
+142
View File
@@ -0,0 +1,142 @@
package proto
import (
"bytes"
"compress/zlib"
"crypto/aes"
"crypto/cipher"
"errors"
"io"
"net"
"sync"
"github.com/admin-else/strom/mc/crypto"
"github.com/admin-else/strom/mc/proto_base"
)
type RawConn struct {
net.Conn
R io.Reader
W io.Writer
compressionThreshold int32
CompressionThresholdMut sync.RWMutex
}
func (c *RawConn) SetCompressionThreshold(threshold int32) {
c.CompressionThresholdMut.Lock()
defer c.CompressionThresholdMut.Unlock()
c.compressionThreshold = threshold
}
func (c *RawConn) GetCompressionThreshold() int32 {
c.CompressionThresholdMut.RLock()
defer c.CompressionThresholdMut.RUnlock()
return c.compressionThreshold
}
func (c *RawConn) SendRaw(rawPacketBytes []byte) (err error) {
//fmt.Println("send:", rawPacketBytes)
var packetBytes []byte
threshold := c.GetCompressionThreshold()
if threshold >= 0 {
packetBuffer := bytes.NewBuffer(nil)
if int32(len(rawPacketBytes)) >= threshold {
err = proto_base.EncodeVarInt(packetBuffer, int32(len(rawPacketBytes)))
if err != nil {
return
}
zW := zlib.NewWriter(packetBuffer) // ignore we don't care about the result if it errors
_, err = zW.Write(rawPacketBytes)
if err != nil {
return
}
err = zW.Close()
if err != nil {
return
}
} else {
err = proto_base.EncodeVarInt(packetBuffer, 0)
if err != nil {
return
}
_, err = packetBuffer.Write(rawPacketBytes)
if err != nil {
return
}
}
packetBytes = packetBuffer.Bytes()
} else {
packetBytes = rawPacketBytes
}
packetWithLenBuffer := bytes.NewBuffer(nil)
err = proto_base.EncodeVarInt(packetWithLenBuffer, int32(len(packetBytes)))
if err != nil {
return
}
_, err = packetWithLenBuffer.Write(packetBytes)
if err != nil {
return
}
_, err = c.W.Write(packetWithLenBuffer.Bytes())
return
}
func (c *RawConn) ReceiveRaw() (packetBytes []byte, err error) {
rawPacketLen, err := proto_base.DecodeVarInt(c.R)
if err != nil {
return
}
rawPacketBytes, err := io.ReadAll(io.LimitReader(c.R, int64(rawPacketLen)))
if err != nil {
return
}
if len(rawPacketBytes) != int(rawPacketLen) {
err = errors.New("bad packet length")
return
}
rawPacketBuffer := bytes.NewBuffer(rawPacketBytes)
if c.GetCompressionThreshold() >= 0 {
var packetLen int32
packetLen, err = proto_base.DecodeVarInt(rawPacketBuffer)
if err != nil {
return
}
if packetLen == 0 {
packetBytes, err = io.ReadAll(rawPacketBuffer)
if err != nil {
return
}
} else {
var zReader io.ReadCloser
zReader, err = zlib.NewReader(rawPacketBuffer)
if err != nil {
return
}
packetBytes, err = io.ReadAll(zReader)
if err != nil {
return
}
err = zReader.Close()
}
} else {
packetBytes = rawPacketBytes
}
return
}
func (c *RawConn) SetSecret(sharedSecret []byte) (err error) {
var b cipher.Block
b, err = aes.NewCipher(sharedSecret)
if err != nil {
return
}
c.R = cipher.StreamReader{
S: crypto.NewCFB8Decrypt(b, sharedSecret),
R: c.Conn,
}
c.W = cipher.StreamWriter{
S: crypto.NewCFB8Encrypt(b, sharedSecret),
W: c.Conn,
}
return
}
+67
View File
@@ -0,0 +1,67 @@
package replay
import (
"bytes"
"encoding/binary"
"io"
"reflect"
proto2 "github.com/admin-else/strom/mc/proto"
"github.com/admin-else/strom/mc/proto_base"
)
type Reader struct {
r io.Reader
State proto_base.State
Version int32
}
func NewReader(r io.Reader) *Reader {
return &Reader{r: r}
}
func (r *Reader) ReadRaw() (packetData []byte, timestamp, length uint32, err error) {
err = binary.Read(r.r, binary.BigEndian, &timestamp)
if err != nil {
return
}
err = binary.Read(r.r, binary.BigEndian, &length)
if err != nil {
return
}
// Read the packet data into a byte slice
packetData = make([]byte, length)
_, err = io.ReadFull(r.r, packetData)
return
}
func (r *Reader) ReadPacket() (packet proto_base.EncodeDecodeAble, err error) {
packetData, _, _, err := r.ReadRaw()
if err != nil {
return
}
b := bytes.NewReader(packetData)
packetId, err := proto_base.DecodeVarInt(b)
if err != nil {
return
}
// Calculate position after reading the packet ID
// indexafterid represents how many bytes we've read so far
indexafterid := int(b.Size()) - b.Len()
i, ok := proto2.LookUpTypeByPacketInfo(proto_base.ToClient, r.State, packetId, r.Version)
if !ok {
packet = &proto2.UnCodablePacket{Err: proto2.BadPacketIdErr, Data: packetData, Info: i}
return
}
packet = reflect.New(reflect.TypeOf(i.Type).Elem()).Interface().(proto_base.EncodeDecodeAble)
err = packet.Decode(b)
if err != nil {
packet = &proto2.UnCodablePacket{Err: err, Data: packetData[indexafterid:], Info: i}
return
}
return
}
+51
View File
@@ -0,0 +1,51 @@
package replay
import (
"bytes"
"encoding/binary"
"io"
proto2 "github.com/admin-else/strom/mc/proto"
"github.com/admin-else/strom/mc/proto_base"
)
type Writer struct {
w io.Writer
}
func NewWriter(w io.Writer) *Writer {
return &Writer{w: w}
}
func (w *Writer) WriteRaw(p []byte, timestamp uint32) (err error) {
err = binary.Write(w.w, binary.BigEndian, timestamp)
if err != nil {
return
}
err = binary.Write(w.w, binary.BigEndian, uint32(len(p)))
if err != nil {
return
}
_, err = w.w.Write(p)
return
}
func (w *Writer) WritePacket(packet proto_base.EncodeDecodeAble, timestamp uint32) (err error) {
b := bytes.NewBuffer(nil)
info, ok := proto2.LookupPacketInfoByType(packet)
if !ok {
return proto2.BadPacketIdErr
}
err = proto_base.EncodeVarInt(b, info.PacketId)
if err != nil {
return
}
err = packet.Encode(b)
if err != nil {
return
}
return w.WriteRaw(b.Bytes(), timestamp)
}
+133
View File
@@ -0,0 +1,133 @@
package proto
import (
"fmt"
"reflect"
"slices"
"github.com/admin-else/strom/mc/data"
"github.com/admin-else/strom/mc/event"
"github.com/admin-else/strom/mc/proto_base"
"github.com/admin-else/strom/mc/proto_generated"
)
func SmartConvertibleTo(from, to reflect.Type) bool {
switch from.Kind() {
case reflect.Struct:
if to.Kind() != reflect.Struct {
return false
}
if from.NumField() != to.NumField() {
return false
}
for i := range from.NumField() {
if !SmartConvertibleTo(from.Field(i).Type, to.Field(i).Type) {
return false
}
}
return true
case reflect.Pointer, reflect.Slice:
if from.Kind() != to.Kind() {
return false
}
return SmartConvertibleTo(from.Elem(), to.Elem())
default:
return from.ConvertibleTo(to)
}
}
func SmartConvert(from reflect.Value, to reflect.Type) (ret reflect.Value) {
switch from.Kind() {
case reflect.Struct:
ret = reflect.New(to).Elem()
for i := range from.NumField() {
ret.Field(i).Set(SmartConvert(from.Field(i), to.Field(i).Type))
}
case reflect.Pointer:
if from.IsNil() {
return reflect.Zero(to)
}
ret = reflect.New(to.Elem())
ret.Elem().Set(SmartConvert(from.Elem(), to.Elem()))
case reflect.Slice:
l := from.Len()
ret = reflect.MakeSlice(to, l, l)
for i := range l {
ret.Index(i).Set(SmartConvert(from.Index(i), to.Elem()))
}
default:
return from.Convert(to)
}
return ret
}
// RegisterUntilLatest registers a handler for all versions between the passed in packet version and the latest supported version.
// Warning: If a bad handler is passed in, the program will panic.
func (c *Conn) RegisterUntilLatest(h any) {
eventType, hv := event.ValidateHandler(h)
c.RegisterUntilLow(eventType, hv, LatestVersion, false)
}
// RegisterUntil registers a handler for all versions between the passed in packet version and the specified version.
// Warning: If a bad handler is passed in, the program will panic.
func (c *Conn) RegisterUntil(h any, until string) {
eventType, hv := event.ValidateHandler(h)
c.RegisterUntilLow(eventType, hv, until, false)
}
// RegisterCriticalUntilLatest registers a handler for all versions between the passed in packet version and the latest supported version.
// If this handler returns an error, the connection will be closed.
// Warning: If a bad handler is passed in, the program will panic.
func (c *Conn) RegisterCriticalUntilLatest(h any) {
eventType, hv := event.ValidateHandler(h)
c.RegisterUntilLow(eventType, hv, LatestVersion, true)
}
// RegisterCriticalUntil registers a handler for all versions between the passed in packet version and the specified version.
// If this handler returns an error, the connection will be closed.
// Warning: If a bad handler is passed in, the program will panic.
func (c *Conn) RegisterCriticalUntil(h any, until string) {
eventType, hv := event.ValidateHandler(h)
c.RegisterUntilLow(eventType, hv, until, true)
}
func (c *Conn) RegisterUntilLow(eventType reflect.Type, hv reflect.Value, until string, critical bool) {
c.RegisterLow(eventType, hv, critical)
if !eventType.Implements(reflect.TypeFor[proto_base.EncodeDecodeAble]()) {
panic("expected method with argument that implements proto_base.EncodeDecodeAble")
}
packetInfo, found := LookupPacketInfoByType(reflect.Zero(eventType).Interface().(proto_base.EncodeDecodeAble))
if !found {
panic("packet not found")
}
versionInfo, err := data.LookUpVersionByProtocolVersion(packetInfo.ProtocolVersion)
if err != nil {
panic("protocol version not found")
}
startIndex := slices.Index(proto_generated.SupportedVersions, versionInfo.MinecraftVersion)
endIndex := slices.Index(proto_generated.SupportedVersions, until)
for i := startIndex + 1; i <= endIndex; i++ {
versionName := proto_generated.SupportedVersions[i]
versionInfo, err = data.LookUpVersionByName(versionName)
if err != nil {
panic("protocol version not found this should not happen even if a wrong type is passed in")
}
var newPacketInfo proto_base.PacketInfo
newPacketInfo, found = LookupPacketInfoByNameProtocolVersionStateAndDirection(packetInfo.Name, versionInfo.Version, packetInfo.State, packetInfo.Direction)
if !found {
panic("packet not found in future version")
}
newT := reflect.TypeOf(newPacketInfo.Type)
if !SmartConvertibleTo(newT, reflect.TypeOf(packetInfo.Type)) {
panic(fmt.Sprintf("packet %v is not convertible to %v", newT, reflect.TypeOf(packetInfo.Type)))
}
handleFunc := func(packet any) error {
v := hv.Call([]reflect.Value{SmartConvert(reflect.ValueOf(packet), reflect.TypeOf(packetInfo.Type))})[0]
if v.IsNil() {
return nil
}
return v.Interface().(error)
}
c.RegisterLow(newT, reflect.ValueOf(handleFunc), critical)
}
}
+68
View File
@@ -0,0 +1,68 @@
package proto_test
import (
"reflect"
"testing"
proto2 "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_21_8"
"github.com/admin-else/strom/mc/proto_generated/v1_21_9"
)
func TestCanSmartConvert(t *testing.T) {
packet := &v1_21_9.PlayToClientPacketPosition{Flags: v1_21_9.PlayToClientPositionUpdateRelatives{Val: 2}, X: 5}
can := proto2.SmartConvertibleTo(reflect.TypeOf(packet), reflect.TypeFor[*v1_21_8.PlayToClientPacketPosition]())
if !can {
t.Fatal("can't smart convert")
}
ret := proto2.SmartConvert(reflect.ValueOf(packet), reflect.TypeFor[*v1_21_8.PlayToClientPacketPosition]())
t.Logf("%#v", ret)
}
func TestCanSmartConvertTags(t *testing.T) {
packet := &v1_21_9.ConfigurationToClientPacketTags{}
targetType := reflect.TypeFor[*v1_21_8.ConfigurationToClientPacketTags]()
can := proto2.SmartConvertibleTo(reflect.TypeOf(packet), targetType)
if !can {
t.Fatal("can't smart convert")
}
ret := proto2.SmartConvert(reflect.ValueOf(packet), targetType)
t.Logf("%#v", ret)
}
func TestRegisterUntilLatest(t *testing.T) {
conn := proto2.NewConn()
ok := false
conn.RegisterCriticalUntilLatest(func(p *v1_21_8.ConfigurationToServerPacketFinishConfiguration) error {
ok = true
return nil
})
err := conn.Fire(&v1_21_11.ConfigurationToServerPacketFinishConfiguration{})
if err != nil {
t.Errorf("fire failed err: %v", err)
}
if !ok {
t.Errorf("handler not called")
}
}
var TestData = []struct{ V, T, E any }{
{V: &v1_21_9.PlayToClientPacketPosition{Flags: v1_21_9.PlayToClientPositionUpdateRelatives{Val: 2}, X: 5}, T: &v1_21_8.PlayToClientPacketPosition{}},
{V: &v1_21_9.ConfigurationToClientPacketTags{}, T: &v1_21_8.ConfigurationToClientPacketTags{}},
{V: &v1_21_11.PlayToServerPacketChatMessage{Signature: &[256]byte{}}, T: &v1_21_8.PlayToServerPacketChatMessage{}},
}
func TestSmartConvert(t *testing.T) {
for _, d := range TestData {
can := proto2.SmartConvertibleTo(reflect.TypeOf(d.V), reflect.TypeOf(d.T))
if !can {
t.Errorf("can't smart convert %#v to %#v", d.V, d.T)
continue
}
got := proto2.SmartConvert(reflect.ValueOf(d.V), reflect.TypeOf(d.T))
if d.E != nil && !reflect.DeepEqual(got.Interface(), d.E) {
t.Errorf("got %#v, want %#v", got.Interface(), d.E)
}
}
}
+2
View File
@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("\x05\x000000")
+2
View File
@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("0")
+2
View File
@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("00")
+2
View File
@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("\x040000")
+2
View File
@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("\x0400000")
+2
View File
@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("\x86\x00000000")
@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("")
@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("80\x00\x01\x05\x01\x010\x01\x00")