282 lines
6.2 KiB
Plaintext
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
|
|
}
|