allow custom user data (untested)

This commit is contained in:
olebeck 2023-02-12 22:22:44 +01:00
parent 4d4eb37647
commit 6670d575d7
15 changed files with 230 additions and 81 deletions

View File

@ -15,6 +15,7 @@ import (
"github.com/bedrock-tool/bedrocktool/locale"
"github.com/bedrock-tool/bedrocktool/utils"
"github.com/bedrock-tool/bedrocktool/utils/crypt"
"gopkg.in/square/go-jose.v2/json"
_ "github.com/bedrock-tool/bedrocktool/subcommands"
_ "github.com/bedrock-tool/bedrocktool/subcommands/skins"
@ -65,6 +66,7 @@ func main() {
ctx, cancel := context.WithCancel(context.Background())
flag.StringVar(&utils.RealmsEnv, "", "", "realms env")
flag.BoolVar(&utils.GDebug, "debug", false, locale.Loc("debug_mode", nil))
flag.BoolVar(&utils.GPreloadPacks, "preload", false, locale.Loc("preload_packs", nil))
flag.BoolVar(&extraDebug, "extra-debug", false, locale.Loc("extra_debug", nil))
@ -189,6 +191,38 @@ func (c *TransCMD) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{})
return 0
}
type CreateCustomDataCMD struct {
path string
}
func (*CreateCustomDataCMD) Name() string { return "create-customdata" }
func (*CreateCustomDataCMD) Synopsis() string { return "" }
func (c *CreateCustomDataCMD) SetFlags(f *flag.FlagSet) {
f.StringVar(&c.path, "path", "customdata.json", "where to save")
}
func (c *CreateCustomDataCMD) Usage() string {
return c.Name() + ": " + c.Synopsis() + "\n"
}
func (c *CreateCustomDataCMD) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
var data utils.CustomClientData
fio, err := os.Create(c.path)
if err == nil {
defer fio.Close()
var bdata []byte
bdata, err = json.MarshalIndent(&data, "", "\t")
fio.Write(bdata)
}
if err != nil {
logrus.Error(err)
return 1
}
return 0
}
func init() {
utils.RegisterCommand(&TransCMD{})
utils.RegisterCommand(&CreateCustomDataCMD{})
}

4
go.mod
View File

@ -3,7 +3,7 @@ module github.com/bedrock-tool/bedrocktool
go 1.19
//replace github.com/sandertv/gophertunnel => ./gophertunnel
replace github.com/sandertv/gophertunnel => github.com/olebeck/gophertunnel v1.27.2-1
replace github.com/sandertv/gophertunnel => github.com/olebeck/gophertunnel v1.27.2-2
//replace github.com/df-mc/dragonfly => ./dragonfly
replace github.com/df-mc/dragonfly => github.com/olebeck/dragonfly v0.9.2-1
@ -29,6 +29,7 @@ require (
golang.org/x/image v0.3.0
golang.org/x/oauth2 v0.4.0
golang.org/x/text v0.6.0
gopkg.in/square/go-jose.v2 v2.6.0
gopkg.in/yaml.v3 v3.0.1
)
@ -54,5 +55,4 @@ require (
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/inconshreveable/go-update.v0 v0.0.0-20150814200126-d8b0b1d421aa // indirect
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
)

2
go.sum
View File

@ -64,6 +64,8 @@ github.com/olebeck/dragonfly v0.9.2-1 h1:/bYHMZ2yvwrLp2SQvsupbRimqDPGJ5utA2Oq857
github.com/olebeck/dragonfly v0.9.2-1/go.mod h1:hGGjGbLxpcn7nVTZOrk8kPfeGGntaMOqqcbuugZauyI=
github.com/olebeck/gophertunnel v1.27.2-1 h1:p+q8XubWgnk37uhjO2e06+A4g7S91jyXbvkQlVL4Xb4=
github.com/olebeck/gophertunnel v1.27.2-1/go.mod h1:hgVpDdaLDP/39Z/YqEU0WFi/DHRDHqvBs3XGqkB4tnU=
github.com/olebeck/gophertunnel v1.27.2-2 h1:ycI1ChA2L0ttr74Qkhy+0nGm01PUlHD0IklSj6+BAkI=
github.com/olebeck/gophertunnel v1.27.2-2/go.mod h1:hgVpDdaLDP/39Z/YqEU0WFi/DHRDHqvBs3XGqkB4tnU=
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/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=

