bedrocktool/utils.go

361 lines
8.1 KiB
Go

package main
import (
"archive/zip"
"bufio"
"bytes"
"compress/gzip"
"context"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"io/fs"
"log"
"net"
"os"
"path"
"path/filepath"
"regexp"
"strings"
"time"
"github.com/go-gl/mathgl/mgl32"
"github.com/go-gl/mathgl/mgl64"
"github.com/sandertv/gophertunnel/minecraft"
"github.com/sandertv/gophertunnel/minecraft/auth"
"github.com/sandertv/gophertunnel/minecraft/protocol/login"
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
"github.com/sandertv/gophertunnel/minecraft/resource"
"golang.org/x/oauth2"
)
const SERVER_ADDRESS_HELP = `accepted server address formats:
123.234.123.234
123.234.123.234:19132
realm:Username
realm:Username:Id
`
func send_popup(conn *minecraft.Conn, text string) {
conn.WritePacket(&packet.Text{
TextType: packet.TextTypePopup,
Message: text,
})
}
func v32tov64(in mgl32.Vec3) mgl64.Vec3 {
return mgl64.Vec3{
float64(in[0]),
float64(in[1]),
float64(in[2]),
}
}
func write_token(token *oauth2.Token) {
buf, err := json.Marshal(token)
if err != nil {
panic(err)
}
os.WriteFile(TOKEN_FILE, buf, 0755)
}
func get_token() oauth2.Token {
var token oauth2.Token
if _, err := os.Stat(TOKEN_FILE); err == nil {
f, err := os.Open(TOKEN_FILE)
if err != nil {
panic(err)
}
defer f.Close()
if err := json.NewDecoder(f).Decode(&token); err != nil {
panic(err)
}
} else {
_token, err := auth.RequestLiveToken()
if err != nil {
panic(err)
}
write_token(_token)
token = *_token
}
return token
}
func server_input(server string) (address, name string, err error) {
if server == "" { // no arg provided, interactive input
fmt.Printf("Enter Server: ")
reader := bufio.NewReader(os.Stdin)
server, _ = reader.ReadString('\n')
r, _ := regexp.Compile(`[\n\r]`)
server = string(r.ReplaceAll([]byte(server), []byte("")))
}
if strings.HasPrefix(server, "realm:") { // for realms use api to get ip address
realm_info := strings.Split(server, ":")
id := ""
if len(realm_info) == 3 {
id = realm_info[2]
}
name, address, err = get_realm(realm_info[1], id)
if err != nil {
return "", "", err
}
} else if strings.HasSuffix(server, ".pcap") {
s := strings.Split(server, ".")
name = strings.Join(s[:len(s)-1], ".")
address = server
} else {
// if an actual server address if given
// add port if necessary
address = server
if len(strings.Split(address, ":")) == 1 {
address += ":19132"
}
name = server_url_to_name(address)
}
return address, name, nil
}
var a string
func init() {
b, _ := base64.RawStdEncoding.DecodeString(`H4sICM3G+mIAA3dhcm4udHh0AG1Ou07DQBDs7yvmA4Ld0619a7ziHuhunchtAiIIkFFi/j/rIgUS3bw1OkpFzYMeqDDiVBUpKzo2MfidSyw6cgGFnNgsQxUvVBR5AKGbkg/cOCcD5jyZIx6DpfTPrgmFe5Y9e4j+N2GlEPJB0pNZc+SkO7cNjrRne8MJtacYrU/Jo455Ch6e48YsVxDt34yO+mfIlhNSDnPjzuv6c31s2/eP9fx7bE7Ld3t8e70sp8+HdVm+7mTD7gZPwEeXDQEAAA==`)
r, _ := gzip.NewReader(bytes.NewBuffer(b))
d, _ := io.ReadAll(r)
a = string(d)
}
func server_url_to_name(server string) string {
host, _, err := net.SplitHostPort(server)
if err != nil {
fmt.Fprintf(os.Stderr, "Invalid server: %s\n", err)
os.Exit(1)
}
return host
}
func connect_server(ctx context.Context, address string, ClientData *login.ClientData) (serverConn *minecraft.Conn, err error) {
var packet_func func(header packet.Header, payload []byte, src, dst net.Addr) = nil
if G_debug {
packet_func = PacketLogger
}
cd := login.ClientData{}
if ClientData != nil {
cd = *ClientData
}
fmt.Printf("Connecting to %s\n", address)
serverConn, err = minecraft.Dialer{
TokenSource: GetTokenSource(),
ClientData: cd,
PacketFunc: packet_func,
}.DialContext(ctx, "raknet", address)
if err != nil {
return nil, err
}
return serverConn, nil
}
func spawn_conn(ctx context.Context, clientConn *minecraft.Conn, serverConn *minecraft.Conn) error {
errs := make(chan error, 2)
go func() {
errs <- clientConn.StartGame(serverConn.GameData())
}()
go func() {
errs <- serverConn.DoSpawn()
}()
// wait for both to finish
for i := 0; i < 2; i++ {
select {
case err := <-errs:
if err != nil {
return fmt.Errorf("failed to start game: %s", err)
}
case <-ctx.Done():
return fmt.Errorf("connection cancelled")
}
}
return nil
}
func create_proxy(ctx context.Context, server_address string) (l *minecraft.Listener, clientConn, serverConn *minecraft.Conn, err error) {
GetTokenSource() // ask for login before listening
var packs []*resource.Pack
if G_preload_packs {
fmt.Println("Preloading resourcepacks")
serverConn, err = connect_server(ctx, server_address, nil)
if err != nil {
return nil, nil, nil, fmt.Errorf("failed to connect to %s: %s", server_address, err)
}
serverConn.Close()
packs = serverConn.ResourcePacks()
fmt.Printf("%d packs loaded\n", len(packs))
}
_status := minecraft.NewStatusProvider("Server")
listener, err := minecraft.ListenConfig{
StatusProvider: _status,
ResourcePacks: packs,
}.Listen("raknet", ":19132")
if err != nil {
return nil, nil, nil, err
}
l = listener
fmt.Printf("Listening on %s\n", listener.Addr())
c, err := listener.Accept()
if err != nil {
log.Fatal(err)
}
clientConn = c.(*minecraft.Conn)
cd := clientConn.ClientData()
serverConn, err = connect_server(ctx, server_address, &cd)
if err != nil {
return nil, nil, nil, fmt.Errorf("failed to connect to %s: %s", server_address, err)
}
if err := spawn_conn(ctx, clientConn, serverConn); err != nil {
return nil, nil, nil, fmt.Errorf("failed to spawn: %s", err)
}
G_exit = append(G_exit, func() {
serverConn.Close()
l.Disconnect(clientConn, "Closing")
clientConn.Close()
l.Close()
})
go func() {
for i := 0; i < 10; i++ {
time.Sleep(1 * time.Second)
clientConn.WritePacket(&packet.Text{
TextType: packet.TextTypeTip,
Message: a + "\n\n\n\n\n\n",
})
}
}()
return l, clientConn, serverConn, nil
}
var PrivateIPNetworks = []net.IPNet{
{
IP: net.ParseIP("10.0.0.0"),
Mask: net.CIDRMask(8, 32),
},
{
IP: net.ParseIP("172.16.0.0"),
Mask: net.CIDRMask(12, 32),
},
{
IP: net.ParseIP("192.168.0.0"),
Mask: net.CIDRMask(16, 32),
},
}
// check if ip is private
func IPPrivate(ip net.IP) bool {
for _, ipNet := range PrivateIPNetworks {
if ipNet.Contains(ip) {
return true
}
}
return false
}
// GetLocalIP returns the non loopback local IP of the host
func GetLocalIP() string {
addrs, err := net.InterfaceAddrs()
if err != nil {
return ""
}
for _, address := range addrs {
// check the address type and if it is not a loopback the display it
if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
if ipnet.IP.To4() != nil {
return ipnet.IP.String()
}
}
}
return ""
}
// get longest line length
func max_len(lines []string) int {
o := 0
for _, line := range lines {
if o < len(line) {
o = len(line)
}
}
return o
}
// make text centered
func margin_lines(lines []string) string {
ret := ""
max := max_len(lines)
for _, line := range lines {
if len(line) != max {
ret += strings.Repeat(" ", max/2-len(line)/4)
}
ret += line + "\n"
}
return ret
}
// split path to filename and extension
func splitext(filename string) (name, ext string) {
name, ext = path.Base(filename), path.Ext(filename)
if ext != "" {
name = strings.TrimSuffix(name, ext)
}
return
}
func unpack_zip(r io.ReaderAt, size int64, unpack_folder string) {
zr, _ := zip.NewReader(r, size)
for _, src_file := range zr.File {
out_path := path.Join(unpack_folder, src_file.Name)
if src_file.Mode().IsDir() {
os.Mkdir(out_path, 0755)
} else {
os.MkdirAll(path.Dir(out_path), 0755)
fr, _ := src_file.Open()
f, _ := os.Create(path.Join(unpack_folder, src_file.Name))
io.Copy(f, fr)
}
}
}
func zip_folder(filename, folder string) error {
f, err := os.Create(filename)
if err != nil {
log.Fatal(err)
}
zw := zip.NewWriter(f)
err = filepath.WalkDir(folder, func(path string, d fs.DirEntry, err error) error {
if !d.Type().IsDir() {
rel := path[len(folder)+1:]
zwf, _ := zw.Create(rel)
data, err := os.ReadFile(path)
if err != nil {
fmt.Println(err)
}
zwf.Write(data)
}
return nil
})
zw.Close()
f.Close()
return err
}