diff --git a/Readme.md b/Readme.md index 13f0d29..206b384 100644 --- a/Readme.md +++ b/Readme.md @@ -1,6 +1,18 @@ # bedrocktool +a minecraft bedrock proxy that can among other things save worlds from servers -## [releases](https://github.com/bedrock-tool/bedrocktool/releases) +
+ +## downloads: +### [here](https://github.com/bedrock-tool/bedrocktool/releases) + +
+ +## issues: + +if you find an issue or a crash, please report it by opening a github issue with a screenshot of the crash or issue, thanks + +
``` Usage: bedrocktool @@ -8,7 +20,8 @@ Usage: bedrocktool Subcommands: capture capture packets in a pcap file help describe subcommands and their syntax - inject inject files into a minecraft install (USE WITH CAUTION) + list-realms prints all realms you have access to + merge merge 2 or more worlds packs download resource packs from a server realms-token print xbl3.0 token for realms api skins download all skins from players on a server @@ -17,6 +30,7 @@ Subcommands: Top-level flags (use "bedrocktool flags" for a full list): - -debug=false: debug mode - -dns=false: enable dns server for consoles + -debug=false: debug mode (enables extra logging useful for finding bugs) + -dns=false: enable dns server for consoles (use this if you need to connect on a console) + -preload=false: preload resourcepacks for proxy (use this if you need server resourcepacks while using the proxy) ``` \ No newline at end of file diff --git a/bpio.go b/bpio.go new file mode 100644 index 0000000..f7971d8 --- /dev/null +++ b/bpio.go @@ -0,0 +1,173 @@ +package main + +import ( + "context" + "errors" + "flag" + "fmt" + "os" + "sync" + "time" + + "github.com/google/subcommands" + buttplug "github.com/pidurentry/buttplug-go" + "github.com/sandertv/gophertunnel/minecraft" + "github.com/sandertv/gophertunnel/minecraft/protocol/packet" +) + +type BpCMD struct { + server_address string + buttplugIP string + dev buttplug.DeviceManager +} + +func (*BpCMD) Name() string { return "bpio" } +func (*BpCMD) Synopsis() string { return "buttplug.io intigration" } + +func (c *BpCMD) SetFlags(f *flag.FlagSet) { + f.StringVar(&c.server_address, "address", "", "remote server address") + f.StringVar(&c.buttplugIP, "bpaddr", "192.168.178.50", "other address") +} +func (c *BpCMD) Usage() string { + return c.Name() + ": " + c.Synopsis() + "\n" +} + +func (c *BpCMD) Execute(ctx context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus { + fmt.Println("connecting to buttplug server") + bp, err := buttplug.Dial(fmt.Sprintf("ws://%s:12345", c.buttplugIP)) + if err != nil { + fmt.Fprintf(os.Stderr, "buttplug connect error: %s\n", err) + } + handler := buttplug.NewHandler(bp) + device_manager := buttplug.NewDeviceManager(handler) + c.dev = device_manager + + scan := c.dev.Scan(30 * time.Second) + fmt.Println("Scanning for devices...") + go func() { + scan.Wait() + fmt.Println("Stopped scanning") + fmt.Println("Found: (") + for _, d := range c.dev.Devices() { + fmt.Printf("\t%s\n", d.DeviceName()) + } + fmt.Println(")") + }() + + address, _, err := server_input(c.server_address) + if err != nil { + fmt.Fprint(os.Stderr, err) + return 1 + } + + listener, clientConn, serverConn, err := create_proxy(ctx, address) + if err != nil { + fmt.Fprintln(os.Stderr, err) + return 1 + } + defer listener.Close() + + println("Connected") + + type vib struct { + v buttplug.Vibrate + end time.Time + } + + vibrates := make(map[string]vib) + vib_mutex := &sync.Mutex{} + + go func() { + for { + now := time.Now() + var to_clear []string + vib_mutex.Lock() + for name, v := range vibrates { + if now.After(v.end) { + fmt.Println("stopping vibrate") + v.v.Stop() + to_clear = append(to_clear, name) + } + } + for _, v := range to_clear { + delete(vibrates, v) + } + vib_mutex.Unlock() + time.Sleep(1 * time.Second) + } + }() + + vibrate_ms := func(v buttplug.Vibrate, ms int) { + vib_mutex.Lock() + var name string = string(v.DeviceName()) + t := time.Now() + if v_e, ok := vibrates[name]; ok { + t = v_e.end + } + vibrates[name] = vib{ + v: v, + end: t.Add(time.Duration(ms) * time.Millisecond), + } + vib_mutex.Unlock() + } + + errs := make(chan error, 2) + go func() { // server -> client + defer serverConn.Close() + defer listener.Disconnect(clientConn, "connection lost") + for { + pk, err := serverConn.ReadPacket() + if err != nil { + if disconnect, ok := errors.Unwrap(err).(minecraft.DisconnectError); ok { + _ = listener.Disconnect(clientConn, disconnect.Error()) + } + return + } + + if err = clientConn.WritePacket(pk); err != nil { + return + } + } + }() + + go func() { // client -> server + for { + pk, err := clientConn.ReadPacket() + if err != nil { + return + } + + switch pk := pk.(type) { + case *packet.Animate: + if pk.ActionType == packet.AnimateActionSwingArm { + devs := device_manager.Vibrators() + fmt.Printf("vibrating %d devices\n", len(devs)) + for _, v := range devs { + vibrate_ms(v, 1000) + } + } + } + + if err := serverConn.WritePacket(pk); err != nil { + if disconnect, ok := errors.Unwrap(err).(minecraft.DisconnectError); ok { + _ = listener.Disconnect(clientConn, disconnect.Error()) + } + return + } + } + }() + + for { + select { + case err := <-errs: + fmt.Fprintln(os.Stderr, err) + return 1 + case <-ctx.Done(): + return 0 + } + } +} + +func init() { + register_command(&BpCMD{}) +}