bedrocktool/utils/proxy.go

469 lines
11 KiB
Go
Raw Normal View History

2022-09-04 14:26:32 +00:00
package utils
2022-09-03 17:32:30 +00:00
import (
"context"
2023-02-12 21:22:44 +00:00
"encoding/base64"
"encoding/json"
2022-09-03 17:32:30 +00:00
"errors"
"fmt"
2022-09-04 13:24:55 +00:00
"net"
2023-02-12 21:22:44 +00:00
"os"
2023-04-02 14:49:24 +00:00
"reflect"
2022-09-03 22:56:40 +00:00
"strings"
2022-09-03 17:32:30 +00:00
"sync"
2023-02-08 11:00:36 +00:00
"time"
2022-09-03 17:32:30 +00:00
"github.com/bedrock-tool/bedrocktool/locale"
2023-04-02 14:49:24 +00:00
"github.com/repeale/fp-go"
2022-09-03 17:32:30 +00:00
"github.com/sandertv/gophertunnel/minecraft"
2022-09-04 00:24:58 +00:00
"github.com/sandertv/gophertunnel/minecraft/protocol"
"github.com/sandertv/gophertunnel/minecraft/protocol/login"
2022-09-03 17:32:30 +00:00
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
"github.com/sandertv/gophertunnel/minecraft/resource"
"github.com/sirupsen/logrus"
)
2023-01-29 21:20:13 +00:00
var DisconnectReason = "Connection lost"
2022-09-03 17:32:30 +00:00
2023-03-25 21:19:14 +00:00
/*
2023-02-18 19:39:45 +00:00
type dummyProto struct {
id int32
ver string
}
func (p dummyProto) ID() int32 { return p.id }
func (p dummyProto) Ver() string { return p.ver }
func (p dummyProto) Packets() packet.Pool { return packet.NewPool() }
func (p dummyProto) ConvertToLatest(pk packet.Packet, _ *minecraft.Conn) []packet.Packet {
return []packet.Packet{pk}
}
func (p dummyProto) ConvertFromLatest(pk packet.Packet, _ *minecraft.Conn) []packet.Packet {
return []packet.Packet{pk}
}
2023-03-25 21:19:14 +00:00
*/
2023-02-18 19:39:45 +00:00
2023-02-12 21:22:44 +00:00
type (
2023-04-02 14:49:24 +00:00
PacketFunc func(header packet.Header, payload []byte, src, dst net.Addr)
IngameCommand struct {
2023-02-12 21:22:44 +00:00
Exec func(cmdline []string) bool
Cmd protocol.Command
}
)
2022-09-03 17:32:30 +00:00
2023-04-02 14:49:24 +00:00
type ProxyHandler struct {
Name string
2023-04-12 12:10:45 +00:00
ProxyRef func(pc *ProxyContext)
2023-04-02 14:49:24 +00:00
//
2023-04-04 00:42:17 +00:00
AddressAndName func(address, hostname string) error
// called to change game data
2023-04-12 12:10:45 +00:00
GameDataModifier func(gd *minecraft.GameData)
2023-04-02 14:49:24 +00:00
// called for every packet
PacketFunc func(header packet.Header, payload []byte, src, dst net.Addr)
// called on every packet after login
PacketCB func(pk packet.Packet, toServer bool, timeReceived time.Time) (packet.Packet, error)
// called after client connected
OnClientConnect func(conn *minecraft.Conn)
SecondaryClientCB func(conn *minecraft.Conn)
// called after game started
ConnectCB func(err error) bool
// called when the proxy stops
OnEnd func()
}
2022-09-03 17:32:30 +00:00
type ProxyContext struct {
2023-04-04 01:51:13 +00:00
Server *minecraft.Conn
Client *minecraft.Conn
clientAddr net.Addr
Listener *minecraft.Listener
2023-02-12 21:22:44 +00:00
AlwaysGetPacks bool
WithClient bool
2023-02-22 15:48:24 +00:00
IgnoreDisconnect bool
2023-02-12 21:22:44 +00:00
CustomClientData *login.ClientData
2022-09-04 13:24:55 +00:00
2023-04-04 01:51:13 +00:00
commands map[string]IngameCommand
2023-04-02 14:49:24 +00:00
handlers []*ProxyHandler
2022-09-03 17:32:30 +00:00
}
2022-09-03 22:56:40 +00:00
2023-03-06 01:03:31 +00:00
func NewProxy() (*ProxyContext, error) {
2023-02-12 21:22:44 +00:00
p := &ProxyContext{
2023-02-22 15:48:24 +00:00
commands: make(map[string]IngameCommand),
2023-04-10 17:55:08 +00:00
AlwaysGetPacks: false,
2023-02-22 15:48:24 +00:00
WithClient: true,
IgnoreDisconnect: false,
2023-02-12 21:22:44 +00:00
}
2023-03-06 01:03:31 +00:00
if Options.PathCustomUserData != "" {
if err := p.LoadCustomUserData(Options.PathCustomUserData); err != nil {
2023-02-12 21:22:44 +00:00
return nil, err
}
}
return p, nil
}
func (p *ProxyContext) AddCommand(cmd IngameCommand) {
p.commands[cmd.Cmd.Name] = cmd
}
type CustomClientData struct {
// skin things
CapeFilename string
SkinFilename string
SkinGeometryFilename string
2023-03-11 15:05:26 +00:00
SkinID string
2023-02-12 21:22:44 +00:00
PlayFabID string
PersonaSkin bool
PremiumSkin bool
TrustedSkin bool
ArmSize string
2023-03-11 15:05:26 +00:00
SkinColour string
2023-02-12 21:22:44 +00:00
// misc
IsEditorMode bool
LanguageCode string
2023-02-23 09:23:35 +00:00
DeviceID string
2023-02-12 21:22:44 +00:00
}
func (p *ProxyContext) LoadCustomUserData(path string) error {
f, err := os.Open(path)
if err != nil {
return err
}
var customData CustomClientData
err = json.NewDecoder(f).Decode(&customData)
if err != nil {
return err
}
p.CustomClientData = &login.ClientData{
2023-03-11 15:05:26 +00:00
SkinID: customData.SkinID,
2023-02-12 21:22:44 +00:00
PlayFabID: customData.PlayFabID,
PersonaSkin: customData.PersonaSkin,
PremiumSkin: customData.PremiumSkin,
TrustedSkin: customData.TrustedSkin,
ArmSize: customData.ArmSize,
2023-03-11 15:05:26 +00:00
SkinColour: customData.SkinColour,
2023-02-12 21:22:44 +00:00
}
if customData.SkinFilename != "" {
img, err := loadPng(customData.SkinFilename)
if err != nil {
return err
}
p.CustomClientData.SkinData = base64.RawStdEncoding.EncodeToString(img.Pix)
p.CustomClientData.SkinImageWidth = img.Rect.Dx()
p.CustomClientData.SkinImageHeight = img.Rect.Dy()
}
if customData.CapeFilename != "" {
img, err := loadPng(customData.CapeFilename)
if err != nil {
return err
}
p.CustomClientData.CapeData = base64.RawStdEncoding.EncodeToString(img.Pix)
p.CustomClientData.CapeImageWidth = img.Rect.Dx()
p.CustomClientData.CapeImageHeight = img.Rect.Dy()
}
if customData.SkinGeometryFilename != "" {
data, err := os.ReadFile(customData.SkinGeometryFilename)
if err != nil {
return err
}
p.CustomClientData.SkinGeometry = base64.RawStdEncoding.EncodeToString(data)
}
2023-02-23 09:23:35 +00:00
p.CustomClientData.DeviceID = customData.DeviceID
2023-02-12 21:22:44 +00:00
return nil
}
2022-09-03 17:32:30 +00:00
2023-03-23 19:39:47 +00:00
func (p *ProxyContext) ClientWritePacket(pk packet.Packet) error {
if p.Client == nil {
return nil
2022-09-03 22:56:40 +00:00
}
2023-03-23 19:39:47 +00:00
return p.Client.WritePacket(pk)
}
func (p *ProxyContext) SendMessage(text string) {
p.ClientWritePacket(&packet.Text{
TextType: packet.TextTypeSystem,
Message: "§8[§bBedrocktool§8]§r " + text,
})
2022-09-03 22:56:40 +00:00
}
2022-09-04 14:26:32 +00:00
func (p *ProxyContext) SendPopup(text string) {
2023-03-23 19:39:47 +00:00
p.ClientWritePacket(&packet.Text{
TextType: packet.TextTypePopup,
Message: text,
})
2022-09-03 22:56:40 +00:00
}
2023-04-02 14:49:24 +00:00
func (p *ProxyContext) AddHandler(handler *ProxyHandler) {
p.handlers = append(p.handlers, handler)
}
2023-03-23 19:39:47 +00:00
func (p *ProxyContext) CommandHandlerPacketCB(pk packet.Packet, toServer bool, _ time.Time) (packet.Packet, error) {
2023-04-12 12:10:45 +00:00
switch pk := pk.(type) {
2022-09-04 00:24:58 +00:00
case *packet.CommandRequest:
2023-04-12 12:10:45 +00:00
cmd := strings.Split(pk.CommandLine, " ")
2022-09-04 00:24:58 +00:00
name := cmd[0][1:]
if h, ok := p.commands[name]; ok {
2022-09-04 14:26:32 +00:00
if h.Exec(cmd[1:]) {
2022-09-04 00:24:58 +00:00
pk = nil
}
}
case *packet.AvailableCommands:
2023-04-12 12:10:45 +00:00
cmds := make([]protocol.Command, 0, len(p.commands))
2022-09-04 00:24:58 +00:00
for _, ic := range p.commands {
2022-10-01 03:10:28 +00:00
cmds = append(cmds, ic.Cmd)
}
pk = &packet.AvailableCommands{
2023-04-12 12:10:45 +00:00
Constraints: pk.Constraints,
Commands: append(pk.Commands, cmds...),
2022-09-04 00:24:58 +00:00
}
}
return pk, nil
}
2023-04-02 14:49:24 +00:00
func (p *ProxyContext) proxyLoop(ctx context.Context, toServer bool) error {
2022-09-03 17:32:30 +00:00
var c1, c2 *minecraft.Conn
if toServer {
c1 = p.Client
c2 = p.Server
2022-09-03 17:32:30 +00:00
} else {
c1 = p.Server
c2 = p.Client
2022-09-03 17:32:30 +00:00
}
for {
if ctx.Err() != nil {
return ctx.Err()
}
pk, err := c1.ReadPacket()
if err != nil {
return err
}
2023-04-02 14:49:24 +00:00
pkName := reflect.TypeOf(pk).String()
for _, handler := range p.handlers {
if handler.PacketCB != nil {
pk, err = handler.PacketCB(pk, toServer, time.Now())
if err != nil {
return err
}
if pk == nil {
logrus.Tracef("Dropped Packet: %s", pkName)
break
}
2022-09-03 17:32:30 +00:00
}
}
if pk != nil && c2 != nil {
2022-09-03 17:32:30 +00:00
if err := c2.WritePacket(pk); err != nil {
if disconnect, ok := errors.Unwrap(err).(minecraft.DisconnectError); ok {
2023-01-29 21:20:13 +00:00
DisconnectReason = disconnect.Error()
2022-09-03 17:32:30 +00:00
}
return err
}
}
}
}
2023-04-04 01:51:13 +00:00
func (p *ProxyContext) IsClient(addr net.Addr) bool {
return p.clientAddr.String() == addr.String()
}
var NewDebugLogger func(bool) *ProxyHandler
2023-04-02 14:49:24 +00:00
func (p *ProxyContext) Run(ctx context.Context, serverAddress, name string) (err error) {
2023-04-04 01:51:13 +00:00
if Options.Debug || Options.ExtraDebug {
p.AddHandler(NewDebugLogger(Options.ExtraDebug))
}
2023-04-10 17:55:08 +00:00
p.AddHandler(&ProxyHandler{
Name: "Commands",
PacketCB: p.CommandHandlerPacketCB,
})
2023-04-04 01:51:13 +00:00
2023-04-02 14:49:24 +00:00
for _, handler := range p.handlers {
if handler.AddressAndName != nil {
handler.AddressAndName(serverAddress, name)
}
if handler.ProxyRef != nil {
handler.ProxyRef(p)
}
}
2023-02-08 11:00:36 +00:00
if strings.HasPrefix(serverAddress, "PCAP!") {
2023-03-23 19:39:47 +00:00
return createReplayConnection(ctx, serverAddress[5:], p)
2022-09-03 22:56:40 +00:00
}
2022-09-03 17:32:30 +00:00
GetTokenSource() // ask for login before listening
var cdp *login.ClientData = nil
if p.WithClient {
var packs []*resource.Pack
2023-03-05 21:50:58 +00:00
if Options.Preload {
logrus.Info(locale.Loc("preloading_packs", nil))
2023-04-04 02:09:01 +00:00
serverConn, err := connectServer(ctx, serverAddress, nil, true, func(header packet.Header, payload []byte, src, dst net.Addr) {})
if err != nil {
2023-04-02 12:20:50 +00:00
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)))
}
2023-04-12 12:10:45 +00:00
status := minecraft.NewStatusProvider(fmt.Sprintf("%s Proxy", name))
p.Listener, err = minecraft.ListenConfig{
2023-04-12 12:10:45 +00:00
StatusProvider: status,
2023-03-08 11:46:16 +00:00
ResourcePacks: packs,
2023-02-18 19:39:45 +00:00
AcceptedProtocols: []minecraft.Protocol{
2023-03-08 11:46:16 +00:00
//dummyProto{id: 567, ver: "1.19.60"},
2023-02-18 19:39:45 +00:00
},
}.Listen("raknet", ":19132")
2022-09-03 17:32:30 +00:00
if err != nil {
2023-02-22 15:48:24 +00:00
return err
2022-09-03 17:32:30 +00:00
}
2023-04-02 14:49:24 +00:00
defer func() {
2023-03-08 11:46:16 +00:00
if p.Client != nil {
p.Listener.Disconnect(p.Client, DisconnectReason)
}
p.Listener.Close()
}()
2023-03-08 11:46:16 +00:00
logrus.Infof(locale.Loc("listening_on", locale.Strmap{"Address": p.Listener.Addr()}))
logrus.Infof(locale.Loc("help_connect", nil))
c, err := p.Listener.Accept()
if err != nil {
2023-03-08 11:46:16 +00:00
return err
}
p.Client = c.(*minecraft.Conn)
cd := p.Client.ClientData()
cdp = &cd
}
2023-02-12 21:22:44 +00:00
2023-04-02 14:49:24 +00:00
for _, handler := range p.handlers {
if handler.OnClientConnect == nil {
continue
}
handler.OnClientConnect(p.Client)
2023-02-12 21:22:44 +00:00
}
2023-03-08 11:46:16 +00:00
if p.CustomClientData != nil {
cdp = p.CustomClientData
2023-02-22 15:48:24 +00:00
}
2023-04-02 14:49:24 +00:00
p.Server, err = connectServer(ctx, serverAddress, cdp, p.AlwaysGetPacks, func(header packet.Header, payload []byte, src, dst net.Addr) {
2023-04-04 01:57:01 +00:00
if header.PacketID == packet.IDRequestNetworkSettings {
p.clientAddr = src
}
2023-04-02 14:49:24 +00:00
for _, handler := range p.handlers {
if handler.PacketFunc == nil {
continue
}
handler.PacketFunc(header, payload, src, dst)
}
})
if err != nil {
2023-04-02 14:49:24 +00:00
for _, handler := range p.handlers {
if handler.ConnectCB == nil {
continue
}
ignore := handler.ConnectCB(err)
if ignore {
2023-02-22 15:48:24 +00:00
err = nil
2023-04-02 14:49:24 +00:00
break
2023-02-22 15:48:24 +00:00
}
}
2023-04-02 14:49:24 +00:00
if err != nil {
err = fmt.Errorf(locale.Loc("failed_to_connect", locale.Strmap{"Address": serverAddress, "Err": err}))
}
2023-02-22 15:48:24 +00:00
return err
}
2023-02-22 15:48:24 +00:00
defer p.Server.Close()
2023-03-25 21:19:14 +00:00
gd := p.Server.GameData()
2023-04-04 00:42:17 +00:00
for _, handler := range p.handlers {
if handler.GameDataModifier != nil {
handler.GameDataModifier(&gd)
}
2023-03-25 21:19:14 +00:00
}
// spawn and start the game
2023-03-25 21:19:14 +00:00
if err = spawnConn(ctx, p.Client, p.Server, gd); err != nil {
err = fmt.Errorf(locale.Loc("failed_to_spawn", locale.Strmap{"Err": err}))
2023-02-22 15:48:24 +00:00
return err
}
2022-09-03 17:32:30 +00:00
2023-04-02 14:49:24 +00:00
for _, handler := range p.handlers {
if handler.ConnectCB == nil {
continue
}
if !handler.ConnectCB(nil) {
2023-02-22 15:48:24 +00:00
return errors.New("Cancelled")
}
}
2023-04-02 14:49:24 +00:00
wg := sync.WaitGroup{}
// server to client
wg.Add(1)
go func() {
defer wg.Done()
2023-04-02 14:49:24 +00:00
if err := p.proxyLoop(ctx, false); err != nil {
logrus.Error(err)
return
}
2022-09-03 17:32:30 +00:00
}()
// client to server
if p.Client != nil {
wg.Add(1)
go func() {
defer wg.Done()
2023-04-02 14:49:24 +00:00
if err := p.proxyLoop(ctx, true); err != nil {
logrus.Error(err)
return
}
}()
}
2023-04-02 14:49:24 +00:00
wantSecondary := fp.Filter(func(handler *ProxyHandler) bool {
return handler.SecondaryClientCB != nil
})(p.handlers)
if len(wantSecondary) > 0 {
go func() {
2023-04-04 00:42:17 +00:00
for {
c, err := p.Listener.Accept()
if err != nil {
logrus.Error(err)
return
}
2023-04-02 14:49:24 +00:00
2023-04-04 00:42:17 +00:00
for _, handler := range wantSecondary {
go handler.SecondaryClientCB(c.(*minecraft.Conn))
}
2023-04-02 14:49:24 +00:00
}
}()
}
wg.Wait()
2023-04-02 14:49:24 +00:00
for _, handler := range p.handlers {
if handler.OnEnd != nil {
handler.OnEnd()
}
}
return err
2022-09-03 17:32:30 +00:00
}