mc root source folder cleanup
This commit is contained in:
@@ -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
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
package proto
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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, ×tamp)
|
||||
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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
go test fuzz v1
|
||||
[]byte("\x05\x000000")
|
||||
@@ -0,0 +1,2 @@
|
||||
go test fuzz v1
|
||||
[]byte("0")
|
||||
@@ -0,0 +1,2 @@
|
||||
go test fuzz v1
|
||||
[]byte("00")
|
||||
@@ -0,0 +1,2 @@
|
||||
go test fuzz v1
|
||||
[]byte("\x040000")
|
||||
@@ -0,0 +1,2 @@
|
||||
go test fuzz v1
|
||||
[]byte("\x0400000")
|
||||
@@ -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")
|
||||
Reference in New Issue
Block a user