bedrocktool/utils/resourcepack-ace.go.ignore

282 lines
6.2 KiB
Plaintext

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
}