rewrite replay to support resourcepacks
This commit is contained in:
parent
87cd32e280
commit
5149487c18
3
go.mod
3
go.mod
|
@ -3,7 +3,7 @@ module github.com/bedrock-tool/bedrocktool
|
||||||
go 1.20
|
go 1.20
|
||||||
|
|
||||||
//replace github.com/sandertv/gophertunnel => ./gophertunnel
|
//replace github.com/sandertv/gophertunnel => ./gophertunnel
|
||||||
replace github.com/sandertv/gophertunnel => github.com/olebeck/gophertunnel v1.28.1-1
|
replace github.com/sandertv/gophertunnel => github.com/olebeck/gophertunnel v1.28.1-2
|
||||||
|
|
||||||
//replace github.com/df-mc/dragonfly => ./dragonfly
|
//replace github.com/df-mc/dragonfly => ./dragonfly
|
||||||
replace github.com/df-mc/dragonfly => github.com/olebeck/dragonfly v0.9.4-9
|
replace github.com/df-mc/dragonfly => github.com/olebeck/dragonfly v0.9.4-9
|
||||||
|
@ -51,6 +51,7 @@ require (
|
||||||
github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21 // indirect
|
github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21 // indirect
|
||||||
github.com/df-mc/atomic v1.10.0 // indirect
|
github.com/df-mc/atomic v1.10.0 // indirect
|
||||||
github.com/df-mc/worldupgrader v1.0.3 // indirect
|
github.com/df-mc/worldupgrader v1.0.3 // indirect
|
||||||
|
github.com/dlclark/regexp2 v1.9.0 // indirect
|
||||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||||
github.com/go-text/typesetting v0.0.0-20221214153724-0399769901d5 // indirect
|
github.com/go-text/typesetting v0.0.0-20221214153724-0399769901d5 // indirect
|
||||||
github.com/golang/protobuf v1.5.2 // indirect
|
github.com/golang/protobuf v1.5.2 // indirect
|
||||||
|
|
4
go.sum
4
go.sum
|
@ -37,6 +37,8 @@ github.com/df-mc/goleveldb v1.1.9 h1:ihdosZyy5jkQKrxucTQmN90jq/2lUwQnJZjIYIC/9YU
|
||||||
github.com/df-mc/goleveldb v1.1.9/go.mod h1:+NHCup03Sci5q84APIA21z3iPZCuk6m6ABtg4nANCSk=
|
github.com/df-mc/goleveldb v1.1.9/go.mod h1:+NHCup03Sci5q84APIA21z3iPZCuk6m6ABtg4nANCSk=
|
||||||
github.com/df-mc/worldupgrader v1.0.3 h1:3nbthy6vfSNQZdqHBR+E5Fh3mCeWmCwLtqrYDiPUG5I=
|
github.com/df-mc/worldupgrader v1.0.3 h1:3nbthy6vfSNQZdqHBR+E5Fh3mCeWmCwLtqrYDiPUG5I=
|
||||||
github.com/df-mc/worldupgrader v1.0.3/go.mod h1:6ybkJ/BV9b0XkcPzcLmvgT9Nv/xgBXdDQTmRhu7b8zQ=
|
github.com/df-mc/worldupgrader v1.0.3/go.mod h1:6ybkJ/BV9b0XkcPzcLmvgT9Nv/xgBXdDQTmRhu7b8zQ=
|
||||||
|
github.com/dlclark/regexp2 v1.9.0 h1:pTK/l/3qYIKaRXuHnEnIf7Y5NxfRPfpb7dis6/gdlVI=
|
||||||
|
github.com/dlclark/regexp2 v1.9.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||||
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
|
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
|
||||||
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
|
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
|
||||||
github.com/flytam/filenamify v1.1.2 h1:dGlfWU4zrhDlsmvob4IFcfgjG5vIjfo4UwLyec6Wx94=
|
github.com/flytam/filenamify v1.1.2 h1:dGlfWU4zrhDlsmvob4IFcfgjG5vIjfo4UwLyec6Wx94=
|
||||||
|
@ -100,6 +102,8 @@ github.com/olebeck/gio v0.0.0-20230321105529-d424f1a59af9 h1:TqDsMHwjW5ZYfh+RE8u
|
||||||
github.com/olebeck/gio v0.0.0-20230321105529-d424f1a59af9/go.mod h1:+W1Kpf96YcfissZocFqIp6O42FDTuphkObbEybp+Ffc=
|
github.com/olebeck/gio v0.0.0-20230321105529-d424f1a59af9/go.mod h1:+W1Kpf96YcfissZocFqIp6O42FDTuphkObbEybp+Ffc=
|
||||||
github.com/olebeck/gophertunnel v1.28.1-1 h1:bw2jeMz94YHF5qQYhq1Yq/6fALkklGu7k26YbPI4DSs=
|
github.com/olebeck/gophertunnel v1.28.1-1 h1:bw2jeMz94YHF5qQYhq1Yq/6fALkklGu7k26YbPI4DSs=
|
||||||
github.com/olebeck/gophertunnel v1.28.1-1/go.mod h1:ekREo7U9TPHh86kbuPMaWA93NMyWsfVvP/iNT3XhAb8=
|
github.com/olebeck/gophertunnel v1.28.1-1/go.mod h1:ekREo7U9TPHh86kbuPMaWA93NMyWsfVvP/iNT3XhAb8=
|
||||||
|
github.com/olebeck/gophertunnel v1.28.1-2 h1:EoGyQNzmsXtg+ObOshCKyNffTqAfk9yRFNdJcHGjxmY=
|
||||||
|
github.com/olebeck/gophertunnel v1.28.1-2/go.mod h1:HxQfl/8mZzvjzhekEH8RO6xLAgan9i/wIyrQzw0tIPY=
|
||||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
|
github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
|
||||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
|
|
|
@ -66,7 +66,7 @@ func NewSecondUser() *utils.ProxyHandler {
|
||||||
s.proxy = pc
|
s.proxy = pc
|
||||||
},
|
},
|
||||||
SecondaryClientCB: s.SecondaryClientCB,
|
SecondaryClientCB: s.SecondaryClientCB,
|
||||||
OnClientConnect: func(conn *minecraft.Conn) {
|
OnClientConnect: func(conn minecraft.IConn) {
|
||||||
id := conn.IdentityData()
|
id := conn.IdentityData()
|
||||||
s.mainPlayer = player.New(id.DisplayName, skin.New(64, 64), mgl64.Vec3{0, 00})
|
s.mainPlayer = player.New(id.DisplayName, skin.New(64, 64), mgl64.Vec3{0, 00})
|
||||||
s.server.World().AddEntity(s.mainPlayer)
|
s.server.World().AddEntity(s.mainPlayer)
|
||||||
|
@ -99,7 +99,7 @@ func NewSecondUser() *utils.ProxyHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *secondaryUser) SecondaryClientCB(conn *minecraft.Conn) {
|
func (s *secondaryUser) SecondaryClientCB(conn minecraft.IConn) {
|
||||||
s.listener.Conn <- conn
|
s.listener.Conn <- conn
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -152,7 +152,7 @@ func (w *worldsHandler) ProcessChunkPackets(pk packet.Packet) packet.Packet {
|
||||||
if ok {
|
if ok {
|
||||||
x, y, z := blockPosInChunk(pk.Position)
|
x, y, z := blockPosInChunk(pk.Position)
|
||||||
c.SetBlock(x, y, z, uint8(pk.Layer), pk.NewBlockRuntimeID)
|
c.SetBlock(x, y, z, uint8(pk.Layer), pk.NewBlockRuntimeID)
|
||||||
w.mapUI.SetChunk(cp, w.worldState.chunks[cp], true)
|
w.mapUI.SetChunk(cp, c, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case *packet.UpdateSubChunkBlocks:
|
case *packet.UpdateSubChunkBlocks:
|
||||||
|
@ -168,7 +168,7 @@ func (w *worldsHandler) ProcessChunkPackets(pk packet.Packet) packet.Packet {
|
||||||
c.SetBlock(x, y, z, 0, bce.BlockRuntimeID)
|
c.SetBlock(x, y, z, 0, bce.BlockRuntimeID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
w.mapUI.SetChunk(cp, w.worldState.chunks[cp], true)
|
w.mapUI.SetChunk(cp, c, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,7 +62,7 @@ func (e serverEntity) Type() world.EntityType {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *worldsHandler) processAddActor(pk *packet.AddActor) {
|
func (w *worldsHandler) processAddActor(pk *packet.AddActor) {
|
||||||
e, ok := w.worldState.entities[pk.EntityRuntimeID]
|
e, ok := w.getEntity(pk.EntityRuntimeID)
|
||||||
if !ok {
|
if !ok {
|
||||||
e = &entityState{
|
e = &entityState{
|
||||||
RuntimeID: pk.EntityRuntimeID,
|
RuntimeID: pk.EntityRuntimeID,
|
||||||
|
@ -216,6 +216,11 @@ func (s *entityState) ToServerEntity() serverEntity {
|
||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *worldsHandler) getEntity(id uint64) (*entityState, bool) {
|
||||||
|
e, ok := w.worldState.entities[id]
|
||||||
|
return e, ok
|
||||||
|
}
|
||||||
|
|
||||||
func (w *worldsHandler) ProcessEntityPackets(pk packet.Packet) packet.Packet {
|
func (w *worldsHandler) ProcessEntityPackets(pk packet.Packet) packet.Packet {
|
||||||
if !w.settings.SaveEntities {
|
if !w.settings.SaveEntities {
|
||||||
return pk
|
return pk
|
||||||
|
@ -226,7 +231,7 @@ func (w *worldsHandler) ProcessEntityPackets(pk packet.Packet) packet.Packet {
|
||||||
w.processAddActor(pk)
|
w.processAddActor(pk)
|
||||||
case *packet.RemoveActor:
|
case *packet.RemoveActor:
|
||||||
case *packet.SetActorData:
|
case *packet.SetActorData:
|
||||||
e, ok := w.worldState.entities[pk.EntityRuntimeID]
|
e, ok := w.getEntity(pk.EntityRuntimeID)
|
||||||
if ok {
|
if ok {
|
||||||
e.Metadata = pk.EntityMetadata
|
e.Metadata = pk.EntityMetadata
|
||||||
w.bp.AddEntity(behaviourpack.EntityIn{
|
w.bp.AddEntity(behaviourpack.EntityIn{
|
||||||
|
@ -236,12 +241,12 @@ func (w *worldsHandler) ProcessEntityPackets(pk packet.Packet) packet.Packet {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
case *packet.SetActorMotion:
|
case *packet.SetActorMotion:
|
||||||
e, ok := w.worldState.entities[pk.EntityRuntimeID]
|
e, ok := w.getEntity(pk.EntityRuntimeID)
|
||||||
if ok {
|
if ok {
|
||||||
e.Velocity = pk.Velocity
|
e.Velocity = pk.Velocity
|
||||||
}
|
}
|
||||||
case *packet.MoveActorDelta:
|
case *packet.MoveActorDelta:
|
||||||
e, ok := w.worldState.entities[pk.EntityRuntimeID]
|
e, ok := w.getEntity(pk.EntityRuntimeID)
|
||||||
if ok {
|
if ok {
|
||||||
if pk.Flags&packet.MoveActorDeltaFlagHasX != 0 {
|
if pk.Flags&packet.MoveActorDeltaFlagHasX != 0 {
|
||||||
e.Position[0] = pk.Position[0]
|
e.Position[0] = pk.Position[0]
|
||||||
|
@ -263,14 +268,14 @@ func (w *worldsHandler) ProcessEntityPackets(pk packet.Packet) packet.Packet {
|
||||||
//}
|
//}
|
||||||
}
|
}
|
||||||
case *packet.MoveActorAbsolute:
|
case *packet.MoveActorAbsolute:
|
||||||
e, ok := w.worldState.entities[pk.EntityRuntimeID]
|
e, ok := w.getEntity(pk.EntityRuntimeID)
|
||||||
if ok {
|
if ok {
|
||||||
e.Position = pk.Position
|
e.Position = pk.Position
|
||||||
e.Pitch = pk.Rotation.X()
|
e.Pitch = pk.Rotation.X()
|
||||||
e.Yaw = pk.Rotation.Y()
|
e.Yaw = pk.Rotation.Y()
|
||||||
}
|
}
|
||||||
case *packet.MobEquipment:
|
case *packet.MobEquipment:
|
||||||
e, ok := w.worldState.entities[pk.EntityRuntimeID]
|
e, ok := w.getEntity(pk.EntityRuntimeID)
|
||||||
if ok {
|
if ok {
|
||||||
w, ok := e.Inventory[pk.WindowID]
|
w, ok := e.Inventory[pk.WindowID]
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -280,7 +285,7 @@ func (w *worldsHandler) ProcessEntityPackets(pk packet.Packet) packet.Packet {
|
||||||
w[pk.HotBarSlot] = pk.NewItem
|
w[pk.HotBarSlot] = pk.NewItem
|
||||||
}
|
}
|
||||||
case *packet.MobArmourEquipment:
|
case *packet.MobArmourEquipment:
|
||||||
e, ok := w.worldState.entities[pk.EntityRuntimeID]
|
e, ok := w.getEntity(pk.EntityRuntimeID)
|
||||||
if ok {
|
if ok {
|
||||||
e.Helmet = &pk.Helmet
|
e.Helmet = &pk.Helmet
|
||||||
e.Chestplate = &pk.Chestplate
|
e.Chestplate = &pk.Chestplate
|
||||||
|
|
|
@ -139,7 +139,7 @@ func NewWorldsHandler(ctx context.Context, ui utils.UI, settings WorldSettings)
|
||||||
w.serverState.Name = hostname
|
w.serverState.Name = hostname
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
OnClientConnect: func(conn *minecraft.Conn) {
|
OnClientConnect: func(conn minecraft.IConn) {
|
||||||
w.gui.Message(messages.SetUIState(messages.UIStateConnecting))
|
w.gui.Message(messages.SetUIState(messages.UIStateConnecting))
|
||||||
},
|
},
|
||||||
GameDataModifier: func(gd *minecraft.GameData) {
|
GameDataModifier: func(gd *minecraft.GameData) {
|
||||||
|
|
|
@ -33,6 +33,7 @@ func (c *CaptureCMD) Execute(ctx context.Context, ui utils.UI) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
proxy.AlwaysGetPacks = true
|
||||||
proxy.AddHandler(handlers.NewPacketCapturer())
|
proxy.AddHandler(handlers.NewPacketCapturer())
|
||||||
return proxy.Run(ctx, address, hostname)
|
return proxy.Run(ctx, address, hostname)
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,6 @@ import (
|
||||||
"github.com/bedrock-tool/bedrocktool/locale"
|
"github.com/bedrock-tool/bedrocktool/locale"
|
||||||
"github.com/bedrock-tool/bedrocktool/ui/messages"
|
"github.com/bedrock-tool/bedrocktool/ui/messages"
|
||||||
"github.com/bedrock-tool/bedrocktool/utils"
|
"github.com/bedrock-tool/bedrocktool/utils"
|
||||||
|
|
||||||
"github.com/sandertv/gophertunnel/minecraft"
|
"github.com/sandertv/gophertunnel/minecraft"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -43,7 +42,7 @@ func (c *SkinCMD) Execute(ctx context.Context, ui utils.UI) error {
|
||||||
}))
|
}))
|
||||||
proxy.AddHandler(&utils.ProxyHandler{
|
proxy.AddHandler(&utils.ProxyHandler{
|
||||||
Name: "Skin CMD",
|
Name: "Skin CMD",
|
||||||
OnClientConnect: func(conn *minecraft.Conn) {
|
OnClientConnect: func(conn minecraft.IConn) {
|
||||||
ui.Message(messages.SetUIState(messages.UIStateConnecting))
|
ui.Message(messages.SetUIState(messages.UIStateConnecting))
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
161
utils/proxy.go
161
utils/proxy.go
|
@ -67,8 +67,8 @@ type ProxyHandler struct {
|
||||||
PacketCB func(pk packet.Packet, toServer bool, timeReceived time.Time) (packet.Packet, error)
|
PacketCB func(pk packet.Packet, toServer bool, timeReceived time.Time) (packet.Packet, error)
|
||||||
|
|
||||||
// called after client connected
|
// called after client connected
|
||||||
OnClientConnect func(conn *minecraft.Conn)
|
OnClientConnect func(conn minecraft.IConn)
|
||||||
SecondaryClientCB func(conn *minecraft.Conn)
|
SecondaryClientCB func(conn minecraft.IConn)
|
||||||
|
|
||||||
// called after game started
|
// called after game started
|
||||||
ConnectCB func(err error) bool
|
ConnectCB func(err error) bool
|
||||||
|
@ -78,8 +78,8 @@ type ProxyHandler struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProxyContext struct {
|
type ProxyContext struct {
|
||||||
Server *minecraft.Conn
|
Server minecraft.IConn
|
||||||
Client *minecraft.Conn
|
Client minecraft.IConn
|
||||||
clientAddr net.Addr
|
clientAddr net.Addr
|
||||||
Listener *minecraft.Listener
|
Listener *minecraft.Listener
|
||||||
|
|
||||||
|
@ -233,7 +233,7 @@ func (p *ProxyContext) CommandHandlerPacketCB(pk packet.Packet, toServer bool, _
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *ProxyContext) proxyLoop(ctx context.Context, toServer bool) error {
|
func (p *ProxyContext) proxyLoop(ctx context.Context, toServer bool) error {
|
||||||
var c1, c2 *minecraft.Conn
|
var c1, c2 minecraft.IConn
|
||||||
if toServer {
|
if toServer {
|
||||||
c1 = p.Client
|
c1 = p.Client
|
||||||
c2 = p.Server
|
c2 = p.Server
|
||||||
|
@ -283,6 +283,56 @@ func (p *ProxyContext) IsClient(addr net.Addr) bool {
|
||||||
|
|
||||||
var NewDebugLogger func(bool) *ProxyHandler
|
var NewDebugLogger func(bool) *ProxyHandler
|
||||||
|
|
||||||
|
func (p *ProxyContext) connectClient(ctx context.Context, serverAddress string, cdpp **login.ClientData) (err error) {
|
||||||
|
GetTokenSource() // ask for login before listening
|
||||||
|
|
||||||
|
var packs []*resource.Pack
|
||||||
|
if Options.Preload {
|
||||||
|
logrus.Info(locale.Loc("preloading_packs", nil))
|
||||||
|
serverConn, err := connectServer(ctx, serverAddress, nil, true, func(header packet.Header, payload []byte, src, dst net.Addr) {})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf(locale.Loc("failed_to_connect", locale.Strmap{"Address": serverAddress, "Err": err}))
|
||||||
|
}
|
||||||
|
serverConn.Close()
|
||||||
|
packs = serverConn.ResourcePacks()
|
||||||
|
logrus.Infof(locale.Locm("pack_count_loaded", locale.Strmap{"Count": len(packs)}, len(packs)))
|
||||||
|
}
|
||||||
|
|
||||||
|
status := minecraft.NewStatusProvider(fmt.Sprintf("%s Proxy", serverAddress))
|
||||||
|
p.Listener, err = minecraft.ListenConfig{
|
||||||
|
StatusProvider: status,
|
||||||
|
ResourcePacks: packs,
|
||||||
|
AcceptedProtocols: []minecraft.Protocol{
|
||||||
|
//dummyProto{id: 567, ver: "1.19.60"},
|
||||||
|
},
|
||||||
|
}.Listen("raknet", ":19132")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
logrus.Infof(locale.Loc("listening_on", locale.Strmap{"Address": p.Listener.Addr()}))
|
||||||
|
logrus.Infof(locale.Loc("help_connect", nil))
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
<-ctx.Done()
|
||||||
|
p.Listener.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
c, err := p.Listener.Accept()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.Client = c.(*minecraft.Conn)
|
||||||
|
cd := p.Client.ClientData()
|
||||||
|
*cdpp = &cd
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ProxyContext) connectServer(ctx context.Context, serverAddress string, cdp *login.ClientData, packetFunc PacketFunc) (err error) {
|
||||||
|
p.Server, err = connectServer(ctx, serverAddress, cdp, p.AlwaysGetPacks, packetFunc)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
func (p *ProxyContext) Run(ctx context.Context, serverAddress, name string) (err error) {
|
func (p *ProxyContext) Run(ctx context.Context, serverAddress, name string) (err error) {
|
||||||
if Options.Debug || Options.ExtraDebug {
|
if Options.Debug || Options.ExtraDebug {
|
||||||
p.AddHandler(NewDebugLogger(Options.ExtraDebug))
|
p.AddHandler(NewDebugLogger(Options.ExtraDebug))
|
||||||
|
@ -301,54 +351,38 @@ func (p *ProxyContext) Run(ctx context.Context, serverAddress, name string) (err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
for _, handler := range p.handlers {
|
||||||
|
if handler.OnEnd != nil {
|
||||||
|
handler.OnEnd()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
isReplay := false
|
||||||
if strings.HasPrefix(serverAddress, "PCAP!") {
|
if strings.HasPrefix(serverAddress, "PCAP!") {
|
||||||
return createReplayConnection(ctx, serverAddress[5:], p)
|
isReplay = true
|
||||||
}
|
}
|
||||||
|
|
||||||
GetTokenSource() // ask for login before listening
|
|
||||||
|
|
||||||
var cdp *login.ClientData = nil
|
var cdp *login.ClientData = nil
|
||||||
if p.WithClient {
|
if p.WithClient && !isReplay {
|
||||||
var packs []*resource.Pack
|
err = p.connectClient(ctx, serverAddress, &cdp)
|
||||||
if Options.Preload {
|
|
||||||
logrus.Info(locale.Loc("preloading_packs", nil))
|
|
||||||
serverConn, err := connectServer(ctx, serverAddress, nil, true, func(header packet.Header, payload []byte, src, dst net.Addr) {})
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf(locale.Loc("failed_to_connect", locale.Strmap{"Address": serverAddress, "Err": err}))
|
|
||||||
}
|
|
||||||
serverConn.Close()
|
|
||||||
packs = serverConn.ResourcePacks()
|
|
||||||
logrus.Infof(locale.Locm("pack_count_loaded", locale.Strmap{"Count": len(packs)}, len(packs)))
|
|
||||||
}
|
|
||||||
|
|
||||||
status := minecraft.NewStatusProvider(fmt.Sprintf("%s Proxy", name))
|
|
||||||
p.Listener, err = minecraft.ListenConfig{
|
|
||||||
StatusProvider: status,
|
|
||||||
ResourcePacks: packs,
|
|
||||||
AcceptedProtocols: []minecraft.Protocol{
|
|
||||||
//dummyProto{id: 567, ver: "1.19.60"},
|
|
||||||
},
|
|
||||||
}.Listen("raknet", ":19132")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
if p.Client != nil {
|
if p.Listener != nil {
|
||||||
p.Listener.Disconnect(p.Client, DisconnectReason)
|
if p.Client != nil {
|
||||||
|
p.Listener.Disconnect(p.Client.(*minecraft.Conn), DisconnectReason)
|
||||||
|
}
|
||||||
|
p.Listener.Close()
|
||||||
}
|
}
|
||||||
p.Listener.Close()
|
|
||||||
}()
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
logrus.Infof(locale.Loc("listening_on", locale.Strmap{"Address": p.Listener.Addr()}))
|
if p.CustomClientData != nil {
|
||||||
logrus.Infof(locale.Loc("help_connect", nil))
|
cdp = p.CustomClientData
|
||||||
|
|
||||||
c, err := p.Listener.Accept()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
p.Client = c.(*minecraft.Conn)
|
|
||||||
cd := p.Client.ClientData()
|
|
||||||
cdp = &cd
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, handler := range p.handlers {
|
for _, handler := range p.handlers {
|
||||||
|
@ -358,11 +392,7 @@ func (p *ProxyContext) Run(ctx context.Context, serverAddress, name string) (err
|
||||||
handler.OnClientConnect(p.Client)
|
handler.OnClientConnect(p.Client)
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.CustomClientData != nil {
|
packetFunc := func(header packet.Header, payload []byte, src, dst net.Addr) {
|
||||||
cdp = p.CustomClientData
|
|
||||||
}
|
|
||||||
|
|
||||||
p.Server, err = connectServer(ctx, serverAddress, cdp, p.AlwaysGetPacks, func(header packet.Header, payload []byte, src, dst net.Addr) {
|
|
||||||
if header.PacketID == packet.IDRequestNetworkSettings {
|
if header.PacketID == packet.IDRequestNetworkSettings {
|
||||||
p.clientAddr = src
|
p.clientAddr = src
|
||||||
}
|
}
|
||||||
|
@ -372,7 +402,16 @@ func (p *ProxyContext) Run(ctx context.Context, serverAddress, name string) (err
|
||||||
}
|
}
|
||||||
handler.PacketFunc(header, payload, src, dst)
|
handler.PacketFunc(header, payload, src, dst)
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
|
||||||
|
if isReplay {
|
||||||
|
p.Server, err = createReplayConnector(serverAddress[5:], packetFunc)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = p.connectServer(ctx, serverAddress, cdp, packetFunc)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
for _, handler := range p.handlers {
|
for _, handler := range p.handlers {
|
||||||
if handler.ConnectCB == nil {
|
if handler.ConnectCB == nil {
|
||||||
|
@ -415,26 +454,22 @@ func (p *ProxyContext) Run(ctx context.Context, serverAddress, name string) (err
|
||||||
}
|
}
|
||||||
|
|
||||||
wg := sync.WaitGroup{}
|
wg := sync.WaitGroup{}
|
||||||
// server to client
|
doProxy := func(client bool) {
|
||||||
wg.Add(1)
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
if err := p.proxyLoop(ctx, false); err != nil {
|
if err := p.proxyLoop(ctx, client); err != nil {
|
||||||
logrus.Error(err)
|
logrus.Error(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}()
|
}
|
||||||
|
|
||||||
|
// server to client
|
||||||
|
wg.Add(1)
|
||||||
|
go doProxy(false)
|
||||||
|
|
||||||
// client to server
|
// client to server
|
||||||
if p.Client != nil {
|
if p.Client != nil {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func() {
|
go doProxy(true)
|
||||||
defer wg.Done()
|
|
||||||
if err := p.proxyLoop(ctx, true); err != nil {
|
|
||||||
logrus.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
wantSecondary := fp.Filter(func(handler *ProxyHandler) bool {
|
wantSecondary := fp.Filter(func(handler *ProxyHandler) bool {
|
||||||
|
@ -458,11 +493,5 @@ func (p *ProxyContext) Run(ctx context.Context, serverAddress, name string) (err
|
||||||
}
|
}
|
||||||
|
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
|
|
||||||
for _, handler := range p.handlers {
|
|
||||||
if handler.OnEnd != nil {
|
|
||||||
handler.OnEnd()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
528
utils/replay.go
528
utils/replay.go
|
@ -4,15 +4,18 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sandertv/gophertunnel/minecraft"
|
"github.com/sandertv/gophertunnel/minecraft"
|
||||||
"github.com/sandertv/gophertunnel/minecraft/protocol"
|
"github.com/sandertv/gophertunnel/minecraft/protocol/login"
|
||||||
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
|
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
|
||||||
|
"github.com/sandertv/gophertunnel/minecraft/resource"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -34,171 +37,430 @@ func WriteReplayHeader(f io.Writer) {
|
||||||
binary.Write(f, binary.LittleEndian, &header)
|
binary.Write(f, binary.LittleEndian, &header)
|
||||||
}
|
}
|
||||||
|
|
||||||
func createReplayConnection(ctx context.Context, filename string, proxy *ProxyContext) error {
|
type replayConnector struct {
|
||||||
logrus.Infof("Reading replay %s", filename)
|
f *os.File
|
||||||
|
totalSize int64
|
||||||
|
ver int
|
||||||
|
|
||||||
f, err := os.Open(filename)
|
packets chan packet.Packet
|
||||||
if err != nil {
|
spawn chan struct{}
|
||||||
return err
|
close chan struct{}
|
||||||
}
|
once sync.Once
|
||||||
stat, err := f.Stat()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
totalSize := stat.Size()
|
|
||||||
|
|
||||||
// default version is version 1, since that didnt have a header
|
pool packet.Pool
|
||||||
ver := 1
|
proto minecraft.Protocol
|
||||||
|
clientData login.ClientData
|
||||||
|
|
||||||
|
gameData minecraft.GameData
|
||||||
|
|
||||||
|
packetFunc PacketFunc
|
||||||
|
|
||||||
|
downloadingPacks map[string]*downloadingPack
|
||||||
|
resourcePacks []*resource.Pack
|
||||||
|
}
|
||||||
|
|
||||||
|
// downloadingPack is a resource pack that is being downloaded by a client connection.
|
||||||
|
type downloadingPack struct {
|
||||||
|
buf *bytes.Buffer
|
||||||
|
chunkSize uint32
|
||||||
|
size uint64
|
||||||
|
expectedIndex uint32
|
||||||
|
newFrag chan []byte
|
||||||
|
contentKey string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *replayConnector) readHeader() error {
|
||||||
|
r.ver = 1
|
||||||
|
|
||||||
magic := make([]byte, 4)
|
magic := make([]byte, 4)
|
||||||
io.ReadAtLeast(f, magic, 4)
|
io.ReadAtLeast(r.f, magic, 4)
|
||||||
if bytes.Equal(magic, replayMagic) {
|
if bytes.Equal(magic, replayMagic) {
|
||||||
var header replayHeader
|
var header replayHeader
|
||||||
if err := binary.Read(f, binary.LittleEndian, &header); err != nil {
|
if err := binary.Read(r.f, binary.LittleEndian, &header); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
ver = int(header.Version)
|
r.ver = int(header.Version)
|
||||||
} else {
|
} else {
|
||||||
logrus.Info("Version 1 capture assumed.")
|
logrus.Info("Version 1 capture assumed.")
|
||||||
f.Seek(-4, io.SeekCurrent)
|
r.f.Seek(-4, io.SeekCurrent)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *replayConnector) readPacket() (payload []byte, toServer bool, err error) {
|
||||||
|
var magic uint32 = 0
|
||||||
|
var packetLength uint32 = 0
|
||||||
|
timeReceived := time.Now()
|
||||||
|
|
||||||
|
offset, _ := r.f.Seek(0, io.SeekCurrent)
|
||||||
|
if offset == r.totalSize {
|
||||||
|
logrus.Info("Reached End")
|
||||||
|
return nil, toServer, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
server, client := &net.UDPAddr{
|
binary.Read(r.f, binary.LittleEndian, &magic)
|
||||||
IP: net.IPv4(1, 1, 1, 1),
|
if magic != 0xAAAAAAAA {
|
||||||
}, &net.UDPAddr{
|
return nil, toServer, fmt.Errorf("wrong Magic")
|
||||||
IP: net.IPv4(2, 2, 2, 2),
|
}
|
||||||
|
binary.Read(r.f, binary.LittleEndian, &packetLength)
|
||||||
|
binary.Read(r.f, binary.LittleEndian, &toServer)
|
||||||
|
if r.ver >= 2 {
|
||||||
|
var timeMs int64
|
||||||
|
binary.Read(r.f, binary.LittleEndian, &timeMs)
|
||||||
|
timeReceived = time.UnixMilli(timeMs)
|
||||||
}
|
}
|
||||||
proxy.clientAddr = client
|
|
||||||
|
|
||||||
proxy.Server = minecraft.NewConn()
|
payload = make([]byte, packetLength)
|
||||||
|
n, err := r.f.Read(payload)
|
||||||
|
if err != nil {
|
||||||
|
return nil, toServer, err
|
||||||
|
}
|
||||||
|
if n != int(packetLength) {
|
||||||
|
return nil, toServer, fmt.Errorf("truncated")
|
||||||
|
}
|
||||||
|
|
||||||
|
var magic2 uint32
|
||||||
|
binary.Read(r.f, binary.LittleEndian, &magic2)
|
||||||
|
if magic2 != 0xBBBBBBBB {
|
||||||
|
return nil, toServer, fmt.Errorf("wrong Magic2")
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = timeReceived
|
||||||
|
return payload, toServer, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *replayConnector) handleLoginSequence(pk packet.Packet) (bool, error) {
|
||||||
|
switch pk := pk.(type) {
|
||||||
|
case *packet.StartGame:
|
||||||
|
r.SetGameData(minecraft.GameData{
|
||||||
|
WorldName: pk.WorldName,
|
||||||
|
WorldSeed: pk.WorldSeed,
|
||||||
|
Difficulty: pk.Difficulty,
|
||||||
|
EntityUniqueID: pk.EntityUniqueID,
|
||||||
|
EntityRuntimeID: pk.EntityRuntimeID,
|
||||||
|
PlayerGameMode: pk.PlayerGameMode,
|
||||||
|
PersonaDisabled: pk.PersonaDisabled,
|
||||||
|
CustomSkinsDisabled: pk.CustomSkinsDisabled,
|
||||||
|
BaseGameVersion: pk.BaseGameVersion,
|
||||||
|
PlayerPosition: pk.PlayerPosition,
|
||||||
|
Pitch: pk.Pitch,
|
||||||
|
Yaw: pk.Yaw,
|
||||||
|
Dimension: pk.Dimension,
|
||||||
|
WorldSpawn: pk.WorldSpawn,
|
||||||
|
EditorWorld: pk.EditorWorld,
|
||||||
|
WorldGameMode: pk.WorldGameMode,
|
||||||
|
GameRules: pk.GameRules,
|
||||||
|
Time: pk.Time,
|
||||||
|
ServerBlockStateChecksum: pk.ServerBlockStateChecksum,
|
||||||
|
CustomBlocks: pk.Blocks,
|
||||||
|
Items: pk.Items,
|
||||||
|
PlayerMovementSettings: pk.PlayerMovementSettings,
|
||||||
|
ServerAuthoritativeInventory: pk.ServerAuthoritativeInventory,
|
||||||
|
Experiments: pk.Experiments,
|
||||||
|
ClientSideGeneration: pk.ClientSideGeneration,
|
||||||
|
ChatRestrictionLevel: pk.ChatRestrictionLevel,
|
||||||
|
DisablePlayerInteractions: pk.DisablePlayerInteractions,
|
||||||
|
})
|
||||||
|
|
||||||
|
case *packet.ResourcePacksInfo:
|
||||||
|
for _, pack := range pk.TexturePacks {
|
||||||
|
r.downloadingPacks[pack.UUID] = &downloadingPack{
|
||||||
|
size: pack.Size,
|
||||||
|
buf: bytes.NewBuffer(make([]byte, 0, pack.Size)),
|
||||||
|
newFrag: make(chan []byte),
|
||||||
|
contentKey: pack.ContentKey,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case *packet.ResourcePackDataInfo:
|
||||||
|
pack, ok := r.downloadingPacks[pk.UUID]
|
||||||
|
if !ok {
|
||||||
|
// We either already downloaded the pack or we got sent an invalid UUID, that did not match any pack
|
||||||
|
// sent in the ResourcePacksInfo packet.
|
||||||
|
return false, fmt.Errorf("unknown pack to download with UUID %v", pk.UUID)
|
||||||
|
}
|
||||||
|
if pack.size != pk.Size {
|
||||||
|
// Size mismatch: The ResourcePacksInfo packet had a size for the pack that did not match with the
|
||||||
|
// size sent here.
|
||||||
|
logrus.Printf("pack %v had a different size in the ResourcePacksInfo packet than the ResourcePackDataInfo packet\n", pk.UUID)
|
||||||
|
pack.size = pk.Size
|
||||||
|
}
|
||||||
|
pack.chunkSize = pk.DataChunkSize
|
||||||
|
|
||||||
|
chunkCount := uint32(pk.Size / uint64(pk.DataChunkSize))
|
||||||
|
if pk.Size%uint64(pk.DataChunkSize) != 0 {
|
||||||
|
chunkCount++
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for i := uint32(0); i < chunkCount; i++ {
|
||||||
|
select {
|
||||||
|
case <-r.close:
|
||||||
|
return
|
||||||
|
case frag := <-pack.newFrag:
|
||||||
|
// Write the fragment to the full buffer of the downloading resource pack.
|
||||||
|
_, _ = pack.buf.Write(frag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if pack.buf.Len() != int(pack.size) {
|
||||||
|
logrus.Printf("incorrect resource pack size: expected %v, but got %v\n", pack.size, pack.buf.Len())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// First parse the resource pack from the total byte buffer we obtained.
|
||||||
|
newPack, err := resource.FromBytes(pack.buf.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
logrus.Printf("invalid full resource pack data for UUID %v: %v\n", pk.UUID, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
r.resourcePacks = append(r.resourcePacks, newPack.WithContentKey(pack.contentKey))
|
||||||
|
}()
|
||||||
|
|
||||||
|
case *packet.ResourcePackChunkData:
|
||||||
|
pack, ok := r.downloadingPacks[pk.UUID]
|
||||||
|
if !ok {
|
||||||
|
// We haven't received a ResourcePackDataInfo packet from the server, so we can't use this data to
|
||||||
|
// download a resource pack.
|
||||||
|
return false, fmt.Errorf("resource pack chunk data for resource pack that was not being downloaded")
|
||||||
|
}
|
||||||
|
lastData := pack.buf.Len()+int(pack.chunkSize) >= int(pack.size)
|
||||||
|
if !lastData && uint32(len(pk.Data)) != pack.chunkSize {
|
||||||
|
// The chunk data didn't have the full size and wasn't the last data to be sent for the resource pack,
|
||||||
|
// meaning we got too little data.
|
||||||
|
return false, fmt.Errorf("resource pack chunk data had a length of %v, but expected %v", len(pk.Data), pack.chunkSize)
|
||||||
|
}
|
||||||
|
if pk.ChunkIndex != pack.expectedIndex {
|
||||||
|
return false, fmt.Errorf("resource pack chunk data had chunk index %v, but expected %v", pk.ChunkIndex, pack.expectedIndex)
|
||||||
|
}
|
||||||
|
pack.expectedIndex++
|
||||||
|
pack.newFrag <- pk.Data
|
||||||
|
|
||||||
|
case *packet.SetLocalPlayerAsInitialised:
|
||||||
|
if pk.EntityRuntimeID != r.gameData.EntityRuntimeID {
|
||||||
|
return false, fmt.Errorf("entity runtime ID mismatch: entity runtime ID in StartGame and SetLocalPlayerAsInitialised packets should be equal")
|
||||||
|
}
|
||||||
|
close(r.spawn)
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *replayConnector) loop() {
|
||||||
gameStarted := false
|
gameStarted := false
|
||||||
i := 0
|
defer r.Close()
|
||||||
for {
|
for {
|
||||||
i += 1
|
payload, toServer, err := r.readPacket()
|
||||||
var magic uint32 = 0
|
|
||||||
var packetLength uint32 = 0
|
|
||||||
var toServer bool = false
|
|
||||||
timeReceived := time.Now()
|
|
||||||
|
|
||||||
offset, _ := f.Seek(0, io.SeekCurrent)
|
|
||||||
if offset == totalSize {
|
|
||||||
logrus.Info("Reached End")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
binary.Read(f, binary.LittleEndian, &magic)
|
|
||||||
if magic != 0xAAAAAAAA {
|
|
||||||
return fmt.Errorf("wrong Magic")
|
|
||||||
}
|
|
||||||
binary.Read(f, binary.LittleEndian, &packetLength)
|
|
||||||
binary.Read(f, binary.LittleEndian, &toServer)
|
|
||||||
if ver >= 2 {
|
|
||||||
var timeMs int64
|
|
||||||
binary.Read(f, binary.LittleEndian, &timeMs)
|
|
||||||
timeReceived = time.UnixMilli(timeMs)
|
|
||||||
}
|
|
||||||
|
|
||||||
payload := make([]byte, packetLength)
|
|
||||||
n, err := f.Read(payload)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Error(err)
|
logrus.Error(err)
|
||||||
break
|
|
||||||
}
|
}
|
||||||
if n != int(packetLength) {
|
if payload == nil {
|
||||||
return fmt.Errorf("truncated %d", i)
|
return
|
||||||
|
}
|
||||||
|
var src, dst = r.RemoteAddr(), r.LocalAddr()
|
||||||
|
if toServer {
|
||||||
|
src, dst = r.LocalAddr(), r.RemoteAddr()
|
||||||
}
|
}
|
||||||
|
|
||||||
var magic2 uint32
|
pkData, err := minecraft.ParseData(payload, r, src, dst)
|
||||||
binary.Read(f, binary.LittleEndian, &magic2)
|
|
||||||
if magic2 != 0xBBBBBBBB {
|
|
||||||
return fmt.Errorf("wrong Magic2")
|
|
||||||
}
|
|
||||||
|
|
||||||
pkData, err := minecraft.ParseData(payload, proxy.Server)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
logrus.Error(err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
pks, err := pkData.Decode(proxy.Server)
|
pks, err := pkData.Decode(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Error(err)
|
logrus.Error(err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, pk := range pks {
|
for _, pk := range pks {
|
||||||
f := bytes.NewBuffer(nil)
|
if !gameStarted {
|
||||||
b := protocol.NewWriter(f, 0)
|
gameStarted, _ = r.handleLoginSequence(pk)
|
||||||
pk.Marshal(b)
|
|
||||||
|
|
||||||
hdr := packet.Header{PacketID: pk.ID()}
|
|
||||||
|
|
||||||
var src, dst net.Addr
|
|
||||||
if toServer {
|
|
||||||
src = client
|
|
||||||
dst = server
|
|
||||||
} else {
|
} else {
|
||||||
src = server
|
r.packets <- pk
|
||||||
dst = client
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, handler := range proxy.handlers {
|
|
||||||
if handler.PacketFunc != nil {
|
|
||||||
handler.PacketFunc(hdr, f.Bytes(), src, dst)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if gameStarted {
|
|
||||||
for _, handler := range proxy.handlers {
|
|
||||||
if handler.PacketCB != nil {
|
|
||||||
handler.PacketCB(pk, toServer, timeReceived)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
switch pk := pk.(type) {
|
|
||||||
case *packet.StartGame:
|
|
||||||
proxy.Server.SetGameData(minecraft.GameData{
|
|
||||||
WorldName: pk.WorldName,
|
|
||||||
WorldSeed: pk.WorldSeed,
|
|
||||||
Difficulty: pk.Difficulty,
|
|
||||||
EntityUniqueID: pk.EntityUniqueID,
|
|
||||||
EntityRuntimeID: pk.EntityRuntimeID,
|
|
||||||
PlayerGameMode: pk.PlayerGameMode,
|
|
||||||
PersonaDisabled: pk.PersonaDisabled,
|
|
||||||
CustomSkinsDisabled: pk.CustomSkinsDisabled,
|
|
||||||
BaseGameVersion: pk.BaseGameVersion,
|
|
||||||
PlayerPosition: pk.PlayerPosition,
|
|
||||||
Pitch: pk.Pitch,
|
|
||||||
Yaw: pk.Yaw,
|
|
||||||
Dimension: pk.Dimension,
|
|
||||||
WorldSpawn: pk.WorldSpawn,
|
|
||||||
EditorWorld: pk.EditorWorld,
|
|
||||||
WorldGameMode: pk.WorldGameMode,
|
|
||||||
GameRules: pk.GameRules,
|
|
||||||
Time: pk.Time,
|
|
||||||
ServerBlockStateChecksum: pk.ServerBlockStateChecksum,
|
|
||||||
CustomBlocks: pk.Blocks,
|
|
||||||
Items: pk.Items,
|
|
||||||
PlayerMovementSettings: pk.PlayerMovementSettings,
|
|
||||||
ServerAuthoritativeInventory: pk.ServerAuthoritativeInventory,
|
|
||||||
Experiments: pk.Experiments,
|
|
||||||
ClientSideGeneration: pk.ClientSideGeneration,
|
|
||||||
ChatRestrictionLevel: pk.ChatRestrictionLevel,
|
|
||||||
DisablePlayerInteractions: pk.DisablePlayerInteractions,
|
|
||||||
})
|
|
||||||
gameStarted = true
|
|
||||||
for _, handler := range proxy.handlers {
|
|
||||||
if handler.ConnectCB != nil {
|
|
||||||
handler.ConnectCB(nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, handler := range proxy.handlers {
|
}
|
||||||
if handler.OnEnd != nil {
|
|
||||||
handler.OnEnd()
|
func createReplayConnector(filename string, packetFunc PacketFunc) (r *replayConnector, err error) {
|
||||||
}
|
r = &replayConnector{
|
||||||
|
pool: packet.NewPool(),
|
||||||
|
proto: minecraft.DefaultProtocol,
|
||||||
|
packetFunc: packetFunc,
|
||||||
|
spawn: make(chan struct{}),
|
||||||
|
close: make(chan struct{}),
|
||||||
|
packets: make(chan packet.Packet),
|
||||||
|
downloadingPacks: make(map[string]*downloadingPack),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logrus.Infof("Reading replay %s", filename)
|
||||||
|
|
||||||
|
r.f, err = os.Open(filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
stat, err := r.f.Stat()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
r.totalSize = stat.Size()
|
||||||
|
|
||||||
|
err = r.readHeader()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
go r.loop()
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *replayConnector) Close() error {
|
||||||
|
r.once.Do(func() {
|
||||||
|
close(r.close)
|
||||||
|
close(r.packets)
|
||||||
|
})
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *replayConnector) Authenticated() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *replayConnector) ChunkRadius() int {
|
||||||
|
return 80
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *replayConnector) ClientCacheEnabled() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *replayConnector) ClientData() login.ClientData {
|
||||||
|
return r.clientData
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *replayConnector) DoSpawn() error {
|
||||||
|
return r.DoSpawnContext(context.Background())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *replayConnector) DoSpawnContext(ctx context.Context) error {
|
||||||
|
select {
|
||||||
|
case <-r.close:
|
||||||
|
return errors.New("do spawn")
|
||||||
|
case <-ctx.Done():
|
||||||
|
return errors.New("do spawn")
|
||||||
|
case <-r.spawn:
|
||||||
|
// Conn was spawned successfully.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *replayConnector) DoSpawnTimeout(timeout time.Duration) error {
|
||||||
|
c, cancel := context.WithTimeout(context.Background(), timeout)
|
||||||
|
defer cancel()
|
||||||
|
return r.DoSpawnContext(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *replayConnector) Flush() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *replayConnector) GameData() minecraft.GameData {
|
||||||
|
return r.gameData
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *replayConnector) IdentityData() login.IdentityData {
|
||||||
|
return login.IdentityData{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *replayConnector) Latency() time.Duration {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *replayConnector) LocalAddr() net.Addr {
|
||||||
|
return &net.UDPAddr{
|
||||||
|
IP: net.IPv4(1, 1, 1, 1),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *replayConnector) Read(b []byte) (n int, err error) {
|
||||||
|
return 0, errors.New("not Implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *replayConnector) ReadPacket() (pk packet.Packet, err error) {
|
||||||
|
select {
|
||||||
|
case <-r.close:
|
||||||
|
return nil, net.ErrClosed
|
||||||
|
case p, ok := <-r.packets:
|
||||||
|
if !ok {
|
||||||
|
err = net.ErrClosed
|
||||||
|
}
|
||||||
|
return p, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *replayConnector) Write(b []byte) (n int, err error) {
|
||||||
|
return 0, errors.New("not Implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *replayConnector) WritePacket(pk packet.Packet) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *replayConnector) RemoteAddr() net.Addr {
|
||||||
|
return &net.UDPAddr{
|
||||||
|
IP: net.IPv4(2, 2, 2, 2),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *replayConnector) ResourcePacks() []*resource.Pack {
|
||||||
|
return r.resourcePacks
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *replayConnector) SetGameData(data minecraft.GameData) {
|
||||||
|
r.gameData = data
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *replayConnector) StartGame(data minecraft.GameData) error {
|
||||||
|
return r.StartGameContext(context.Background(), data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *replayConnector) StartGameContext(ctx context.Context, data minecraft.GameData) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *replayConnector) StartGameTimeout(data minecraft.GameData, timeout time.Duration) error {
|
||||||
|
c, cancel := context.WithTimeout(context.Background(), timeout)
|
||||||
|
defer cancel()
|
||||||
|
return r.StartGameContext(c, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *replayConnector) SetDeadline(t time.Time) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *replayConnector) SetReadDeadline(t time.Time) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *replayConnector) SetWriteDeadline(time.Time) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *replayConnector) Pool() packet.Pool {
|
||||||
|
return r.pool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *replayConnector) ShieldID() int32 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *replayConnector) Proto() minecraft.Protocol {
|
||||||
|
return r.proto
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *replayConnector) PacketFunc(header packet.Header, payload []byte, src, dst net.Addr) {
|
||||||
|
if r.packetFunc != nil {
|
||||||
|
r.packetFunc(header, payload, src, dst)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -60,7 +60,7 @@ var PackFromBase = func(pack *resource.Pack) Pack {
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetPacks(server *minecraft.Conn) (packs []Pack, err error) {
|
func GetPacks(server minecraft.IConn) (packs []Pack, err error) {
|
||||||
for _, pack := range server.ResourcePacks() {
|
for _, pack := range server.ResourcePacks() {
|
||||||
pack := PackFromBase(pack)
|
pack := PackFromBase(pack)
|
||||||
if pack.Encrypted() && pack.CanDecrypt() {
|
if pack.Encrypted() && pack.CanDecrypt() {
|
||||||
|
|
|
@ -76,7 +76,7 @@ func connectServer(ctx context.Context, address string, ClientData *login.Client
|
||||||
return serverConn, nil
|
return serverConn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func spawnConn(ctx context.Context, clientConn *minecraft.Conn, serverConn *minecraft.Conn, gd minecraft.GameData) error {
|
func spawnConn(ctx context.Context, clientConn minecraft.IConn, serverConn minecraft.IConn, gd minecraft.GameData) error {
|
||||||
wg := sync.WaitGroup{}
|
wg := sync.WaitGroup{}
|
||||||
errs := make(chan error, 2)
|
errs := make(chan error, 2)
|
||||||
if clientConn != nil {
|
if clientConn != nil {
|
||||||
|
|
Loading…
Reference in New Issue