rework ui code to allow writing uis for subcommands

This commit is contained in:
olebeck 2023-03-06 21:49:30 +01:00
parent 5565301f11
commit c9850772d5
23 changed files with 412 additions and 287 deletions

View File

@ -5,7 +5,6 @@ import (
"context"
"flag"
"fmt"
"io"
"os"
"os/signal"
"runtime/debug"
@ -13,31 +12,30 @@ 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"
_ "github.com/bedrock-tool/bedrocktool/subcommands/world"
_ "github.com/bedrock-tool/bedrocktool/ui/gui"
_ "github.com/bedrock-tool/bedrocktool/ui"
"github.com/google/subcommands"
"github.com/sirupsen/logrus"
)
type CLI struct {
utils.UI
utils.BaseUI
}
func (c *CLI) Init() {
func (c *CLI) Init() bool {
utils.SetCurrentUI(c)
return true
}
func (c *CLI) SetOptions(context.Context) bool {
func (c *CLI) Start(ctx context.Context) error {
flag.Parse()
return false
}
func (c *CLI) Execute(ctx context.Context) error {
utils.InitDNS()
utils.InitExtraDebug()
subcommands.Execute(ctx)
return nil
}
@ -105,53 +103,6 @@ func main() {
ui = &CLI{}
}
ui.Init()
if ui.SetOptions(ctx) {
return
}
if ctx.Err() != nil {
return
}
if utils.Options.EnableDNS {
utils.InitDNS()
}
if utils.Options.ExtraDebug {
utils.Options.Debug = true
var logPlain, logCryptEnc io.WriteCloser = nil, nil
// open plain text log
logPlain, err = os.Create("packets.log")
if err != nil {
logrus.Error(err)
} else {
defer logPlain.Close()
}
// open gpg log
logCrypt, err := os.Create("packets.log.gpg")
if err != nil {
logrus.Error(err)
} else {
defer logCrypt.Close()
// encrypter for the log
logCryptEnc, err = crypt.Encer("packets.log", logCrypt)
if err != nil {
logrus.Error(err)
} else {
defer logCryptEnc.Close()
}
}
utils.FLog = io.MultiWriter(logPlain, logCryptEnc)
if err != nil {
logrus.Error(err)
}
}
// exit cleanup
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
@ -161,12 +112,13 @@ func main() {
cancel()
}()
ui.Execute(ctx)
if utils.Options.IsInteractive {
logrus.Info(locale.Loc("enter_to_exit", nil))
input := bufio.NewScanner(os.Stdin)
input.Scan()
if !ui.Init() {
logrus.Error("Failed to init UI!")
return
}
err = ui.Start(ctx)
if err != nil {
logrus.Error(err)
}
}
@ -176,16 +128,10 @@ type TransCMD struct {
func (*TransCMD) Name() string { return "trans" }
func (*TransCMD) Synopsis() string { return "" }
func (c *TransCMD) SetFlags(f *flag.FlagSet) {
f.BoolVar(&c.auth, "auth", false, locale.Loc("should_login_xbox", nil))
}
func (c *TransCMD) Usage() string {
return c.Name() + ": " + c.Synopsis() + "\n"
}
func (c *TransCMD) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
func (c *TransCMD) Execute(_ context.Context, ui utils.UI) error {
const (
BlackFg = "\033[30m"
Bold = "\033[1m"
@ -198,7 +144,7 @@ func (c *TransCMD) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{})
utils.GetTokenSource()
}
fmt.Println(BlackFg + Bold + Blue + " Trans " + Pink + " Rights " + White + " Are " + Pink + " Human " + Blue + " Rights " + Reset)
return 0
return nil
}
type CreateCustomDataCMD struct {
@ -212,11 +158,7 @@ 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 {
func (c *CreateCustomDataCMD) Execute(_ context.Context, ui utils.UI) error {
var data utils.CustomClientData
fio, err := os.Create(c.path)
if err == nil {
@ -226,10 +168,9 @@ func (c *CreateCustomDataCMD) Execute(_ context.Context, f *flag.FlagSet, _ ...i
fio.Write(bdata)
}
if err != nil {
logrus.Error(err)
return 1
return err
}
return 0
return nil
}
func init() {

View File

@ -14,7 +14,6 @@ import (
"github.com/bedrock-tool/bedrocktool/locale"
"github.com/bedrock-tool/bedrocktool/utils"
"github.com/google/subcommands"
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
"github.com/sirupsen/logrus"
)
@ -49,27 +48,20 @@ type CaptureCMD struct {
func (*CaptureCMD) Name() string { return "capture" }
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")
}
func (c *CaptureCMD) Usage() string {
return c.Name() + ": " + c.Synopsis() + "\n" + locale.Loc("server_address_help", nil)
}
func (c *CaptureCMD) Execute(ctx context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
func (c *CaptureCMD) Execute(ctx context.Context, ui utils.UI) error {
address, hostname, err := utils.ServerInput(ctx, c.ServerAddress)
if err != nil {
logrus.Fatal(err)
return 1
return err
}
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)
return 1
return err
}
defer fio.Close()
utils.WriteReplayHeader(fio)
@ -90,8 +82,7 @@ func (c *CaptureCMD) Execute(ctx context.Context, f *flag.FlagSet, _ ...interfac
err = proxy.Run(ctx, address)
time.Sleep(2 * time.Second)
if err != nil {
logrus.Fatal(err)
return 1
return err
}
return 0
return nil
}

View File

@ -10,7 +10,6 @@ import (
"github.com/bedrock-tool/bedrocktool/locale"
"github.com/bedrock-tool/bedrocktool/utils"
"github.com/google/subcommands"
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
"github.com/sirupsen/logrus"
)
@ -22,33 +21,27 @@ type ChatLogCMD struct {
func (*ChatLogCMD) Name() string { return "chat-log" }
func (*ChatLogCMD) Synopsis() string { return locale.Loc("chat_log_synopsis", nil) }
func (c *ChatLogCMD) SetFlags(f *flag.FlagSet) {
f.StringVar(&c.ServerAddress, "address", "", "remote server address")
f.BoolVar(&c.Verbose, "v", false, "verbose")
}
func (c *ChatLogCMD) Usage() string {
return c.Name() + ": " + c.Synopsis() + "\n" + locale.Loc("server_address_help", nil)
}
func (c *ChatLogCMD) Execute(ctx context.Context, flags *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
func (c *ChatLogCMD) Execute(ctx context.Context, ui utils.UI) error {
address, hostname, err := utils.ServerInput(ctx, c.ServerAddress)
if err != nil {
logrus.Error(err)
return 1
return err
}
filename := fmt.Sprintf("%s_%s_chat.log", hostname, time.Now().Format("2006-01-02_15-04-05_Z07"))
f, err := os.Create(filename)
if err != nil {
logrus.Fatal(err)
return err
}
defer f.Close()
proxy, err := utils.NewProxy()
if err != nil {
logrus.Fatal(err)
return err
}
proxy.PacketCB = func(pk packet.Packet, proxy *utils.ProxyContext, toServer bool, _ time.Time) (packet.Packet, error) {
if text, ok := pk.(*packet.Text); ok {
@ -66,12 +59,8 @@ func (c *ChatLogCMD) Execute(ctx context.Context, flags *flag.FlagSet, _ ...inte
return pk, nil
}
if err := proxy.Run(ctx, address); err != nil {
logrus.Error(err)
return 1
}
return 0
err = proxy.Run(ctx, address)
return err
}
func init() {

View File

@ -7,9 +7,6 @@ import (
"github.com/bedrock-tool/bedrocktool/locale"
"github.com/bedrock-tool/bedrocktool/utils"
"github.com/google/subcommands"
"github.com/sirupsen/logrus"
)
type DebugProxyCMD struct {
@ -19,21 +16,15 @@ type DebugProxyCMD struct {
func (*DebugProxyCMD) Name() string { return "debug-proxy" }
func (*DebugProxyCMD) Synopsis() string { return locale.Loc("debug_proxy_synopsis", nil) }
func (c *DebugProxyCMD) SetFlags(f *flag.FlagSet) {
f.StringVar(&c.ServerAddress, "address", "", locale.Loc("remote_address", nil))
f.StringVar(&c.Filter, "filter", "", locale.Loc("packet_filter", nil))
}
func (c *DebugProxyCMD) Usage() string {
return c.Name() + ": " + c.Synopsis() + "\n" + locale.Loc("server_address_help", nil)
}
func (c *DebugProxyCMD) Execute(ctx context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
func (c *DebugProxyCMD) Execute(ctx context.Context, ui utils.UI) error {
address, _, err := utils.ServerInput(ctx, c.ServerAddress)
if err != nil {
logrus.Error(err)
return 1
return err
}
utils.Options.Debug = true
@ -55,14 +46,10 @@ func (c *DebugProxyCMD) Execute(ctx context.Context, f *flag.FlagSet, _ ...inter
proxy, err := utils.NewProxy()
if err != nil {
logrus.Error(err)
return 1
return err
}
if err := proxy.Run(ctx, address); err != nil {
logrus.Error(err)
return 1
}
return 0
err = proxy.Run(ctx, address)
return err
}
func init() {

View File

@ -1,3 +1,5 @@
//go:build false
package subcommands
import (

View File

@ -9,12 +9,8 @@ import (
"github.com/bedrock-tool/bedrocktool/locale"
resourcepackd "github.com/bedrock-tool/bedrocktool/subcommands/resourcepack-d"
"github.com/bedrock-tool/bedrocktool/utils"
"github.com/google/subcommands"
"github.com/sirupsen/logrus"
)
// decrypt using cfb with segmentsize = 1
type ResourcePackCMD struct {
ServerAddress string
SaveEncrypted bool
@ -30,17 +26,8 @@ func (c *ResourcePackCMD) SetFlags(f *flag.FlagSet) {
f.BoolVar(&c.OnlyKeys, "only-keys", false, locale.Loc("only_keys", nil))
}
func (c *ResourcePackCMD) Usage() string {
return c.Name() + ": " + c.Synopsis() + "\n" + locale.Loc("server_address_help", nil)
}
func (c *ResourcePackCMD) Execute(ctx context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
err := resourcepackd.Execute_cmd(ctx, c.ServerAddress, c.OnlyKeys, c.SaveEncrypted)
if err != nil {
logrus.Error(err)
return 1
}
return 0
func (c *ResourcePackCMD) Execute(ctx context.Context, ui utils.UI) error {
return resourcepackd.Execute_cmd(ctx, c.ServerAddress, c.OnlyKeys, c.SaveEncrypted)
}
func init() {

View File

@ -4,11 +4,10 @@ package subcommands
import (
"context"
"errors"
"flag"
"github.com/bedrock-tool/bedrocktool/utils"
"github.com/google/subcommands"
"github.com/sirupsen/logrus"
)
type ResourcePackCMD struct {
@ -17,18 +16,11 @@ type ResourcePackCMD struct {
OnlyKeys bool
}
func (*ResourcePackCMD) Name() string { return "packs" }
func (*ResourcePackCMD) Synopsis() string { return "NOT COMPILED" }
func (c *ResourcePackCMD) SetFlags(f *flag.FlagSet) {}
func (c *ResourcePackCMD) Usage() string {
return c.Name() + ": " + c.Synopsis()
}
func (c *ResourcePackCMD) Execute(ctx context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
logrus.Error("not compiled")
return 1
func (*ResourcePackCMD) Name() string { return "packs" }
func (*ResourcePackCMD) Synopsis() string { return "NOT COMPILED" }
func (*ResourcePackCMD) SetFlags(f *flag.FlagSet) {}
func (*ResourcePackCMD) Execute(ctx context.Context, ui utils.UI) error {
return errors.New("not compiled")
}
func init() {

View File

@ -12,7 +12,6 @@ import (
"github.com/bedrock-tool/bedrocktool/locale"
"github.com/bedrock-tool/bedrocktool/utils"
"github.com/google/subcommands"
"github.com/google/uuid"
"github.com/sandertv/gophertunnel/minecraft/protocol"
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
@ -124,15 +123,10 @@ func (c *SkinCMD) SetFlags(f *flag.FlagSet) {
f.BoolVar(&c.NoProxy, "no-proxy", false, "use headless version")
}
func (c *SkinCMD) Usage() string {
return c.Name() + ": " + c.Synopsis() + "\n" + locale.Loc("server_address_help", nil)
}
func (c *SkinCMD) Execute(ctx context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
func (c *SkinCMD) Execute(ctx context.Context, ui utils.UI) error {
address, hostname, err := utils.ServerInput(ctx, c.ServerAddress)
if err != nil {
logrus.Error(err)
return 1
return err
}
proxy, _ := utils.NewProxy()
@ -158,10 +152,7 @@ func (c *SkinCMD) Execute(ctx context.Context, f *flag.FlagSet, _ ...interface{}
}
err = proxy.Run(ctx, address)
if err != nil {
logrus.Error(err)
}
return 0
return err
}
func init() {

View File

@ -8,40 +8,31 @@ import (
"github.com/bedrock-tool/bedrocktool/locale"
"github.com/bedrock-tool/bedrocktool/utils"
"github.com/sirupsen/logrus"
"github.com/google/subcommands"
)
type UpdateCMD struct{}
func (*UpdateCMD) Name() string { return "update" }
func (*UpdateCMD) Synopsis() string { return locale.Loc("update_synopsis", nil) }
func (*UpdateCMD) Name() string { return "update" }
func (*UpdateCMD) Synopsis() string { return locale.Loc("update_synopsis", nil) }
func (c *UpdateCMD) SetFlags(f *flag.FlagSet) {}
func (c *UpdateCMD) Usage() string {
return c.Name() + ": " + c.Synopsis() + "\n"
}
func (c *UpdateCMD) Execute(ctx context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
func (c *UpdateCMD) Execute(ctx context.Context, ui utils.UI) error {
newVersion, err := utils.Updater.UpdateAvailable()
if err != nil {
logrus.Error(err)
return 1
return err
}
if newVersion == "" {
logrus.Info(locale.Loc("no_update", nil))
return 0
return nil
}
logrus.Infof(locale.Loc("updating", locale.Strmap{"Version": newVersion}))
if err := utils.Updater.Update(); err != nil {
logrus.Error(err)
return 1
return err
}
logrus.Infof(locale.Loc("updated", nil))
return 0
return nil
}
func init() {

View File

@ -60,9 +60,9 @@ func (w *WorldState) processLevelChunk(pk *packet.LevelChunk) {
w.chunks[pk.Position] = ch
if pk.SubChunkRequestMode == protocol.SubChunkRequestModeLegacy {
w.ui.SetChunk(pk.Position, ch)
w.mapUI.SetChunk(pk.Position, ch)
} else {
w.ui.SetChunk(pk.Position, nil)
w.mapUI.SetChunk(pk.Position, nil)
// request all the subchunks
max := w.Dim.Range().Height() / 16
@ -109,9 +109,9 @@ func (w *WorldState) processSubChunk(pk *packet.SubChunk) {
// redraw the chunks
for pos := range posToRedraw {
w.ui.SetChunk(pos, w.chunks[pos])
w.mapUI.SetChunk(pos, w.chunks[pos])
}
w.ui.SchedRedraw()
w.mapUI.SchedRedraw()
}
func (w *WorldState) ProcessChunkPackets(pk packet.Packet) packet.Packet {

View File

@ -43,7 +43,7 @@ var MapItemPacket packet.InventoryContent = packet.InventoryContent{
},
}
func (m *MapUI) getBounds() (min, max protocol.ChunkPos) {
func (m *MapUI) GetBounds() (min, max protocol.ChunkPos) {
// get the chunk coord bounds
i := 0
for _ch := range m.renderedChunks {
@ -75,6 +75,7 @@ type MapUI struct {
renderQueue *lockfree.Queue
renderedChunks map[protocol.ChunkPos]*image.RGBA // prerendered chunks
needRedraw bool // when the map has updated this is true
showOnGui bool
ticker *time.Ticker
w *WorldState
@ -93,6 +94,21 @@ func NewMapUI(w *WorldState) *MapUI {
}
func (m *MapUI) Start() {
r := m.w.gui.Message("init_map", struct {
GetTiles func() map[protocol.ChunkPos]*image.RGBA
GetBounds func() (min, max protocol.ChunkPos)
}{
GetTiles: func() map[protocol.ChunkPos]*image.RGBA {
return m.renderedChunks
},
GetBounds: func() (min, max protocol.ChunkPos) {
return m.GetBounds()
},
})
if r.Ok {
m.showOnGui = true
}
// init map
if m.w.proxy.Client != nil {
if err := m.w.proxy.Client.WritePacket(&packet.ClientBoundMapItemData{
@ -224,11 +240,15 @@ func (m *MapUI) Redraw() {
bmp.Encode(buf, img2)
os.WriteFile("test.bmp", buf.Bytes(), 0o777)
}
if m.showOnGui {
m.w.gui.Message("update_map", nil)
}
}
func (m *MapUI) ToImage() *image.RGBA {
// get the chunk coord bounds
min, max := m.getBounds()
min, max := m.GetBounds()
chunksX := int(max[0] - min[0] + 1) // how many chunk lengths is x coordinate
chunksY := int(max[1] - min[1] + 1)
@ -254,8 +274,8 @@ func (m *MapUI) SetChunk(pos protocol.ChunkPos, ch *chunk.Chunk) {
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}))
w.mapUI.ChangeZoom()
w.proxy.SendPopup(locale.Loc("zoom_level", locale.Strmap{"Level": w.mapUI.zoomLevel}))
}
}
@ -267,7 +287,7 @@ func (w *WorldState) processMapPacketsClient(pk packet.Packet, forward *bool) pa
w.SetPlayerPos(pk.Position, pk.Pitch, pk.Yaw, pk.HeadYaw)
case *packet.MapInfoRequest:
if pk.MapID == ViewMapID {
w.ui.SchedRedraw()
w.mapUI.SchedRedraw()
*forward = false
}
case *packet.Animate:

View File

@ -1,4 +1,3 @@
// Package world Bedrock World Downloader
package world
import (
@ -26,7 +25,6 @@ import (
"github.com/df-mc/dragonfly/server/world/mcdb"
"github.com/df-mc/goleveldb/leveldb/opt"
"github.com/go-gl/mathgl/mgl32"
"github.com/google/subcommands"
"github.com/sandertv/gophertunnel/minecraft/protocol"
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
"github.com/sirupsen/logrus"
@ -44,7 +42,8 @@ type TPlayerPos struct {
type WorldState struct {
ctx context.Context
proxy *utils.ProxyContext
ui *MapUI
mapUI *MapUI
gui utils.UI
bp *behaviourpack.BehaviourPack
// save state
@ -66,11 +65,12 @@ type WorldState struct {
experimentInventory bool
}
func NewWorldState(ctx context.Context, proxy *utils.ProxyContext, ServerName string) *WorldState {
func NewWorldState(ctx context.Context, proxy *utils.ProxyContext, ServerName string, ui utils.UI) *WorldState {
w := &WorldState{
ctx: ctx,
proxy: proxy,
ui: nil,
mapUI: nil,
gui: ui,
bp: behaviourpack.New(ServerName),
ServerName: ServerName,
@ -82,7 +82,7 @@ func NewWorldState(ctx context.Context, proxy *utils.ProxyContext, ServerName st
WorldName: "world",
PlayerPos: TPlayerPos{},
}
w.ui = NewMapUI(w)
w.mapUI = NewMapUI(w)
return w
}
@ -130,24 +130,18 @@ func (c *WorldCMD) SetFlags(f *flag.FlagSet) {
f.BoolVar(&c.ExperimentInventory, "inv", false, locale.Loc("test_block_inv", nil))
}
func (c *WorldCMD) Usage() string {
return c.Name() + ": " + c.Synopsis() + "\n" + locale.Loc("server_address_help", nil)
}
func (c *WorldCMD) Execute(ctx context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
serverAddress, hostname, err := utils.ServerInput(ctx, c.ServerAddress)
func (c *WorldCMD) Execute(ctx context.Context, ui utils.UI) error {
serverAddress, hostname, err := ui.ServerInput(ctx, c.ServerAddress)
if err != nil {
logrus.Error(err)
return 1
return err
}
proxy, err := utils.NewProxy()
if err != nil {
logrus.Error(err)
return 1
return err
}
w := NewWorldState(ctx, proxy, hostname)
w := NewWorldState(ctx, proxy, hostname, ui)
w.voidGen = c.EnableVoid
w.withPacks = c.Packs
w.saveImage = c.SaveImage
@ -177,11 +171,10 @@ func (c *WorldCMD) Execute(ctx context.Context, f *flag.FlagSet, _ ...interface{
err = w.proxy.Run(ctx, serverAddress)
if err != nil {
logrus.Error(err)
} else {
w.SaveAndReset()
return err
}
return 0
w.SaveAndReset()
return nil
}
func (w *WorldState) SetPlayerPos(Position mgl32.Vec3, Pitch, Yaw, HeadYaw float32) {
@ -194,14 +187,14 @@ func (w *WorldState) SetPlayerPos(Position mgl32.Vec3, Pitch, Yaw, HeadYaw float
}
if int(last.Position.X()) != int(w.PlayerPos.Position.X()) || int(last.Position.Z()) != int(w.PlayerPos.Position.Z()) {
w.ui.SchedRedraw()
w.mapUI.SchedRedraw()
}
}
func (w *WorldState) Reset() {
w.chunks = make(map[protocol.ChunkPos]*chunk.Chunk)
w.WorldName = fmt.Sprintf("world-%d", w.worldCounter)
w.ui.Reset()
w.mapUI.Reset()
}
// SaveAndReset writes the world to a folder, resets all the chunks
@ -421,7 +414,7 @@ func (w *WorldState) SaveAndReset() {
if w.saveImage {
f, _ := os.Create(folder + ".png")
png.Encode(f, w.ui.ToImage())
png.Encode(f, w.mapUI.ToImage())
f.Close()
}
@ -485,7 +478,7 @@ func (w *WorldState) OnConnect(proxy *utils.ProxyContext, err error) bool {
w.proxy.SendMessage(locale.Loc("use_setname", nil))
w.ui.Start()
w.mapUI.Start()
proxy.AddCommand(utils.IngameCommand{
Exec: func(cmdline []string) bool {

View File

@ -1,9 +1,10 @@
//go:build gui || android || true
//go:build gui || android
package gui
package ui
import (
"context"
"sync"
"fyne.io/fyne/v2/app"
"fyne.io/fyne/v2/container"
@ -12,6 +13,7 @@ import (
"github.com/bedrock-tool/bedrocktool/subcommands"
"github.com/bedrock-tool/bedrocktool/subcommands/skins"
"github.com/bedrock-tool/bedrocktool/subcommands/world"
"github.com/bedrock-tool/bedrocktool/ui/gui"
"github.com/bedrock-tool/bedrocktool/utils"
)
@ -86,12 +88,16 @@ var settings = map[string]func(utils.Command) *widget.Form{
}
type GUI struct {
utils.UI
utils.BaseUI
selected binding.String
commandUI gui.CommandUI
}
func (g *GUI) SetOptions(ctx context.Context) bool {
func (g *GUI) Init() bool {
return true
}
func (g *GUI) Start(ctx context.Context) error {
a := app.New()
w := a.NewWindow("Bedrocktool")
@ -120,11 +126,10 @@ func (g *GUI) SetOptions(ctx context.Context) bool {
}
}
g.selected = binding.NewString()
selected := binding.NewString()
forms_box := container.NewVBox()
var quit = true
start_button := widget.NewButton("Start", nil)
l := sync.Mutex{}
w.SetContent(container.NewVBox(
widget.NewRichTextFromMarkdown("## Settings"),
@ -138,46 +143,58 @@ func (g *GUI) SetOptions(ctx context.Context) bool {
),
widget.NewRichTextFromMarkdown("# Commands"),
widget.NewSelect(entries, func(s string) {
g.selected.Set(s)
quit = false
l.Lock()
selected.Set(s)
forms_box.RemoveAll()
forms_box.Add(forms[s])
l.Unlock()
}),
forms_box,
widget.NewButton("Start", func() {
w.Close()
}),
start_button,
))
for _, f := range forms {
forms_box.Add(f)
}
g.selected.AddListener(binding.NewDataListener(func() {
v, _ := g.selected.Get()
for n, f := range forms {
if n == v {
f.Show()
} else {
f.Hide()
}
start_button.OnTapped = func() {
sub, _ := selected.Get()
cmd := utils.ValidCMDs[sub]
u := gui.CommandUIs[sub]
if u != nil {
g.commandUI = u
w.SetContent(u.Layout())
}
}))
utils.InitDNS()
utils.InitExtraDebug()
go cmd.Execute(ctx, g)
}
w.ShowAndRun()
return quit
}
func (g *GUI) Init() {
}
func (g *GUI) Execute(ctx context.Context) error {
sub, err := g.selected.Get()
if err != nil {
return err
}
cmd := utils.ValidCMDs[sub]
cmd.Execute(ctx, nil)
return nil
}
func (g *GUI) Message(name string, data interface{}) utils.MessageResponse {
h := g.commandUI.Handler()
if h != nil {
r := h(name, data)
if r.Ok {
return r
}
}
r := utils.MessageResponse{
Ok: false,
Data: nil,
}
switch name {
case "can_show_images":
r.Ok = true
}
return r
}
func init() {
utils.MakeGui = func() utils.UI {
return &GUI{}

59
ui/gui/map_widget.go Normal file
View File

@ -0,0 +1,59 @@
package gui
import (
"image"
"image/draw"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/canvas"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/widget"
"github.com/sandertv/gophertunnel/minecraft/protocol"
)
type mapWidget struct {
widget.BaseWidget
GetTiles func() map[protocol.ChunkPos]*image.RGBA
GetBounds func() (min, max protocol.ChunkPos)
pixels image.Image
w, h int
}
func (m *mapWidget) MinSize() fyne.Size {
return fyne.NewSize(128, 128)
}
func (m *mapWidget) CreateRenderer() fyne.WidgetRenderer {
c := container.NewMax(canvas.NewRaster(m.draw))
return widget.NewSimpleRenderer(c)
}
func (m *mapWidget) draw(w, h int) image.Image {
if m.w != w || m.h != h {
m.pixels = image.NewNRGBA(image.Rect(0, 0, w, h))
}
if m.GetBounds == nil {
return m.pixels
}
min, max := m.GetBounds()
//chunksX := int(max[0] - min[0] + 1) // how many chunk lengths is x coordinate
//chunksY := int(max[1] - min[1] + 1)
_ = max
for pos, tile := range m.GetTiles() {
px := image.Pt(
int((pos[0]-min[0])*16),
int((pos[1]-min[1])*16),
)
draw.Draw(m.pixels.(*image.NRGBA), image.Rect(
px.X, px.Y,
px.X+16, px.Y+16,
), tile, image.Pt(0, 0), draw.Src)
}
return m.pixels
}

View File

@ -1 +0,0 @@
package gui

15
ui/gui/uis.go Normal file
View File

@ -0,0 +1,15 @@
package gui
import (
"fyne.io/fyne/v2"
"github.com/bedrock-tool/bedrocktool/utils"
)
type HandlerFunc func(name string, data interface{}) utils.MessageResponse
type CommandUI interface {
Layout() fyne.CanvasObject
Handler() HandlerFunc
}
var CommandUIs = map[string]CommandUI{}

54
ui/gui/worlds.go Normal file
View File

@ -0,0 +1,54 @@
package gui
import (
"image"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/widget"
"github.com/bedrock-tool/bedrocktool/utils"
"github.com/sandertv/gophertunnel/minecraft/protocol"
)
type worldsUI struct {
mapElement *mapWidget
}
func (w *worldsUI) Layout() fyne.CanvasObject {
w.mapElement = &mapWidget{}
return container.NewVBox(
widget.NewRichTextFromMarkdown("# worlds Ui!"),
w.mapElement,
)
}
func (w *worldsUI) handler(name string, data interface{}) utils.MessageResponse {
r := utils.MessageResponse{
Ok: false,
Data: nil,
}
switch name {
case "init_map":
init_map := data.(struct {
GetTiles func() map[protocol.ChunkPos]*image.RGBA
GetBounds func() (min, max protocol.ChunkPos)
})
w.mapElement.GetBounds = init_map.GetBounds
w.mapElement.GetTiles = init_map.GetTiles
r.Ok = true
case "update_map":
w.mapElement.Refresh()
r.Ok = true
}
return r
}
func (w *worldsUI) Handler() HandlerFunc {
return w.handler
}
func init() {
CommandUIs["worlds"] = &worldsUI{}
}

1
ui/stub.go Normal file
View File

@ -0,0 +1 @@
package ui

View File

@ -1,16 +1,42 @@
package utils
import (
"context"
"flag"
"github.com/google/subcommands"
"github.com/sirupsen/logrus"
)
var ValidCMDs = make(map[string]Command, 0)
type Command interface {
Name() string
Synopsis() string
SetFlags(f *flag.FlagSet)
Execute(ctx context.Context, ui UI) error
}
type cmdWrap struct {
subcommands.Command
cmd Command
}
func (c *cmdWrap) Name() string { return c.cmd.Name() }
func (c *cmdWrap) Synopsis() string { return c.cmd.Synopsis() }
func (c *cmdWrap) SetFlags(f *flag.FlagSet) { c.cmd.SetFlags(f) }
func (c *cmdWrap) Usage() string { return c.Name() + ": " + c.Synopsis() }
func (c *cmdWrap) Execute(ctx context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
err := c.cmd.Execute(ctx, currentUI)
if err != nil {
logrus.Error(err)
return 1
}
return 0
}
func RegisterCommand(sub Command) {
subcommands.Register(sub, "")
subcommands.Register(&cmdWrap{cmd: sub}, "")
ValidCMDs[sub.Name()] = sub
}

View File

@ -84,6 +84,9 @@ func (d *DNSServer) handler(w dns.ResponseWriter, req *dns.Msg) {
}
func InitDNS() {
if !Options.EnableDNS {
return
}
d := DNSServer{}
dns.HandleFunc(".", d.handler)

View File

@ -1,6 +1,7 @@
package utils
import (
"bufio"
"context"
"flag"
"fmt"
@ -9,25 +10,56 @@ import (
"github.com/bedrock-tool/bedrocktool/locale"
"github.com/google/subcommands"
"github.com/sirupsen/logrus"
)
type UI interface {
Init()
SetOptions(context.Context) bool
Execute(context.Context) error
type MessageResponse struct {
Ok bool
Data interface{}
}
type InteractiveCLI struct {
type UI interface {
Init() bool
Start(context.Context) error
Message(name string, data interface{}) MessageResponse
ServerInput(context.Context, string) (string, string, error)
}
type BaseUI struct {
UI
}
func (c *InteractiveCLI) Init() {
func (u *BaseUI) Message(name string, data interface{}) MessageResponse {
return MessageResponse{
Ok: false,
Data: nil,
}
}
func (c *InteractiveCLI) SetOptions(ctx context.Context) bool {
func (u *BaseUI) ServerInput(ctx context.Context, server string) (string, string, error) {
address, name, err := ServerInput(ctx, server)
return address, name, err
}
var currentUI UI
func SetCurrentUI(ui UI) {
currentUI = ui
}
type InteractiveCLI struct {
BaseUI
}
func (c *InteractiveCLI) Init() bool {
currentUI = c
return true
}
func (c *InteractiveCLI) Start(ctx context.Context) error {
select {
case <-ctx.Done():
return true
return nil
default:
fmt.Println(locale.Loc("available_commands", nil))
for name, cmd := range ValidCMDs {
@ -37,18 +69,23 @@ func (c *InteractiveCLI) SetOptions(ctx context.Context) bool {
cmd, cancelled := UserInput(ctx, locale.Loc("input_command", nil))
if cancelled {
return true
return nil
}
_cmd := strings.Split(cmd, " ")
os.Args = append(os.Args, _cmd...)
}
flag.Parse()
return false
}
func (c *InteractiveCLI) Execute(ctx context.Context) error {
InitDNS()
InitExtraDebug()
subcommands.Execute(ctx)
if Options.IsInteractive {
logrus.Info(locale.Loc("enter_to_exit", nil))
input := bufio.NewScanner(os.Stdin)
input.Scan()
}
return nil
}

View File

@ -7,8 +7,6 @@ import (
"strings"
"github.com/bedrock-tool/bedrocktool/locale"
"github.com/google/subcommands"
"github.com/sirupsen/logrus"
)
func getRealm(ctx context.Context, realmName, id string) (name string, address string, err error) {
@ -34,25 +32,18 @@ func getRealm(ctx context.Context, realmName, id string) (name string, address s
type RealmListCMD struct{}
func (*RealmListCMD) Name() string { return "list-realms" }
func (*RealmListCMD) Synopsis() string { return locale.Loc("list_realms_synopsis", nil) }
func (*RealmListCMD) Name() string { return "list-realms" }
func (*RealmListCMD) Synopsis() string { return locale.Loc("list_realms_synopsis", nil) }
func (c *RealmListCMD) SetFlags(f *flag.FlagSet) {}
func (c *RealmListCMD) Usage() string {
return c.Name() + ": " + c.Synopsis() + "\n"
}
func (c *RealmListCMD) Execute(ctx context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
func (c *RealmListCMD) Execute(ctx context.Context, ui UI) error {
realms, err := GetRealmsAPI().Realms(ctx)
if err != nil {
logrus.Error(err)
return 1
return err
}
for _, realm := range realms {
fmt.Println(locale.Loc("realm_list_line", locale.Strmap{"Name": realm.Name, "Id": realm.ID}))
}
return 0
return nil
}
func init() {

View File

@ -8,6 +8,7 @@ import (
"crypto/sha256"
"encoding/json"
"errors"
"io"
"net"
"os"
"path"
@ -16,6 +17,7 @@ import (
"sync"
"github.com/bedrock-tool/bedrocktool/locale"
"github.com/bedrock-tool/bedrocktool/utils/crypt"
"github.com/google/uuid"
"github.com/sandertv/gophertunnel/minecraft"
"github.com/sirupsen/logrus"
@ -195,3 +197,40 @@ func CfbDecrypt(data []byte, key []byte) ([]byte, error) {
}
return data, nil
}
func InitExtraDebug() {
if !Options.ExtraDebug {
return
}
Options.Debug = true
var logPlain, logCryptEnc io.WriteCloser = nil, nil
// open plain text log
logPlain, err := os.Create("packets.log")
if err != nil {
logrus.Error(err)
} else {
defer logPlain.Close()
}
// open gpg log
logCrypt, err := os.Create("packets.log.gpg")
if err != nil {
logrus.Error(err)
} else {
defer logCrypt.Close()
// encrypter for the log
logCryptEnc, err = crypt.Encer("packets.log", logCrypt)
if err != nil {
logrus.Error(err)
} else {
defer logCryptEnc.Close()
}
}
FLog = io.MultiWriter(logPlain, logCryptEnc)
if err != nil {
logrus.Error(err)
}
}