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{})
+}