diff --git a/cmd/bedrocktool/main.go b/cmd/bedrocktool/main.go index 930acee..2740d41 100644 --- a/cmd/bedrocktool/main.go +++ b/cmd/bedrocktool/main.go @@ -10,7 +10,11 @@ import ( "regexp" "syscall" - "bedrocktool/cmd/bedrocktool/utils" + "github.com/bedrock-tool/bedrocktool/utils" + + _ "github.com/bedrock-tool/bedrocktool/subcommands" + _ "github.com/bedrock-tool/bedrocktool/subcommands/skins" + _ "github.com/bedrock-tool/bedrocktool/subcommands/world" "github.com/google/subcommands" "github.com/sirupsen/logrus" diff --git a/cmd/bedrocktool/utils/resourcepack-ace.go.ignore b/cmd/bedrocktool/utils/resourcepack-ace.go.ignore deleted file mode 100644 index a8b6fee..0000000 Binary files a/cmd/bedrocktool/utils/resourcepack-ace.go.ignore and /dev/null differ diff --git a/go.mod b/go.mod index d142e88..de94325 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module bedrocktool +module github.com/bedrock-tool/bedrocktool go 1.19 @@ -34,9 +34,9 @@ require ( github.com/jinzhu/copier v0.3.5 github.com/klauspost/compress v1.15.9 // indirect github.com/muhammadmuzzammil1998/jsonc v1.0.0 // indirect - github.com/sandertv/go-raknet v1.11.1 - github.com/sirupsen/logrus v1.9.0 // indirect - go.uber.org/atomic v1.10.0 // indirect + github.com/sandertv/go-raknet v1.11.1 // indirect + github.com/sirupsen/logrus v1.9.0 + go.uber.org/atomic v1.10.0 golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa // indirect golang.org/x/image v0.0.0-20220722155232-062f8c9fd539 golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect diff --git a/go.sum b/go.sum index 6a8c162..d69f481 100644 --- a/go.sum +++ b/go.sum @@ -55,6 +55,8 @@ github.com/olebeck/gophertunnel v1.24.8-2 h1:T7WY2M/GrKTOgcH1RcDiB8LNlhK+PEzGdHS github.com/olebeck/gophertunnel v1.24.8-2/go.mod h1:dMOw79FHxr2azEqiGH20AwdljisAN1kqwu5SjPBnZ5k= github.com/olebeck/gophertunnel v1.24.8-3 h1:qR5PCFWUAcUureCt36tLqaXWOjHVsFx66PiKm1obtkY= github.com/olebeck/gophertunnel v1.24.8-3/go.mod h1:dMOw79FHxr2azEqiGH20AwdljisAN1kqwu5SjPBnZ5k= +github.com/olebeck/gophertunnel v1.24.8-4 h1:V0Giy93JYDzR6NhtXOw/UcWpY85Jt/czp7xcAfJz22Y= +github.com/olebeck/gophertunnel v1.24.8-4/go.mod h1:dMOw79FHxr2azEqiGH20AwdljisAN1kqwu5SjPBnZ5k= github.com/olebeck/gophertunnel v1.24.8 h1:jdqBOABDAE1yISqzm9IxIrI+/lJApLBjTieynXUSalw= github.com/olebeck/gophertunnel v1.24.8/go.mod h1:dMOw79FHxr2azEqiGH20AwdljisAN1kqwu5SjPBnZ5k= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= diff --git a/cmd/bedrocktool/subcommands/capture.go b/subcommands/capture.go similarity index 97% rename from cmd/bedrocktool/subcommands/capture.go rename to subcommands/capture.go index f32c86f..cd55f5a 100644 --- a/cmd/bedrocktool/subcommands/capture.go +++ b/subcommands/capture.go @@ -1,4 +1,4 @@ -package main +package subcommands import ( "bytes" @@ -10,7 +10,7 @@ import ( "os" "time" - "bedrocktool/cmd/bedrocktool/utils" + "github.com/bedrock-tool/bedrocktool/utils" "github.com/google/gopacket" "github.com/google/gopacket/layers" diff --git a/cmd/bedrocktool/subcommands/merge.go b/subcommands/merge.go similarity index 97% rename from cmd/bedrocktool/subcommands/merge.go rename to subcommands/merge.go index 2b93ae6..7b45642 100644 --- a/cmd/bedrocktool/subcommands/merge.go +++ b/subcommands/merge.go @@ -1,4 +1,4 @@ -package main +package subcommands import ( "context" @@ -8,7 +8,7 @@ import ( "os" "time" - "bedrocktool/cmd/bedrocktool/utils" + "github.com/bedrock-tool/bedrocktool/utils" "github.com/df-mc/dragonfly/server/world/mcdb" "github.com/df-mc/goleveldb/leveldb/opt" diff --git a/cmd/bedrocktool/subcommands/skins/skins-proxy.go b/subcommands/skins/skins-proxy.go similarity index 97% rename from cmd/bedrocktool/subcommands/skins/skins-proxy.go rename to subcommands/skins/skins-proxy.go index 51f6358..5f59f45 100644 --- a/cmd/bedrocktool/subcommands/skins/skins-proxy.go +++ b/subcommands/skins/skins-proxy.go @@ -6,7 +6,7 @@ import ( "fmt" "os" - "bedrocktool/cmd/bedrocktool/utils" + "github.com/bedrock-tool/bedrocktool/utils" "github.com/google/subcommands" "github.com/sandertv/gophertunnel/minecraft/protocol/packet" diff --git a/cmd/bedrocktool/subcommands/skins/skins.go b/subcommands/skins/skins.go similarity index 99% rename from cmd/bedrocktool/subcommands/skins/skins.go rename to subcommands/skins/skins.go index 9e9bd48..119e446 100644 --- a/cmd/bedrocktool/subcommands/skins/skins.go +++ b/subcommands/skins/skins.go @@ -13,7 +13,7 @@ import ( "path" "strings" - "bedrocktool/cmd/bedrocktool/utils" + "github.com/bedrock-tool/bedrocktool/utils" "github.com/flytam/filenamify" "github.com/google/subcommands" diff --git a/cmd/bedrocktool/subcommands/world/chunk_render.go b/subcommands/world/chunk_render.go similarity index 97% rename from cmd/bedrocktool/subcommands/world/chunk_render.go rename to subcommands/world/chunk_render.go index 37e0625..2135690 100644 --- a/cmd/bedrocktool/subcommands/world/chunk_render.go +++ b/subcommands/world/chunk_render.go @@ -4,7 +4,7 @@ import ( "image" "image/color" - "bedrocktool/cmd/bedrocktool/utils" + "github.com/bedrock-tool/bedrocktool/utils" "github.com/df-mc/dragonfly/server/block" "github.com/df-mc/dragonfly/server/block/cube" diff --git a/cmd/bedrocktool/subcommands/world/chunk_test.go b/subcommands/world/chunk_test.go similarity index 100% rename from cmd/bedrocktool/subcommands/world/chunk_test.go rename to subcommands/world/chunk_test.go diff --git a/cmd/bedrocktool/subcommands/world/map_item.go b/subcommands/world/map_item.go similarity index 99% rename from cmd/bedrocktool/subcommands/world/map_item.go rename to subcommands/world/map_item.go index 63f6e19..2b7ba24 100644 --- a/cmd/bedrocktool/subcommands/world/map_item.go +++ b/subcommands/world/map_item.go @@ -9,7 +9,7 @@ import ( "sync" "time" - "bedrocktool/cmd/bedrocktool/utils" + "github.com/bedrock-tool/bedrocktool/utils" "github.com/df-mc/dragonfly/server/world/chunk" "github.com/sandertv/gophertunnel/minecraft/protocol" diff --git a/cmd/bedrocktool/subcommands/world/world.go b/subcommands/world/world.go similarity index 99% rename from cmd/bedrocktool/subcommands/world/world.go rename to subcommands/world/world.go index 01c6ba4..873b5dc 100644 --- a/cmd/bedrocktool/subcommands/world/world.go +++ b/subcommands/world/world.go @@ -15,7 +15,7 @@ import ( "strings" "time" - "bedrocktool/cmd/bedrocktool/utils" + "github.com/bedrock-tool/bedrocktool/utils" "github.com/df-mc/dragonfly/server/block/cube" "github.com/df-mc/dragonfly/server/world" diff --git a/cmd/bedrocktool/utils/auth.go b/utils/auth.go similarity index 100% rename from cmd/bedrocktool/utils/auth.go rename to utils/auth.go diff --git a/cmd/bedrocktool/utils/command_register.go b/utils/command_register.go similarity index 100% rename from cmd/bedrocktool/utils/command_register.go rename to utils/command_register.go diff --git a/cmd/bedrocktool/utils/dns.go b/utils/dns.go similarity index 100% rename from cmd/bedrocktool/utils/dns.go rename to utils/dns.go diff --git a/cmd/bedrocktool/utils/images.go b/utils/images.go similarity index 100% rename from cmd/bedrocktool/utils/images.go rename to utils/images.go diff --git a/cmd/bedrocktool/utils/input.go b/utils/input.go similarity index 100% rename from cmd/bedrocktool/utils/input.go rename to utils/input.go diff --git a/cmd/bedrocktool/utils/net.go b/utils/net.go similarity index 100% rename from cmd/bedrocktool/utils/net.go rename to utils/net.go diff --git a/cmd/bedrocktool/utils/packet_logger.go b/utils/packet_logger.go similarity index 100% rename from cmd/bedrocktool/utils/packet_logger.go rename to utils/packet_logger.go diff --git a/cmd/bedrocktool/utils/proxy.go b/utils/proxy.go similarity index 100% rename from cmd/bedrocktool/utils/proxy.go rename to utils/proxy.go diff --git a/cmd/bedrocktool/utils/realms.go b/utils/realms.go similarity index 100% rename from cmd/bedrocktool/utils/realms.go rename to utils/realms.go diff --git a/cmd/bedrocktool/utils/replay.go b/utils/replay.go similarity index 100% rename from cmd/bedrocktool/utils/replay.go rename to utils/replay.go diff --git a/utils/resourcepack-ace.go.ignore b/utils/resourcepack-ace.go.ignore new file mode 100644 index 0000000..7d06e12 --- /dev/null +++ b/utils/resourcepack-ace.go.ignore @@ -0,0 +1,281 @@ +package utils + +import ( + "archive/zip" + "bytes" + "context" + "crypto/aes" + "encoding/json" + "flag" + "fmt" + "io" + "os" + "path" + "strings" + + "github.com/flytam/filenamify" + "github.com/google/subcommands" + "github.com/sandertv/gophertunnel/minecraft" + "github.com/sandertv/gophertunnel/minecraft/resource" + "github.com/sirupsen/logrus" +) + +const KEYS_FILE = "keys.db" + +// decrypt using cfb with segmentsize = 1 +func cfb_decrypt(data []byte, key []byte) ([]byte, error) { + cipher, err := aes.NewCipher([]byte(key)) + if err != nil { + return nil, err + } + + shift_register := append(key[:16], data...) + iv := make([]byte, 16) + off := 0 + for ; off < len(data); off += 1 { + cipher.Encrypt(iv, shift_register) + data[off] ^= iv[0] + shift_register = shift_register[1:] + } + return data, nil +} + +type content_item struct { + Path string `json:"path"` + Key string `json:"key"` +} + +type Content struct { + Content []content_item `json:"content"` +} + +type Pack struct { + resource.Pack +} + +func (p *Pack) ReadAll() ([]byte, error) { + buf := make([]byte, p.Len()) + off := 0 + for { + n, err := p.ReadAt(buf[off:], int64(off)) + if err != nil { + if err == io.EOF { + break + } + return nil, err + } + off += n + } + return buf, nil +} + +func (p *Pack) Decrypt() ([]byte, error) { + data, err := p.ReadAll() + if err != nil { + return nil, err + } + z, err := zip.NewReader(bytes.NewReader(data), int64(len(data))) + if err != nil { + return nil, err + } + + zip_out_buf := bytes.NewBuffer(nil) + zw := zip.NewWriter(zip_out_buf) + + written := make(map[string]interface{}) + + // read key contents file + var content Content = Content{} + { + ff, err := z.Open("contents.json") + if err != nil { + if !os.IsNotExist(err) { + return nil, err + } + } else { + fw, _ := zw.Create("contents.json") + buf, _ := io.ReadAll(ff) + if err := json.Unmarshal(buf, &content); err != nil { + dec, _ := cfb_decrypt(buf[0x100:], []byte(p.ContentKey())) + dec = bytes.Split(dec, []byte("\x00"))[0] + fw.Write(dec) + if err := json.Unmarshal(dec, &content); err != nil { + return nil, err + } + } else { + fw.Write(buf) + } + written["contents.json"] = true + } + } + + // decrypt each file in the contents file + for _, entry := range content.Content { + ff, _ := z.Open(entry.Path) + stat, _ := ff.Stat() + if ff == nil || stat.IsDir() { + continue + } + buf, _ := io.ReadAll(ff) + if entry.Key != "" { + buf, _ = cfb_decrypt(buf, []byte(entry.Key)) + } + fw, _ := zw.Create(entry.Path) + fw.Write(buf) + written[entry.Path] = true + } + + // copy files not in the contents file + for _, src_file := range z.File { + if written[src_file.Name] == nil { + zw.Copy(src_file) + } + } + + zw.Close() + return zip_out_buf.Bytes(), nil +} + +func dump_keys(keys map[string]string) { + f, err := os.OpenFile(KEYS_FILE, os.O_APPEND|os.O_RDWR|os.O_CREATE, 0o775) + if err != nil { + panic(err) + } + defer f.Close() + + data, err := io.ReadAll(f) + if err != nil { + panic(err) + } + lines := strings.Split(strings.ReplaceAll(string(data), "\r\n", "\n"), "\n") + existing_keys := map[string]string{} + for _, v := range lines { + if len(v) == 0 { + continue + } + item := strings.Split(v, "=") + existing_keys[item[0]] = item[1] + } + + for uuid, key := range keys { + if key == "" { + continue + } + existing := existing_keys[uuid] + if existing != "" { + logrus.Warnf("key %s exists already\n", uuid) + if existing != key { + logrus.Warnf("uuid:%s, key in db: %s != new key %s\n", uuid, existing, key) + } + continue + } + f.WriteString(uuid + "=" + key + "\n") + } +} + +type ResourcePackCMD struct { + server_address string + save_encrypted bool +} + +func (*ResourcePackCMD) Name() string { return "packs" } +func (*ResourcePackCMD) Synopsis() string { return "download resource packs from a server" } + +func (c *ResourcePackCMD) SetFlags(f *flag.FlagSet) { + f.StringVar(&c.server_address, "address", "", "remote server address") + f.BoolVar(&c.save_encrypted, "save-encrypted", false, "save encrypted resourcepacks") +} + +func (c *ResourcePackCMD) Usage() string { + return c.Name() + ": " + c.Synopsis() + "\n" + SERVER_ADDRESS_HELP +} + +func (c *ResourcePackCMD) Execute(ctx context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus { + address, hostname, err := ServerInput(c.server_address) + if err != nil { + fmt.Fprint(os.Stderr, err) + return 1 + } + + serverConn, err := ConnectServer(ctx, address, nil, true, nil) + if err != nil { + fmt.Fprint(os.Stderr, err) + return 1 + } + serverConn.Close() + logrus.Info("Received") + + if len(serverConn.ResourcePacks()) > 0 { + logrus.Info("Decrypting Resource Packs") + dir := "packs/" + hostname + os.MkdirAll(dir, 0o777) + + pack_names := make(map[string]int) + for _, pack := range serverConn.ResourcePacks() { + pack_names[pack.Name()] += 1 + } + + // dump keys, download and decrypt the packs + keys := make(map[string]string) + for _, pack := range serverConn.ResourcePacks() { + pack := &Pack{*pack} + keys[pack.UUID()] = pack.ContentKey() + + pack_name := pack.Name() + if pack_names[pack_name] >= 2 { + pack_name += "_" + pack.UUID() + } + pack_name, _ = filenamify.FilenamifyV2(pack_name) + + logrus.Infof("ResourcePack(Id: %s Key: %s | Name: %s %s)\n", pack.UUID(), keys[pack.UUID()], pack_name, pack.Version()) + + if c.save_encrypted { + data, err := pack.ReadAll() + if err != nil { + fmt.Fprint(os.Stderr, err) + return 1 + } + os.WriteFile(path.Join(dir, pack_name+".zip"), data, 0o644) + } + logrus.Infof("Decrypting...") + + data, err := pack.Decrypt() + if err != nil { + fmt.Fprint(os.Stderr, err) + return 1 + } + os.WriteFile(path.Join(dir, pack_name+".mcpack"), data, 0o644) + } + logrus.Infof("Writing keys to %s", KEYS_FILE) + dump_keys(keys) + } else { + logrus.Warn("No Resourcepack sent") + } + fmt.Printf("Done!\n") + return 0 +} + +func init() { + RegisterCommand(&ResourcePackCMD{}) +} + +func GetPacks(server *minecraft.Conn) (packs map[string]*resource.Pack, err error) { + packs = make(map[string]*resource.Pack) + for _, pack := range server.ResourcePacks() { + _pack := &Pack{*pack} + if _pack.Encrypted() { + data, err := _pack.Decrypt() + if err != nil { + return nil, err + } + pack2, err := resource.FromBytes(data) + if err != nil { + return nil, err + } + packs[pack.Name()] = pack2 + } else { + packs[pack.Name()] = pack + } + } + return +} diff --git a/cmd/bedrocktool/utils/resourcepack.go b/utils/resourcepack.go similarity index 100% rename from cmd/bedrocktool/utils/resourcepack.go rename to utils/resourcepack.go diff --git a/cmd/bedrocktool/utils/utils.go b/utils/utils.go similarity index 100% rename from cmd/bedrocktool/utils/utils.go rename to utils/utils.go diff --git a/cmd/bedrocktool/utils/zip.go b/utils/zip.go similarity index 100% rename from cmd/bedrocktool/utils/zip.go rename to utils/zip.go