View File

@ -44,7 +44,8 @@ func dumpPacket(f io.WriteCloser, toServer bool, payload []byte) {
}
type CaptureCMD struct {
serverAddress string
serverAddress string
pathCustomUserData string
}
func (*CaptureCMD) Name() string { return "capture" }
@ -52,6 +53,7 @@ func (*CaptureCMD) Synopsis() string { return locale.Loc("capture_synopsis", nil
func (c *CaptureCMD) SetFlags(f *flag.FlagSet) {
f.StringVar(&c.serverAddress, "address", "", "remote server address")
f.StringVar(&c.pathCustomUserData, "userdata", "", locale.Loc("custom_user_data", nil))
}
func (c *CaptureCMD) Usage() string {
@ -66,7 +68,6 @@ func (c *CaptureCMD) Execute(ctx context.Context, f *flag.FlagSet, _ ...interfac
}
os.Mkdir("captures", 0o775)
fio, err := os.Create("captures/" + hostname + "-" + time.Now().Format("2006-01-02_15-04-05") + ".pcap2")
if err != nil {
logrus.Fatal(err)
@ -75,7 +76,10 @@ func (c *CaptureCMD) Execute(ctx context.Context, f *flag.FlagSet, _ ...interfac
defer fio.Close()
utils.WriteReplayHeader(fio)
proxy := utils.NewProxy()
proxy, err := utils.NewProxy(c.pathCustomUserData)
if err != nil {
logrus.Fatal(err)
}
proxy.PacketFunc = func(header packet.Header, payload []byte, src, dst net.Addr) {
IsfromClient := src.String() == proxy.Client.LocalAddr().String()

View File

@ -16,8 +16,9 @@ import (
)
type ChatLogCMD struct {
Address string
verbose bool
Address string
verbose bool
pathCustomUserData string
}
func (*ChatLogCMD) Name() string { return "chat-log" }
@ -26,6 +27,7 @@ func (*ChatLogCMD) Synopsis() string { return locale.Loc("chat_log_synopsis", ni
func (c *ChatLogCMD) SetFlags(f *flag.FlagSet) {
f.StringVar(&c.Address, "address", "", "remote server address")
f.BoolVar(&c.verbose, "v", false, "verbose")
f.StringVar(&c.pathCustomUserData, "userdata", "", locale.Loc("custom_user_data", nil))
}
func (c *ChatLogCMD) Usage() string {
@ -46,7 +48,10 @@ func (c *ChatLogCMD) Execute(ctx context.Context, flags *flag.FlagSet, _ ...inte
}
defer f.Close()
proxy := utils.NewProxy()
proxy, err := utils.NewProxy(c.pathCustomUserData)
if err != nil {
logrus.Fatal(err)
}
proxy.PacketCB = func(pk packet.Packet, proxy *utils.ProxyContext, toServer bool, _ time.Time) (packet.Packet, error) {
if text, ok := pk.(*packet.Text); ok {
logLine := text.Message

View File

@ -13,8 +13,9 @@ import (
)
type DebugProxyCMD struct {
Address string
filter string
Address string
filter string
pathCustomUserData string
}
func (*DebugProxyCMD) Name() string { return "debug-proxy" }
@ -23,6 +24,7 @@ func (*DebugProxyCMD) Synopsis() string { return locale.Loc("debug_proxy_synopsi
func (c *DebugProxyCMD) SetFlags(f *flag.FlagSet) {
f.StringVar(&c.Address, "address", "", locale.Loc("remote_address", nil))
f.StringVar(&c.filter, "filter", "", locale.Loc("packet_filter", nil))
f.StringVar(&c.pathCustomUserData, "userdata", "", locale.Loc("custom_user_data", nil))
}
func (c *DebugProxyCMD) Usage() string {
@ -53,7 +55,11 @@ func (c *DebugProxyCMD) Execute(ctx context.Context, f *flag.FlagSet, _ ...inter
}
}
proxy := utils.NewProxy()
proxy, err := utils.NewProxy(c.pathCustomUserData)
if err != nil {
logrus.Error(err)
return 1
}
if err := proxy.Run(ctx, address); err != nil {
logrus.Error(err)
return 1

View File

@ -19,6 +19,7 @@ type SkinProxyCMD struct {
server_address string
filter string
only_with_geometry bool
pathCustomUserData string
}
func (*SkinProxyCMD) Name() string { return "skins-proxy" }
@ -28,6 +29,7 @@ func (c *SkinProxyCMD) SetFlags(f *flag.FlagSet) {
f.StringVar(&c.server_address, "address", "", locale.Loc("remote_address", nil))
f.StringVar(&c.filter, "filter", "", locale.Loc("name_prefix", nil))
f.BoolVar(&c.only_with_geometry, "only-geom", false, locale.Loc("only_with_geometry", nil))
f.StringVar(&c.pathCustomUserData, "userdata", "", locale.Loc("custom_user_data", nil))
}
func (c *SkinProxyCMD) Usage() string {
@ -41,7 +43,11 @@ func (c *SkinProxyCMD) Execute(ctx context.Context, f *flag.FlagSet, _ ...interf
return 1
}
proxy := utils.NewProxy()
proxy, err := utils.NewProxy(c.pathCustomUserData)
if err != nil {
logrus.Error(err)
return 1
}
s := NewSkinsSession(proxy, hostname)
s.OnlyIfHasGeometry = c.only_with_geometry

View File

@ -143,7 +143,7 @@ func (c *SkinCMD) Execute(ctx context.Context, f *flag.FlagSet, _ ...interface{}
return 1
}
proxy := utils.NewProxy()
proxy, _ := utils.NewProxy("")
proxy.WithClient = false
proxy.ConnectCB = func(proxy *utils.ProxyContext) {
logrus.Info(locale.Loc("ctrl_c_to_exit", nil))

View File

@ -8,6 +8,7 @@ import (
"os"
"time"
"github.com/bedrock-tool/bedrocktool/locale"
"github.com/bedrock-tool/bedrocktool/utils"
"golang.design/x/lockfree"
@ -254,6 +255,13 @@ func (m *MapUI) SetChunk(pos protocol.ChunkPos, ch *chunk.Chunk) {
m.SchedRedraw()
}
func (w *WorldState) ProcessAnimate(pk *packet.Animate) {
if pk.ActionType == packet.AnimateActionSwingArm {
w.ui.ChangeZoom()
w.proxy.SendPopup(locale.Loc("zoom_level", locale.Strmap{"Level": w.ui.zoomLevel}))
}
}
func (w *WorldState) processMapPacketsClient(pk packet.Packet, forward *bool) packet.Packet {
switch pk := pk.(type) {
case *packet.MovePlayer:

View File

@ -60,14 +60,20 @@ type WorldState struct {
ispre118 bool
// settings
voidgen bool
voidGen bool
withPacks bool
saveImage bool
experimentInventory bool
}
func NewWorldState() *WorldState {
func NewWorldState(ctx context.Context, proxy *utils.ProxyContext, ServerName string) *WorldState {
w := &WorldState{
ctx: ctx,
proxy: proxy,
ui: nil,
bp: behaviourpack.New(ServerName),
ServerName: ServerName,
chunks: make(map[protocol.ChunkPos]*chunk.Chunk),
blockNBT: make(map[protocol.SubChunkPos][]map[string]any),
openItemContainers: make(map[byte]*itemContainer),
@ -111,6 +117,7 @@ type WorldCMD struct {
enableVoid bool
saveImage bool
experimentInventory bool
pathCustomUserData string
}
func (*WorldCMD) Name() string { return "worlds" }
@ -122,6 +129,7 @@ func (c *WorldCMD) SetFlags(f *flag.FlagSet) {
f.BoolVar(&c.enableVoid, "void", true, locale.Loc("enable_void", nil))
f.BoolVar(&c.saveImage, "image", false, locale.Loc("save_image", nil))
f.BoolVar(&c.experimentInventory, "inv", false, locale.Loc("test_block_inv", nil))
f.StringVar(&c.pathCustomUserData, "userdata", "", locale.Loc("custom_user_data", nil))
}
func (c *WorldCMD) Usage() string {
@ -135,16 +143,18 @@ func (c *WorldCMD) Execute(ctx context.Context, f *flag.FlagSet, _ ...interface{
return 1
}
w := NewWorldState()
w.voidgen = c.enableVoid
w.ServerName = hostname
proxy, err := utils.NewProxy(c.pathCustomUserData)
if err != nil {
logrus.Error(err)
return 1
}
w := NewWorldState(ctx, proxy, hostname)
w.voidGen = c.enableVoid
w.withPacks = c.packs
w.saveImage = c.saveImage
w.experimentInventory = c.experimentInventory
w.ctx = ctx
w.bp = behaviourpack.New(w.ServerName)
proxy := utils.NewProxy()
proxy.AlwaysGetPacks = true
proxy.ConnectCB = w.OnConnect
proxy.PacketCB = func(pk packet.Packet, proxy *utils.ProxyContext, toServer bool, _ time.Time) (packet.Packet, error) {
@ -167,7 +177,7 @@ func (c *WorldCMD) Execute(ctx context.Context, f *flag.FlagSet, _ ...interface{
return pk, nil
}
err = proxy.Run(ctx, serverAddress)
err = w.proxy.Run(ctx, serverAddress)
if err != nil {
logrus.Error(err)
} else {
@ -176,13 +186,6 @@ func (c *WorldCMD) Execute(ctx context.Context, f *flag.FlagSet, _ ...interface{
return 0
}
func (w *WorldState) ProcessAnimate(pk *packet.Animate) {
if pk.ActionType == packet.AnimateActionSwingArm {
w.ui.ChangeZoom()
w.proxy.SendPopup(locale.Loc("zoom_level", locale.Strmap{"Level": w.ui.zoomLevel}))
}
}
func (w *WorldState) SetPlayerPos(Position mgl32.Vec3, Pitch, Yaw, HeadYaw float32) {
last := w.PlayerPos
w.PlayerPos = TPlayerPos{
@ -336,7 +339,7 @@ func (w *WorldState) SaveAndReset() {
ld.RandomSeed = int64(gd.WorldSeed)
// void world
if w.voidgen {
if w.voidGen {
ld.FlatWorldLayers = `{"biome_id":1,"block_layers":[{"block_data":0,"block_id":0,"count":1},{"block_data":0,"block_id":0,"count":2},{"block_data":0,"block_id":0,"count":1}],"encoding_version":3,"structure_options":null}`
ld.Generator = 2
}
@ -508,9 +511,9 @@ func (w *WorldState) OnConnect(proxy *utils.ProxyContext) {
proxy.AddCommand(utils.IngameCommand{
Exec: func(cmdline []string) bool {
w.voidgen = !w.voidgen
w.voidGen = !w.voidGen
var s string
if w.voidgen {
if w.voidGen {
s = locale.Loc("void_generator_true", nil)
} else {
s = locale.Loc("void_generator_false", nil)

View File

@ -2,6 +2,7 @@ package utils
import (
"encoding/json"
"fmt"
"os"
"github.com/bedrock-tool/bedrocktool/locale"
@ -33,10 +34,15 @@ func GetTokenSource() oauth2.TokenSource {
return gTokenSrc
}
var RealmsEnv string
var gRealmsAPI *realms.Client
func GetRealmsAPI() *realms.Client {
if gRealmsAPI == nil {
if RealmsEnv != "" {
realms.RealmsAPIBase = fmt.Sprintf("https://pocket-%s.realms.minecraft.net/", RealmsEnv)
}
gRealmsAPI = realms.NewClient(GetTokenSource())
}
return gRealmsAPI

View File

@ -3,6 +3,8 @@ package utils
import (
"image"
"image/color"
"image/png"
"os"
"reflect"
"unsafe"
)
@ -14,6 +16,18 @@ func Img2rgba(img *image.RGBA) []color.RGBA {
return *(*[]color.RGBA)(unsafe.Pointer(&header))
}
func loadPng(path string) (*image.RGBA, error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}
img, err := png.Decode(f)
if err != nil {
return nil, err
}
return (*image.RGBA)(img.(*image.NRGBA)), err
}
// LERP is a linear interpolation function
func LERP(p1, p2, alpha float64) float64 {
return (1-alpha)*p1 + alpha*p2

View File

@ -2,9 +2,12 @@ package utils
import (
"context"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"net"
"os"
"strings"
"sync"
"time"
@ -20,29 +23,24 @@ import (
var DisconnectReason = "Connection lost"
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}
}
type (
PacketFunc func(header packet.Header, payload []byte, src, dst net.Addr)
PacketCallback func(pk packet.Packet, proxy *ProxyContext, toServer bool, timeReceived time.Time) (packet.Packet, error)
ConnectCallback func(proxy *ProxyContext)
IngameCommand struct {
Exec func(cmdline []string) bool
Cmd protocol.Command
}
)
type ProxyContext struct {
Server *minecraft.Conn
Client *minecraft.Conn
Listener *minecraft.Listener
commands map[string]IngameCommand
AlwaysGetPacks bool
WithClient bool
Server *minecraft.Conn
Client *minecraft.Conn
Listener *minecraft.Listener
commands map[string]IngameCommand
AlwaysGetPacks bool
WithClient bool
CustomClientData *login.ClientData
// called for every packet
PacketFunc PacketFunc
@ -52,11 +50,88 @@ type ProxyContext struct {
PacketCB PacketCallback
}
type (
PacketFunc func(header packet.Header, payload []byte, src, dst net.Addr)
PacketCallback func(pk packet.Packet, proxy *ProxyContext, toServer bool, timeReceived time.Time) (packet.Packet, error)
ConnectCallback func(proxy *ProxyContext)
)
func NewProxy(pathCustomData string) (*ProxyContext, error) {
p := &ProxyContext{
commands: make(map[string]IngameCommand),
WithClient: true,
}
if pathCustomData != "" {
if err := p.LoadCustomUserData(pathCustomData); err != nil {
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
PlayFabID string
PersonaSkin bool
PremiumSkin bool
TrustedSkin bool
ArmSize string
// misc
IsEditorMode bool
LanguageCode string
}
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{
PlayFabID: customData.PlayFabID,
PersonaSkin: customData.PersonaSkin,
PremiumSkin: customData.PremiumSkin,
TrustedSkin: customData.TrustedSkin,
ArmSize: customData.ArmSize,
}
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)
}
return nil
}
func (p *ProxyContext) SendMessage(text string) {
if p.Client != nil {
@ -76,15 +151,6 @@ func (p *ProxyContext) SendPopup(text string) {
}
}
type IngameCommand struct {
Exec func(cmdline []string) bool
Cmd protocol.Command
}
func (p *ProxyContext) AddCommand(cmd IngameCommand) {
p.commands[cmd.Cmd.Name] = cmd
}
func (p *ProxyContext) CommandHandlerPacketCB(pk packet.Packet, proxy *ProxyContext, toServer bool, _ time.Time) (packet.Packet, error) {
switch _pk := pk.(type) {
case *packet.CommandRequest:
@ -146,13 +212,6 @@ func (p *ProxyContext) proxyLoop(ctx context.Context, toServer bool, packetCBs [
}
}
func NewProxy() *ProxyContext {
return &ProxyContext{
commands: make(map[string]IngameCommand),
WithClient: true,
}
}
var ClientAddr net.Addr
func (p *ProxyContext) Run(ctx context.Context, serverAddress string) (err error) {
@ -182,9 +241,6 @@ func (p *ProxyContext) Run(ctx context.Context, serverAddress string) (err error
p.Listener, err = minecraft.ListenConfig{
StatusProvider: _status,
ResourcePacks: packs,
AcceptedProtocols: []minecraft.Protocol{
dummyProto{id: 544, ver: "1.19.20"},
},
}.Listen("raknet", ":19132")
if err != nil {
return
@ -208,6 +264,11 @@ func (p *ProxyContext) Run(ctx context.Context, serverAddress string) (err error
cd := p.Client.ClientData()
cdp = &cd
}
if p.CustomClientData != nil {
cdp = p.CustomClientData
}
p.Server, err = connectServer(ctx, serverAddress, cdp, p.AlwaysGetPacks, p.PacketFunc)
if err != nil {
err = fmt.Errorf(locale.Loc("failed_to_connect", locale.Strmap{"Address": serverAddress, "Err": err}))
@ -229,9 +290,9 @@ func (p *ProxyContext) Run(ctx context.Context, serverAddress string) (err error
}
wg := sync.WaitGroup{}
cbs := []PacketCallback{
p.CommandHandlerPacketCB,
}
var cbs []PacketCallback
cbs = append(cbs, p.CommandHandlerPacketCB)
if p.PacketCB != nil {
cbs = append(cbs, p.PacketCB)
}

View File

@ -63,7 +63,7 @@ func createReplayConnection(ctx context.Context, filename string, onConnect Conn
f.Seek(-4, io.SeekCurrent)
}
proxy := NewProxy()
proxy, _ := NewProxy("")
proxy.Server = minecraft.NewConn()
gameStarted := false

Binary file not shown.