Compare commits
82 Commits
d0ecf1c261
...
7a6061dba2
Author | SHA1 | Date |
---|---|---|
olebeck | 7a6061dba2 | |
olebeck | 3bf371d07c | |
olebeck | ced2b28d9b | |
olebeck | c8aa5389c1 | |
olebeck | 525a5e64fe | |
olebeck | 4cc99f5753 | |
olebeck | 3bba95dc64 | |
olebeck | 105a318aa8 | |
olebeck | 7ddec1b09d | |
olebeck | 3c87663d02 | |
olebeck | 04293a74c5 | |
olebeck | a746dee75b | |
olebeck | 359ad83fc0 | |
olebeck | 3fbfbcf2fc | |
olebeck | e51bcc6b7f | |
olebeck | 31e6daba3a | |
olebeck | 710723aedb | |
olebeck | bd8e3fcddb | |
olebeck | a6488c98c9 | |
olebeck | a163c6757c | |
olebeck | 049a9a6d48 | |
olebeck | 006f604436 | |
olebeck | 8b763cde79 | |
olebeck | b73ef84c1f | |
olebeck | f2dc5000dc | |
olebeck | c5e7237273 | |
olebeck | c94d7038e9 | |
olebeck | 2bf6f3c534 | |
olebeck | 12ee9d2bcf | |
olebeck | c9850772d5 | |
olebeck | 5565301f11 | |
olebeck | f643e59e3f | |
olebeck | 818aa73a3e | |
olebeck | 06cf948913 | |
olebeck | ee156a1271 | |
olebeck | fbb2305d1c | |
olebeck | 3e6da4ebfe | |
olebeck | f661895d2f | |
olebeck | 5a54b98fd7 | |
olebeck | 5b5ddc92bb | |
olebeck | e8062430e7 | |
olebeck | c93cf7d0f9 | |
olebeck | 17572e8e94 | |
olebeck | 5485182135 | |
olebeck | 8365adb4cd | |
olebeck | d6b2f53f48 | |
olebeck | e53208969e | |
olebeck | 6acb821a5a | |
olebeck | 956448d707 | |
olebeck | f099996de7 | |
olebeck | 8450304c72 | |
olebeck | c4cc819dc2 | |
olebeck | 6670d575d7 | |
olebeck | 4d4eb37647 | |
olebeck | 55916e4e55 | |
olebeck | 2251d7096d | |
olebeck | a306a6e77f | |
olebeck | a601cb33a0 | |
olebeck | aed76d6372 | |
olebeck | 3a5a13aa75 | |
olebeck | 32f1e8019a | |
olebeck | ea6dea7953 | |
olebeck | e84b3ea2fe | |
olebeck | 0576f3ce63 | |
olebeck | ec5bae8b8f | |
olebeck | a4affc7d23 | |
olebeck | a4a293ef52 | |
olebeck | 2d3650f001 | |
olebeck | 9e99a9206b | |
olebeck | 2f9c74ae5c | |
olebeck | 12417c63a3 | |
olebeck | d5dec0af98 | |
olebeck | 56ac765b1d | |
olebeck | 3dba4525b5 | |
olebeck | 1cc242680a | |
olebeck | 1537c7f705 | |
olebeck | 1160e025ec | |
olebeck | 7d2974c9c4 | |
itz_Deprestion | 75396f908f | |
itz_Deprestion | 77d47124c8 | |
olebeck | f80061543c | |
olebeck | 47f85f78b3 |
|
@ -1 +1 @@
|
|||
utils/resourcepack-ace.go.ignore filter=git-crypt diff=git-crypt
|
||||
subcommands/resourcepack-d/resourcepack-d.go filter=git-crypt diff=git-crypt
|
|
@ -1,6 +1,8 @@
|
|||
name: ci-build
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '**'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
|
@ -13,51 +15,56 @@ jobs:
|
|||
fetch-depth: 0
|
||||
- run: |
|
||||
git fetch --force --tags
|
||||
|
||||
- uses: actions/setup-go@v3
|
||||
|
||||
- name: Setup Golang with cache
|
||||
uses: magnetikonline/action-golang-cache@v3
|
||||
with:
|
||||
go-version: '1.19'
|
||||
check-latest: true
|
||||
cache: true
|
||||
go-version-file: go.mod
|
||||
|
||||
- name: Install SSH Key
|
||||
if: ${{ env.SSH_PRIVATE_KEY != '' }}
|
||||
uses: shimataro/ssh-key-action@v2
|
||||
with:
|
||||
key: ${{ secrets.SSH_PRIVATE_KEY }}
|
||||
known_hosts: ${{ secrets.KNOWN_HOSTS }}
|
||||
env:
|
||||
SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
|
||||
|
||||
- uses: awalsh128/cache-apt-pkgs-action@latest
|
||||
if: ${{ env.REPO_KEY != '' }}
|
||||
with:
|
||||
packages: git-crypt xxd
|
||||
packages: git-crypt xxd gcc pkg-config libwayland-dev libx11-dev libx11-xcb-dev libxkbcommon-x11-dev libgles2-mesa-dev libegl1-mesa-dev libffi-dev libxcursor-dev libvulkan-dev
|
||||
version: 1.0
|
||||
env:
|
||||
REPO_KEY: ${{ secrets.REPO_KEY }}
|
||||
|
||||
- name: decrypt
|
||||
if: ${{ !env.ACT }}
|
||||
if: ${{ env.REPO_KEY != '' }}
|
||||
run: |
|
||||
echo ${REPO_KEY} | xxd -r -p > ../bedrock-repo-key.key
|
||||
git-crypt unlock ../bedrock-repo-key.key
|
||||
rm ../bedrock-repo-key.key
|
||||
env:
|
||||
REPO_KEY: ${{ secrets.REPO_KEY }}
|
||||
|
||||
- run: go install github.com/sanbornm/go-selfupdate/cmd/go-selfupdate
|
||||
- run: go get ./cmd/bedrocktool
|
||||
|
||||
- name: Install SSH Key
|
||||
uses: shimataro/ssh-key-action@v2
|
||||
with:
|
||||
key: ${{ secrets.SSH_PRIVATE_KEY }}
|
||||
known_hosts: ${{ secrets.KNOWN_HOSTS }}
|
||||
- name: dependencies
|
||||
run: |
|
||||
go get ./cmd/bedrocktool
|
||||
go install gioui.org/cmd/gogio@latest
|
||||
|
||||
- name: build
|
||||
id: build
|
||||
run: |
|
||||
make -j dists updates
|
||||
run: python build.py
|
||||
|
||||
- name: Deploy with rsync
|
||||
run: rsync -avz ./public/ olebeck@${{ secrets.SSH_HOST }}:/var/www/updates/bedrocktool/
|
||||
|
||||
- name: 'Get Previous tag'
|
||||
id: previoustag
|
||||
uses: "WyriHaximus/github-action-get-previous-tag@v1"
|
||||
if: ${{ env.SSH_HOST != '' }}
|
||||
run: rsync -avzO ./updates/ olebeck@${SSH_HOST}:/var/www/updates/
|
||||
env:
|
||||
SSH_HOST: ${{ secrets.SSH_HOST }}
|
||||
|
||||
- uses: "marvinpinto/action-automatic-releases@latest"
|
||||
with:
|
||||
repo_token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
automatic_release_tag: ${{ steps.previoustag.outputs.tag }}
|
||||
files: dist/*
|
||||
automatic_release_tag: ${{ steps.build.outputs.release_tag }}
|
||||
files: ./builds/*
|
||||
prerelease: false
|
||||
|
|
|
@ -8,6 +8,7 @@ token.json
|
|||
*.bmp
|
||||
*.bin
|
||||
*.log
|
||||
*.syso
|
||||
|
||||
/bedrocktool
|
||||
/bedrocktool-*
|
||||
|
@ -17,6 +18,12 @@ __debug_bin
|
|||
keys.db
|
||||
/skins/
|
||||
/worlds/
|
||||
/packs/
|
||||
/dist/
|
||||
/public/
|
||||
/builds/
|
||||
/builds/
|
||||
/updates/
|
||||
/fyne-cross/
|
||||
/tmp/
|
||||
|
||||
packets.log.gpg
|
||||
customdata.json
|
||||
|
|
59
Makefile
59
Makefile
|
@ -1,59 +0,0 @@
|
|||
TAG = $(shell git describe --exclude "r-*" --tags --always)
|
||||
NAME = bedrocktool-${TAG}
|
||||
SRCS = $(wildcard **/*.go)
|
||||
|
||||
GC = go build -ldflags "-s -w -X github.com/bedrock-tool/bedrocktool/utils.Version=${TAG}"
|
||||
|
||||
.PHONY: dists clean updates
|
||||
|
||||
# check if packs are supported
|
||||
HAVE_PACKS = false
|
||||
ifeq ($(shell head -c 7 ./utils/resourcepack-ace.go.ignore),package)
|
||||
HAVE_PACKS = true
|
||||
endif
|
||||
|
||||
$(info pack support: ${HAVE_PACKS})
|
||||
ifeq ($(HAVE_PACKS),true)
|
||||
GC += -overlay overlay.json
|
||||
endif
|
||||
|
||||
bedrocktool: $(SRCS)
|
||||
$(GC) -o $@ ./cmd/bedrocktool
|
||||
|
||||
BUILDS=\
|
||||
windows_386.exe\
|
||||
windows_amd64.exe\
|
||||
windows_arm64.exe\
|
||||
windows_arm.exe\
|
||||
darwin_amd64\
|
||||
darwin_arm64\
|
||||
linux_386\
|
||||
linux_amd64\
|
||||
linux_arm64\
|
||||
linux_arm
|
||||
|
||||
DISTS=$(BUILDS:%=dist/$(NAME)_%)
|
||||
dists: $(DISTS)
|
||||
$(DISTS): OS = $(word 2,$(subst _, ,$@))
|
||||
$(DISTS): ARCH = $(word 1,$(subst ., ,$(word 3,$(subst _, ,$@))))
|
||||
$(DISTS): BUILD = builds/$(OS)-$(ARCH)
|
||||
|
||||
dist builds:
|
||||
mkdir -p dist builds
|
||||
|
||||
$(DISTS): dist builds $(SRCS)
|
||||
$(info building: $@)
|
||||
GOOS=$(OS) GOARCH=$(ARCH) $(GC) -o $(BUILD) ./cmd/bedrocktool
|
||||
cp $(BUILD) $@
|
||||
|
||||
|
||||
UPDATES=$(BUILDS)
|
||||
$(UPDATES): OS = $(word 1,$(subst _, ,$@))
|
||||
$(UPDATES): ARCH = $(word 1,$(subst ., ,$(word 2,$(subst _, ,$@))))
|
||||
updates: $(UPDATES)
|
||||
|
||||
$(UPDATES): $(DISTS)
|
||||
go-selfupdate -platform $(OS)-$(ARCH) builds/ $(TAG)
|
||||
|
||||
clean:
|
||||
rm -r dist builds public
|
|
@ -0,0 +1,189 @@
|
|||
import subprocess, re, sys, os, shutil, json, binascii, hashlib, gzip
|
||||
|
||||
VER_RE = re.compile(r"v(\d\.\d+\.\d+)(?:-(\d+)-(\w))?")
|
||||
|
||||
NAME = "bedrocktool"
|
||||
APP_ID = "yuv.pink.bedrocktool"
|
||||
GIT_TAG = subprocess.run(["git", "describe", "--exclude", "r*", "--tags", "--always"], stdout=subprocess.PIPE).stdout.decode("utf8").split("\n")[0]
|
||||
if GIT_TAG == "":
|
||||
GIT_TAG = "v0.0.0"
|
||||
VER_MATCH = VER_RE.match(GIT_TAG)
|
||||
VER = VER_MATCH.group(1)
|
||||
PATCH = VER_MATCH.group(2) or "0"
|
||||
TAG = f"{VER}-{PATCH}"
|
||||
|
||||
print(f"VER: {VER}")
|
||||
print(f"TAG: {TAG}")
|
||||
|
||||
GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT")
|
||||
if GITHUB_OUTPUT:
|
||||
with open(GITHUB_OUTPUT, "a") as f:
|
||||
f.write(f"release_tag=r{VER}\n")
|
||||
|
||||
with open("./subcommands/resourcepack-d/resourcepack-d.go", "rb") as f:
|
||||
PACK_SUPPORT = f.read(100).count(b"package ") > 0
|
||||
print(f"Pack Support: {PACK_SUPPORT}")
|
||||
|
||||
LDFLAGS = f"-s -w -X github.com/bedrock-tool/bedrocktool/utils.Version={TAG}"
|
||||
|
||||
PLATFORMS = [
|
||||
("windows", ["amd64"], ".exe"),
|
||||
("linux", ["amd64"], ""),
|
||||
#("darwin", ["amd64", "arm64"], ""),
|
||||
#("android", ["arm64"], ".apk")
|
||||
]
|
||||
|
||||
|
||||
def clean():
|
||||
if os.path.exists("./tmp"):
|
||||
shutil.rmtree("./tmp")
|
||||
if os.path.exists("./builds"):
|
||||
shutil.rmtree("./builds")
|
||||
if os.path.exists("./updates"):
|
||||
shutil.rmtree("./updates")
|
||||
for file in os.listdir("./cmd/bedrocktool"):
|
||||
if file.endswith(".syso"):
|
||||
os.remove(f"./cmd/bedrocktool/{file}")
|
||||
|
||||
def make_dirs():
|
||||
os.mkdir("./tmp")
|
||||
os.mkdir("./builds")
|
||||
os.mkdir("./updates")
|
||||
|
||||
|
||||
def build_cli(platform: str, arch: str, env_in: dict[str,str], tags: list[str], ldflags, compiled_path: str):
|
||||
env = {}
|
||||
env.update(env_in)
|
||||
env.update({
|
||||
"GOOS": platform,
|
||||
"GOARCH": arch,
|
||||
})
|
||||
args = [
|
||||
"go", "build",
|
||||
"-ldflags", ldflags,
|
||||
"-trimpath",
|
||||
"-tags", ",".join(tags),
|
||||
"-o", compiled_path,
|
||||
"-v"
|
||||
]
|
||||
args.append("./cmd/bedrocktool")
|
||||
|
||||
env2 = os.environ.copy()
|
||||
env2.update(env)
|
||||
subprocess.run(args, env=env2).check_returncode()
|
||||
|
||||
|
||||
def build_gui(platform: str, arch: str, env, tags: list[str], ldflags, compiled_path: str):
|
||||
if platform == "windows":
|
||||
ldflags += " -H=windows"
|
||||
|
||||
args = [
|
||||
"gogio",
|
||||
"-arch", arch,
|
||||
"-target", platform,
|
||||
"-icon", "icon.png",
|
||||
"-tags", ",".join(tags),
|
||||
"-ldflags", ldflags,
|
||||
"-o", compiled_path,
|
||||
"-x"
|
||||
]
|
||||
if platform in ["android", "ios"]:
|
||||
args.extend(["-appid", APP_ID])
|
||||
args.append("./cmd/bedrocktool")
|
||||
|
||||
env2 = os.environ.copy()
|
||||
env2.update(env)
|
||||
subprocess.run(args, env=env2).check_returncode()
|
||||
|
||||
|
||||
def package(platform: str, arch: str, compiled_path: str, GUI: bool, ext: str):
|
||||
SUB1 = '-gui' if GUI else ''
|
||||
exe_out_path = f"./builds/{NAME}-{platform}-{arch}-{TAG}{SUB1}{ext}"
|
||||
|
||||
# create hash and copy
|
||||
with open(compiled_path, "rb") as f:
|
||||
exe_data = f.read()
|
||||
sha = binascii.b2a_base64(hashlib.sha256(exe_data).digest()).decode("utf8").split("\n")[0]
|
||||
shutil.copy(compiled_path, exe_out_path)
|
||||
|
||||
# create update
|
||||
updates_dir = f"./updates/{NAME}{SUB1}"
|
||||
os.makedirs(updates_dir, exist_ok=True)
|
||||
with open(f"{updates_dir}/{platform}-{arch}.json", "w") as f:
|
||||
f.write(json.dumps({
|
||||
"Version": TAG,
|
||||
"Sha256": sha,
|
||||
}, indent=2))
|
||||
|
||||
# write update data
|
||||
os.makedirs(f"{updates_dir}/{TAG}", exist_ok=True)
|
||||
with gzip.open(f"{updates_dir}/{TAG}/{platform}-{arch}.gz", "wb") as f:
|
||||
f.write(exe_data)
|
||||
|
||||
os.remove(compiled_path)
|
||||
|
||||
|
||||
def build_all(platform_filter: str, arch_filter: str):
|
||||
for (platform, archs, ext) in PLATFORMS:
|
||||
if platform_filter and platform_filter != platform:
|
||||
continue
|
||||
archs = [a for a in archs if arch_filter == "" or a == arch_filter]
|
||||
if len(archs) == 0:
|
||||
continue
|
||||
for GUI in [False, True]:
|
||||
if platform in ["android"] and not GUI:
|
||||
continue
|
||||
|
||||
print(f"Building {platform} gui: {GUI}")
|
||||
SUB1 = '-gui' if GUI else ''
|
||||
name = f"{NAME}{SUB1}"
|
||||
|
||||
tags = []
|
||||
if PACK_SUPPORT:
|
||||
tags.append("packs")
|
||||
if GUI:
|
||||
tags.append("gui")
|
||||
|
||||
env = {
|
||||
"GOVCS": "*:off"
|
||||
}
|
||||
|
||||
ldflags = LDFLAGS
|
||||
if tags.count("gui"):
|
||||
CmdName = "bedrocktool-gui"
|
||||
else:
|
||||
CmdName = "bedrocktool"
|
||||
ldflags += f" -X github.com/bedrock-tool/bedrocktool/utils.CmdName={CmdName}"
|
||||
|
||||
|
||||
for arch in archs:
|
||||
compiled_path = f"./tmp/{platform}-{arch}{SUB1}/{name}{ext}"
|
||||
os.makedirs(os.path.dirname(compiled_path), exist_ok=True)
|
||||
|
||||
if GUI and platform != "linux":
|
||||
build_gui(platform, arch, env, tags, ldflags, compiled_path)
|
||||
else:
|
||||
build_cli(platform, arch, env, tags, ldflags, compiled_path)
|
||||
|
||||
package(platform, arch, compiled_path, GUI, ext)
|
||||
|
||||
|
||||
def main():
|
||||
platform_filter = ""
|
||||
arch_filter = ""
|
||||
if len(sys.argv) > 1:
|
||||
platform_filter = sys.argv[1]
|
||||
|
||||
if len(sys.argv) > 2:
|
||||
arch_filter = sys.argv[2]
|
||||
|
||||
if platform_filter == "clean":
|
||||
clean()
|
||||
return
|
||||
|
||||
clean()
|
||||
make_dirs()
|
||||
build_all(platform_filter, arch_filter)
|
||||
|
||||
|
||||
main()
|
|
@ -8,36 +8,55 @@ import (
|
|||
"os"
|
||||
"os/signal"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/bedrock-tool/bedrocktool/locale"
|
||||
"github.com/bedrock-tool/bedrocktool/utils"
|
||||
"gopkg.in/square/go-jose.v2/json"
|
||||
|
||||
_ "github.com/bedrock-tool/bedrocktool/subcommands"
|
||||
_ "github.com/bedrock-tool/bedrocktool/subcommands/skins"
|
||||
_ "github.com/bedrock-tool/bedrocktool/subcommands/world"
|
||||
_ "github.com/bedrock-tool/bedrocktool/ui"
|
||||
|
||||
"github.com/google/subcommands"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type CLI struct {
|
||||
utils.BaseUI
|
||||
}
|
||||
|
||||
func (c *CLI) Init() bool {
|
||||
utils.SetCurrentUI(c)
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *CLI) Start(ctx context.Context, cancel context.CancelFunc) error {
|
||||
flag.Parse()
|
||||
utils.InitDNS()
|
||||
utils.InitExtraDebug(ctx)
|
||||
subcommands.Execute(ctx)
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
logrus.Errorf("Fatal Error occurred.")
|
||||
logrus.Errorf(locale.Loc("fatal_error", nil))
|
||||
println("")
|
||||
println("--COPY FROM HERE--")
|
||||
logrus.Infof("Version: %s", utils.Version)
|
||||
logrus.Infof("Cmdline: %s", os.Args)
|
||||
logrus.Errorf("Error: %s", err)
|
||||
fmt.Println("stacktrace from panic: \n" + string(debug.Stack()))
|
||||
println("stacktrace from panic: \n" + string(debug.Stack()))
|
||||
println("--END COPY HERE--")
|
||||
println("")
|
||||
println("if you want to report this error, please open an issue at")
|
||||
println("https://github.com/bedrock-tool/bedrocktool/issues")
|
||||
println("And attach the error info, describe what you did to get this error.")
|
||||
println("Thanks!\n")
|
||||
if utils.G_interactive {
|
||||
println(locale.Loc("report_issue", nil))
|
||||
if utils.Options.ExtraDebug {
|
||||
println(locale.Loc("used_extra_debug_report", nil))
|
||||
}
|
||||
if utils.Options.IsInteractive {
|
||||
input := bufio.NewScanner(os.Stdin)
|
||||
input.Scan()
|
||||
}
|
||||
|
@ -47,7 +66,7 @@ func main() {
|
|||
|
||||
logrus.SetLevel(logrus.DebugLevel)
|
||||
if utils.Version != "" {
|
||||
logrus.Infof("bedrocktool version: %s", utils.Version)
|
||||
logrus.Infof(locale.Loc("bedrocktool_version", locale.Strmap{"Version": utils.Version}))
|
||||
}
|
||||
|
||||
newVersion, err := utils.Updater.UpdateAvailable()
|
||||
|
@ -56,14 +75,18 @@ func main() {
|
|||
}
|
||||
|
||||
if newVersion != "" && utils.Version != "" {
|
||||
logrus.Infof("Update Available: %s", newVersion)
|
||||
logrus.Infof(locale.Loc("update_available", locale.Strmap{"Version": newVersion}))
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
flag.BoolVar(&utils.G_debug, "debug", false, "debug mode")
|
||||
flag.BoolVar(&utils.G_preload_packs, "preload", false, "preload resourcepacks for proxy")
|
||||
enable_dns := flag.Bool("dns", false, "enable dns server for consoles")
|
||||
flag.StringVar(&utils.RealmsEnv, "realms-env", "", "realms env")
|
||||
flag.BoolVar(&utils.Options.Debug, "debug", false, locale.Loc("debug_mode", nil))
|
||||
flag.BoolVar(&utils.Options.Preload, "preload", false, locale.Loc("preload_packs", nil))
|
||||
flag.BoolVar(&utils.Options.ExtraDebug, "extra-debug", false, locale.Loc("extra_debug", nil))
|
||||
flag.StringVar(&utils.Options.PathCustomUserData, "userdata", "", locale.Loc("custom_user_data", nil))
|
||||
flag.String("lang", "", "lang")
|
||||
flag.BoolVar(&utils.Options.EnableDNS, "dns", false, locale.Loc("enable_dns", nil))
|
||||
|
||||
subcommands.Register(subcommands.HelpCommand(), "")
|
||||
subcommands.ImportantFlag("debug")
|
||||
|
@ -71,33 +94,13 @@ func main() {
|
|||
subcommands.ImportantFlag("preload")
|
||||
subcommands.HelpCommand()
|
||||
|
||||
{ // interactive input
|
||||
if len(os.Args) < 2 {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
default:
|
||||
fmt.Println("Available commands:")
|
||||
for name, desc := range utils.ValidCMDs {
|
||||
fmt.Printf("\t%s\t%s\n", name, desc)
|
||||
}
|
||||
fmt.Printf("Use '%s <command>' to run a command\n", os.Args[0])
|
||||
var ui utils.UI
|
||||
|
||||
cmd, cancelled := utils.User_input(ctx, "Input Command: ")
|
||||
if cancelled {
|
||||
return
|
||||
}
|
||||
_cmd := strings.Split(cmd, " ")
|
||||
os.Args = append(os.Args, _cmd...)
|
||||
utils.G_interactive = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
flag.Parse()
|
||||
|
||||
if *enable_dns {
|
||||
utils.InitDNS()
|
||||
if len(os.Args) < 2 {
|
||||
ui = utils.MakeGui()
|
||||
utils.Options.IsInteractive = true
|
||||
} else {
|
||||
ui = &CLI{}
|
||||
}
|
||||
|
||||
// exit cleanup
|
||||
|
@ -109,12 +112,14 @@ func main() {
|
|||
cancel()
|
||||
}()
|
||||
|
||||
subcommands.Execute(ctx)
|
||||
|
||||
if utils.G_interactive {
|
||||
logrus.Info("Press Enter to exit.")
|
||||
input := bufio.NewScanner(os.Stdin)
|
||||
input.Scan()
|
||||
if !ui.Init() {
|
||||
logrus.Error("Failed to init UI!")
|
||||
return
|
||||
}
|
||||
err = ui.Start(ctx, cancel)
|
||||
cancel()
|
||||
if err != nil {
|
||||
logrus.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -124,31 +129,52 @@ type TransCMD struct {
|
|||
|
||||
func (*TransCMD) Name() string { return "trans" }
|
||||
func (*TransCMD) Synopsis() string { return "" }
|
||||
|
||||
func (c *TransCMD) SetFlags(f *flag.FlagSet) {
|
||||
f.BoolVar(&c.auth, "auth", false, "if it should login to xbox")
|
||||
f.BoolVar(&c.auth, "auth", false, locale.Loc("should_login_xbox", nil))
|
||||
}
|
||||
|
||||
func (c *TransCMD) Usage() string {
|
||||
return c.Name() + ": " + c.Synopsis() + "\n"
|
||||
}
|
||||
|
||||
func (c *TransCMD) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
|
||||
func (c *TransCMD) Execute(_ context.Context, ui utils.UI) error {
|
||||
const (
|
||||
BLACK_FG = "\033[30m"
|
||||
BOLD = "\033[1m"
|
||||
BLUE = "\033[46m"
|
||||
PINK = "\033[45m"
|
||||
WHITE = "\033[47m"
|
||||
RESET = "\033[0m"
|
||||
BlackFg = "\033[30m"
|
||||
Bold = "\033[1m"
|
||||
Blue = "\033[46m"
|
||||
Pink = "\033[45m"
|
||||
White = "\033[47m"
|
||||
Reset = "\033[0m"
|
||||
)
|
||||
if c.auth {
|
||||
utils.GetTokenSource()
|
||||
}
|
||||
fmt.Println(BLACK_FG + BOLD + BLUE + " Trans " + PINK + " Rights " + WHITE + " Are " + PINK + " Human " + BLUE + " Rights " + RESET)
|
||||
return 0
|
||||
fmt.Println(BlackFg + Bold + Blue + " Trans " + Pink + " Rights " + White + " Are " + Pink + " Human " + Blue + " Rights " + Reset)
|
||||
return nil
|
||||
}
|
||||
|
||||
type CreateCustomDataCMD struct {
|
||||
path string
|
||||
}
|
||||
|
||||
func (*CreateCustomDataCMD) Name() string { return "create-customdata" }
|
||||
func (*CreateCustomDataCMD) Synopsis() string { return "" }
|
||||
|
||||
func (c *CreateCustomDataCMD) SetFlags(f *flag.FlagSet) {
|
||||
f.StringVar(&c.path, "path", "customdata.json", "where to save")
|
||||
}
|
||||
|
||||
func (c *CreateCustomDataCMD) Execute(_ context.Context, ui utils.UI) error {
|
||||
var data utils.CustomClientData
|
||||
fio, err := os.Create(c.path)
|
||||
if err == nil {
|
||||
defer fio.Close()
|
||||
var bdata []byte
|
||||
bdata, err = json.MarshalIndent(&data, "", "\t")
|
||||
fio.Write(bdata)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
utils.RegisterCommand(&TransCMD{})
|
||||
utils.RegisterCommand(&CreateCustomDataCMD{})
|
||||
}
|
||||
|
|
60
go.mod
60
go.mod
|
@ -1,54 +1,66 @@
|
|||
module github.com/bedrock-tool/bedrocktool
|
||||
|
||||
go 1.19
|
||||
go 1.20
|
||||
|
||||
//replace github.com/sandertv/gophertunnel => ./gophertunnel
|
||||
replace github.com/sandertv/gophertunnel => github.com/olebeck/gophertunnel v1.26.1
|
||||
replace github.com/sandertv/gophertunnel => github.com/olebeck/gophertunnel v1.27.4-3
|
||||
|
||||
//replace github.com/df-mc/dragonfly => ./dragonfly
|
||||
replace github.com/df-mc/dragonfly => github.com/olebeck/dragonfly v0.8.10-1
|
||||
replace github.com/df-mc/dragonfly => github.com/olebeck/dragonfly v0.9.3-3
|
||||
|
||||
require (
|
||||
github.com/df-mc/dragonfly v0.8.5
|
||||
gioui.org v0.0.0-20221219171716-c455f0f342ef
|
||||
gioui.org/x v0.0.0-20230313161557-05b40af72ed0
|
||||
github.com/cloudfoundry-attic/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21
|
||||
github.com/df-mc/dragonfly v0.9.3
|
||||
github.com/df-mc/goleveldb v1.1.9
|
||||
github.com/fatih/color v1.13.0
|
||||
github.com/flytam/filenamify v1.1.1
|
||||
github.com/fatih/color v1.14.1
|
||||
github.com/flytam/filenamify v1.1.2
|
||||
github.com/go-gl/mathgl v1.0.0
|
||||
github.com/google/subcommands v1.2.0
|
||||
github.com/miekg/dns v1.1.50
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/jinzhu/copier v0.3.5
|
||||
github.com/miekg/dns v1.1.51
|
||||
github.com/nicksnyder/go-i18n/v2 v2.2.1
|
||||
github.com/sanbornm/go-selfupdate v0.0.0-20210106163404-c9b625feac49
|
||||
github.com/sandertv/gophertunnel v1.26.0
|
||||
github.com/sandertv/go-raknet v1.12.0
|
||||
github.com/sandertv/gophertunnel v1.27.4
|
||||
github.com/sirupsen/logrus v1.9.0
|
||||
golang.design/x/lockfree v0.0.1
|
||||
golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9
|
||||
golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1
|
||||
golang.org/x/crypto v0.7.0
|
||||
golang.org/x/exp v0.0.0-20230304125523-9ff063c70017
|
||||
golang.org/x/image v0.6.0
|
||||
golang.org/x/oauth2 v0.6.0
|
||||
golang.org/x/text v0.8.0
|
||||
gopkg.in/square/go-jose.v2 v2.6.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
||||
require (
|
||||
gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2 // indirect
|
||||
gioui.org/shader v1.0.6 // indirect
|
||||
git.wow.st/gmp/jni v0.0.0-20210610011705-34026c7e22d0 // indirect
|
||||
github.com/benoitkugler/textlayout v0.3.0 // indirect
|
||||
github.com/brentp/intintmap v0.0.0-20190211203843-30dc0ade9af9 // indirect
|
||||
github.com/changkun/lockfree v0.0.1 // indirect
|
||||
github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21 // indirect
|
||||
github.com/df-mc/atomic v1.10.0 // indirect
|
||||
github.com/go-text/typesetting v0.0.0-20221214153724-0399769901d5 // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/jinzhu/copier v0.3.5
|
||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
|
||||
github.com/klauspost/compress v1.15.11 // indirect
|
||||
github.com/klauspost/compress v1.15.15 // indirect
|
||||
github.com/kr/binarydist v0.1.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.16 // indirect
|
||||
github.com/mattn/go-isatty v0.0.17 // indirect
|
||||
github.com/muhammadmuzzammil1998/jsonc v1.0.0 // indirect
|
||||
github.com/sandertv/go-raknet v1.12.0 // indirect
|
||||
github.com/sirupsen/logrus v1.9.0
|
||||
go.uber.org/atomic v1.10.0 // indirect
|
||||
golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be // indirect
|
||||
golang.org/x/image v0.0.0-20220902085622-e7cb96979f69
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
|
||||
golang.org/x/net v0.0.0-20220930213112-107f3e3c3b0b // indirect
|
||||
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
golang.org/x/tools v0.1.12 // indirect
|
||||
golang.org/x/exp/shiny v0.0.0-20220827204233-334a2380cb91 // indirect
|
||||
golang.org/x/mod v0.8.0 // indirect
|
||||
golang.org/x/net v0.8.0 // indirect
|
||||
golang.org/x/sys v0.6.0 // indirect
|
||||
golang.org/x/tools v0.6.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/protobuf v1.28.1 // indirect
|
||||
gopkg.in/inconshreveable/go-update.v0 v0.0.0-20150814200126-d8b0b1d421aa // indirect
|
||||
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
|
||||
)
|
||||
|
|
150
go.sum
150
go.sum
|
@ -1,7 +1,30 @@
|
|||
eliasnaur.com/font v0.0.0-20220124212145-832bb8fc08c3 h1:djFprmHZgrSepsHAIRMp5UJn3PzsoTg9drI+BDmif5Q=
|
||||
gioui.org v0.0.0-20221219171716-c455f0f342ef h1:0nxeswOamzSRIjFjRmASp+sWCAFzVt7UCC3HlRHIB6k=
|
||||
gioui.org v0.0.0-20221219171716-c455f0f342ef/go.mod h1:3lLo7xMHYnnHTrgKNNctBjEKKH3wQCO2Sn7ti5Jy8mU=
|
||||
gioui.org/cpu v0.0.0-20210808092351-bfe733dd3334/go.mod h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ=
|
||||
gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2 h1:AGDDxsJE1RpcXTAxPG2B4jrwVUJGFDjINIPi1jtO6pc=
|
||||
gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2/go.mod h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ=
|
||||
gioui.org/shader v1.0.6 h1:cvZmU+eODFR2545X+/8XucgZdTtEjR3QWW6W65b0q5Y=
|
||||
gioui.org/shader v1.0.6/go.mod h1:mWdiME581d/kV7/iEhLmUgUK5iZ09XR5XpduXzbePVM=
|
||||
gioui.org/x v0.0.0-20230313161557-05b40af72ed0 h1:jjdcmnjM8R1yhiBBbPinm5TLNs6uRei25fjnvgYBTjU=
|
||||
gioui.org/x v0.0.0-20230313161557-05b40af72ed0/go.mod h1:kmHRtak7XgGZYYuqFgDBJ42PbQjPrV/xOpw9FLx3eVY=
|
||||
git.wow.st/gmp/jni v0.0.0-20210610011705-34026c7e22d0 h1:bGG/g4ypjrCJoSvFrP5hafr9PPB5aw8SjcOWWila7ZI=
|
||||
git.wow.st/gmp/jni v0.0.0-20210610011705-34026c7e22d0/go.mod h1:+axXBRUTIDlCeE73IKeD/os7LoEnTKdkp8/gQOFjqyo=
|
||||
github.com/BurntSushi/toml v1.0.0 h1:dtDWrepsVPfW9H/4y7dDgFc2MBUSeJhlaDtK13CxFlU=
|
||||
github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||
github.com/benoitkugler/pstokenizer v1.0.0/go.mod h1:l1G2Voirz0q/jj0TQfabNxVsa8HZXh/VMxFSRALWTiE=
|
||||
github.com/benoitkugler/textlayout v0.3.0 h1:2ehWXEkgb6RUokTjXh1LzdGwG4dRP6X3dqhYYDYhUVk=
|
||||
github.com/benoitkugler/textlayout v0.3.0/go.mod h1:o+1hFV+JSHBC9qNLIuwVoLedERU7sBPgEFcuSgfvi/w=
|
||||
github.com/benoitkugler/textlayout-testdata v0.1.1 h1:AvFxBxpfrQd8v55qH59mZOJOQjtD6K2SFe9/HvnIbJk=
|
||||
github.com/benoitkugler/textlayout-testdata v0.1.1/go.mod h1:i/qZl09BbUOtd7Bu/W1CAubRwTWrEXWq6JwMkw8wYxo=
|
||||
github.com/brentp/intintmap v0.0.0-20190211203843-30dc0ade9af9 h1:/G0ghZwrhou0Wq21qc1vXXMm/t/aKWkALWwITptKbE0=
|
||||
github.com/brentp/intintmap v0.0.0-20190211203843-30dc0ade9af9/go.mod h1:TOk10ahXejq9wkEaym3KPRNeuR/h5Jx+s8QRWIa2oTM=
|
||||
github.com/changkun/lockfree v0.0.1 h1:5WefVJLglY4IHRqOQmh6Ao6wkJYaJkarshKU8VUtId4=
|
||||
github.com/changkun/lockfree v0.0.1/go.mod h1:3bKiaXn/iNzIPlSvSOMSVbRQUQtAp8qUAyBUtzU11s4=
|
||||
github.com/cloudfoundry-attic/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21 h1:Yg2hDs4b13Evkpj42FU2idX2cVXVFqQSheXYKM86Qsk=
|
||||
github.com/cloudfoundry-attic/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21/go.mod h1:MgJyK38wkzZbiZSKeIeFankxxSA8gayko/nr5x5bgBA=
|
||||
github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21 h1:tuijfIjZyjZaHq9xDUh0tNitwXshJpbLkqMOJv4H3do=
|
||||
github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21/go.mod h1:po7NpZ/QiTKzBKyrsEAxwnTamCoh8uDk/egRpQ7siIc=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
|
@ -9,13 +32,15 @@ github.com/df-mc/atomic v1.10.0 h1:0ZuxBKwR/hxcFGorKiHIp+hY7hgY+XBTzhCYD2NqSEg=
|
|||
github.com/df-mc/atomic v1.10.0/go.mod h1:Gw9rf+rPIbydMjA329Jn4yjd/O2c/qusw3iNp4tFGSc=
|
||||
github.com/df-mc/goleveldb v1.1.9 h1:ihdosZyy5jkQKrxucTQmN90jq/2lUwQnJZjIYIC/9YU=
|
||||
github.com/df-mc/goleveldb v1.1.9/go.mod h1:+NHCup03Sci5q84APIA21z3iPZCuk6m6ABtg4nANCSk=
|
||||
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
|
||||
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
||||
github.com/flytam/filenamify v1.1.1 h1:508gP8QR6vtbv46S3oz2ob9l7JGFdDFfqqMeh/TwzTk=
|
||||
github.com/flytam/filenamify v1.1.1/go.mod h1:Dzf9kVycwcsBlr2ATg6uxjqiFgKGH+5SKFuhdeP5zu8=
|
||||
github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w=
|
||||
github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg=
|
||||
github.com/flytam/filenamify v1.1.2 h1:dGlfWU4zrhDlsmvob4IFcfgjG5vIjfo4UwLyec6Wx94=
|
||||
github.com/flytam/filenamify v1.1.2/go.mod h1:Dzf9kVycwcsBlr2ATg6uxjqiFgKGH+5SKFuhdeP5zu8=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/go-gl/mathgl v1.0.0 h1:t9DznWJlXxxjeeKLIdovCOVJQk/GzDEL7h/h+Ro2B68=
|
||||
github.com/go-gl/mathgl v1.0.0/go.mod h1:yhpkQzEiH9yPyxDUGzkmgScbaBVlhC06qodikEM0ZwQ=
|
||||
github.com/go-text/typesetting v0.0.0-20221214153724-0399769901d5 h1:iOA0HmtpANn48hX2nlDNMu0VVaNza35HJG0WeetBVzQ=
|
||||
github.com/go-text/typesetting v0.0.0-20221214153724-0399769901d5/go.mod h1:/cmOXaoTiO+lbCwkTZBgCvevJpbFsZ5reXIpEJVh5MI=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
|
@ -37,35 +62,29 @@ github.com/jinzhu/copier v0.3.5/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S
|
|||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 h1:iQTw/8FWTuc7uiaSepXwyf3o52HaUYcV+Tu66S3F5GA=
|
||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
|
||||
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||
github.com/klauspost/compress v1.15.11 h1:Lcadnb3RKGin4FYM/orgq0qde+nc15E5Cbqg4B9Sx9c=
|
||||
github.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM=
|
||||
github.com/klauspost/compress v1.15.12 h1:YClS/PImqYbn+UILDnqxQCZ3RehC9N318SU3kElDUEM=
|
||||
github.com/klauspost/compress v1.15.12/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM=
|
||||
github.com/klauspost/compress v1.15.15 h1:EF27CXIuDsYJ6mmvtBRlEuB2UVOqHG1tAXgZ7yIO+lw=
|
||||
github.com/klauspost/compress v1.15.15/go.mod h1:ZcK2JAFqKOpnBlxcLsJzYfrS9X1akm9fHZNnD9+Vo/4=
|
||||
github.com/kr/binarydist v0.1.0 h1:6kAoLA9FMMnNGSehX0s1PdjbEaACznAv/W219j2uvyo=
|
||||
github.com/kr/binarydist v0.1.0/go.mod h1:DY7S//GCoz1BCd0B0EVrinCKAZN3pXe+MDaIZbXQVgM=
|
||||
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA=
|
||||
github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
|
||||
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
|
||||
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/miekg/dns v1.1.51 h1:0+Xg7vObnhrz/4ZCZcZh7zPXlmU0aveS2HDBd0m0qSo=
|
||||
github.com/miekg/dns v1.1.51/go.mod h1:2Z9d3CP1LQWihRZUf29mQ19yDThaI4DAYzte2CaQW5c=
|
||||
github.com/muhammadmuzzammil1998/jsonc v1.0.0 h1:8o5gBQn4ZA3NBA9DlTujCj2a4w0tqWrPVjDwhzkgTIs=
|
||||
github.com/muhammadmuzzammil1998/jsonc v1.0.0/go.mod h1:saF2fIVw4banK0H4+/EuqfFLpRnoy5S+ECwTOCcRcSU=
|
||||
github.com/olebeck/dragonfly v0.8.5-1 h1:+z1WO0W4jCc1jcPMGDz85AxLWz59haEakD0yAVEXeWQ=
|
||||
github.com/olebeck/dragonfly v0.8.5-1/go.mod h1:CHQ21j4LjSwHgUeMk06tcxEW6U+SI7avLCUeSvp9AXo=
|
||||
github.com/olebeck/dragonfly v0.8.10-1 h1:oMeCcyoKfb9xEDxxbKM6YyUPlK/R8wix/zzZ4UzkYYQ=
|
||||
github.com/olebeck/dragonfly v0.8.10-1/go.mod h1:ZjzPME6I1nc73voUgr2s5lpkoTxnWuR54V6c1KbULX0=
|
||||
github.com/olebeck/gophertunnel v1.24.11-1 h1:DKEqjK5nAcjlwejHAD/SHeolAM6y9EkyjJcMuoK7VVY=
|
||||
github.com/olebeck/gophertunnel v1.24.11-1/go.mod h1:dYFetA6r62huhc1EgR9p8VFAFtKOuGgVE/iXf5CzZ4o=
|
||||
github.com/olebeck/gophertunnel v1.24.13-1 h1:3Sj22hVFAGCnPMJAewoSeuC9N8ikzcWo0DvJNBnXvs0=
|
||||
github.com/olebeck/gophertunnel v1.24.13-1/go.mod h1:dYFetA6r62huhc1EgR9p8VFAFtKOuGgVE/iXf5CzZ4o=
|
||||
github.com/olebeck/gophertunnel v1.25.0-1 h1:9vh6MIUo7/3EJI5oULr5F9KutAoa9qnpgWa566fImaI=
|
||||
github.com/olebeck/gophertunnel v1.25.0-1/go.mod h1:dYFetA6r62huhc1EgR9p8VFAFtKOuGgVE/iXf5CzZ4o=
|
||||
github.com/olebeck/gophertunnel v1.26.1 h1:mkIthqpU5vSksh/mxUlhqFN/nsK9CcP9hAn1LjjqKnQ=
|
||||
github.com/olebeck/gophertunnel v1.26.1/go.mod h1:dYFetA6r62huhc1EgR9p8VFAFtKOuGgVE/iXf5CzZ4o=
|
||||
github.com/nicksnyder/go-i18n/v2 v2.2.1 h1:aOzRCdwsJuoExfZhoiXHy4bjruwCMdt5otbYojM/PaA=
|
||||
github.com/nicksnyder/go-i18n/v2 v2.2.1/go.mod h1:fF2++lPHlo+/kPaj3nB0uxtPwzlPm+BlgwGX7MkeGj0=
|
||||
github.com/olebeck/dragonfly v0.9.3-2 h1:FBO5raxeQvA286L+mUhnAsUSWxVfKmVFoofw0KdJlXA=
|
||||
github.com/olebeck/dragonfly v0.9.3-2/go.mod h1:ODAzVcmM7KvKgPB89hoYndQiVVgTm9FlDXUko1H3YVs=
|
||||
github.com/olebeck/dragonfly v0.9.3-3 h1:sv4TJjdgFoCBRMd5mS0g5NyPeMsVfUifhxIlJPTj6xg=
|
||||
github.com/olebeck/dragonfly v0.9.3-3/go.mod h1:ODAzVcmM7KvKgPB89hoYndQiVVgTm9FlDXUko1H3YVs=
|
||||
github.com/olebeck/gophertunnel v1.27.4-1 h1:rtB5wksJBKk3sy9lBFZ1aH2giCathINj4zQkl1rPTMg=
|
||||
github.com/olebeck/gophertunnel v1.27.4-1/go.mod h1:ekREo7U9TPHh86kbuPMaWA93NMyWsfVvP/iNT3XhAb8=
|
||||
github.com/olebeck/gophertunnel v1.27.4-3 h1:RktAdTNTvCFn6PQou0H3RyqrTo3/xH0bqODrHb/oXAs=
|
||||
github.com/olebeck/gophertunnel v1.27.4-3/go.mod h1:ekREo7U9TPHh86kbuPMaWA93NMyWsfVvP/iNT3XhAb8=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
|
||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
|
@ -82,101 +101,86 @@ github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs
|
|||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
|
||||
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||
golang.design/x/lockfree v0.0.1 h1:IHFNwZgM5bnZYWkEbzn5lWHMYr8WsRBdCJ/RBVY0xMM=
|
||||
golang.design/x/lockfree v0.0.1/go.mod h1:iaZUx6UgZaOdePjzI6wFd+seYMl1i0rsG8+xKvA8c4I=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be h1:fmw3UbQh+nxngCAHrDCCztao/kbYFnWjoqop8dHx05A=
|
||||
golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.3.0 h1:a06MkbcxBrEFc0w0QIZWXrH/9cCX6KJyWbBOIwAn+7A=
|
||||
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
||||
golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 h1:RjggHMcaTVp0LOVZcW0bo8alwHrOaCrGUDgfWUHhnN4=
|
||||
golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
|
||||
golang.org/x/exp v0.0.0-20221126150942-6ab00d035af9 h1:yZNXmy+j/JpX19vZkVktWqAo7Gny4PBWYYK3zskGpx4=
|
||||
golang.org/x/exp v0.0.0-20221126150942-6ab00d035af9/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
|
||||
golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
|
||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||
golang.org/x/exp v0.0.0-20230304125523-9ff063c70017 h1:3Ea9SZLCB0aRIhSEjM+iaGIlzzeDJdpi579El/YIhEE=
|
||||
golang.org/x/exp v0.0.0-20230304125523-9ff063c70017/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
|
||||
golang.org/x/exp/shiny v0.0.0-20220827204233-334a2380cb91 h1:ryT6Nf0R83ZgD8WnFFdfI8wCeyqgdXWN4+CkFVNPAT0=
|
||||
golang.org/x/exp/shiny v0.0.0-20220827204233-334a2380cb91/go.mod h1:VjAR7z0ngyATZTELrBSkxOOHhhlnVUxDye4mcjx5h/8=
|
||||
golang.org/x/image v0.0.0-20190321063152-3fc05d484e9f/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20220902085622-e7cb96979f69 h1:Lj6HJGCSn5AjxRAH2+r35Mir4icalbqku+CLUtjnvXY=
|
||||
golang.org/x/image v0.0.0-20220902085622-e7cb96979f69/go.mod h1:doUCurBvlfPMKfmIpRIywoHmhN3VyhnoFDbvIEWF4hY=
|
||||
golang.org/x/image v0.1.0 h1:r8Oj8ZA2Xy12/b5KZYj3tuv7NG/fBz3TwQVvpJ9l8Rk=
|
||||
golang.org/x/image v0.1.0/go.mod h1:iyPr49SD/G/TBxYVB/9RRtGUT5eNbo2u4NamWeQcD5c=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
|
||||
golang.org/x/image v0.0.0-20210504121937-7319ad40d33e/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
|
||||
golang.org/x/image v0.6.0 h1:bR8b5okrPI3g/gyZakLZHeWxAR8Dn5CyxXv1hLH5g/4=
|
||||
golang.org/x/image v0.6.0/go.mod h1:MXLdDR43H7cDJq5GEGXEVeeNhPgi+YYEQ2pC1byI1x0=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA=
|
||||
golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.0.0-20220930213112-107f3e3c3b0b h1:uKO3Js8lXGjpjdc4J3rqs0/Ex5yDKUGfk43tTYWVLas=
|
||||
golang.org/x/net v0.0.0-20220930213112-107f3e3c3b0b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
golang.org/x/net v0.2.0 h1:sZfSu1wtKLGlWI4ZZayP0ck9Y73K1ynO6gqzTdBVdPU=
|
||||
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
||||
golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1 h1:lxqLZaMad/dJHMFZH0NiNpiEZI/nhgWhe4wgzpE+MuA=
|
||||
golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=
|
||||
golang.org/x/oauth2 v0.2.0 h1:GtQkldQ9m7yvzCL1V+LrYow3Khe0eJH0w7RbX/VbaIU=
|
||||
golang.org/x/oauth2 v0.2.0/go.mod h1:Cwn6afJ8jrQwYMxQDTpISoXmXW9I6qF6vDeuuoX3Ibs=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
|
||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||
golang.org/x/oauth2 v0.6.0 h1:Lh8GPgSKBfWSwFvtuWOfeI3aAAnbXTSutYxJiOJFgIw=
|
||||
golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec h1:BkDtF2Ih9xZ7le9ndzTA7KJow28VbQW3odyk/8drmuI=
|
||||
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
|
||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.3.0 h1:SrNbZl6ECOS1qFzgTdQfWXZM9XBkiA6tkFrH9YSTPHM=
|
||||
golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k=
|
||||
golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
|
||||
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
|
||||
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
|
@ -188,5 +192,7 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkep
|
|||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
|
|
@ -0,0 +1,200 @@
|
|||
bedrocktool_version:
|
||||
other: "Bedrocktool Version {{.Version}}"
|
||||
available_commands:
|
||||
other: "Verfügbare Befehle:"
|
||||
use_to_run_command:
|
||||
other: "Verwenden Sie 'bedrocktool <Befehl>', um einen Befehl auszuführen"
|
||||
input_command:
|
||||
other: "Eingabeaufforderung: "
|
||||
fatal_error:
|
||||
other: "Schwerer Fehler aufgetreten."
|
||||
report_issue:
|
||||
other: |
|
||||
Wenn Sie diesen Fehler melden möchten, öffnen Sie bitte ein Problem unter
|
||||
https://github.com/bedrock-tool/bedrocktool/issues
|
||||
Und fügen Sie die Fehlerinformationen hinzu, beschreiben Sie, was Sie getan haben, um diesen Fehler zu erhalten.
|
||||
Vielen Dank!
|
||||
used_extra_debug_report:
|
||||
other: |
|
||||
HINWEIS: Sie haben extra-debug verwendet, welches eine packets.log und eine packets.log.gpg Datei erstellt.
|
||||
Bitte laden Sie nur die packets.log.gpg Datei hoch, wenn Sie ein Problem melden möchten.
|
||||
packets.log und packets.log.gpg können Account-Informationen enthalten (nie Login-Informationen), daher ist es sicherer, die verschlüsselte Datei hochzuladen.
|
||||
Diese Datei kann von dem Entwickler gelesen werden, aber nicht von Personen, die die öffentlichen Problemseiten ansehen.
|
||||
enter_to_exit:
|
||||
other: "Drücken Sie die Eingabetaste, um das Programm zu beenden."
|
||||
should_login_xbox:
|
||||
other: "ob es sich bei Xbox anmelden soll"
|
||||
enable_dns:
|
||||
other: "DNS-Server für Konsolen aktivieren"
|
||||
preload_packs:
|
||||
other: "Vorladen von Ressourcenpacks für den Proxy"
|
||||
debug_mode:
|
||||
other: "Debug-Modus"
|
||||
refreshed_token:
|
||||
other: Token wurde aktualisiert
|
||||
done:
|
||||
other: Fertig
|
||||
|
||||
starting_dns:
|
||||
other: "Starten des DNS auf {{.Ip}}:53"
|
||||
failed_to_start_dns:
|
||||
other: "Fehler beim Starten des DNS-Servers: {{.Err}}"
|
||||
suggest_bedrockconnect:
|
||||
other: "Sie müssen möglicherweise Bedrockconnect verwenden"
|
||||
|
||||
invalid_server:
|
||||
other: "Ungültige Serveradresse"
|
||||
enter_server:
|
||||
other: "Serveradresse eingeben: "
|
||||
disconnect:
|
||||
other: "Verbindung trennen {{.Pk}}"
|
||||
preloading_packs:
|
||||
other: "Ressourcenpacks werden vorgeladen"
|
||||
failed_to_connect:
|
||||
other: "Verbindung zu {{.Address}} fehlgeschlagen: {{.Err}}"
|
||||
listening_on:
|
||||
other: "Hört auf {{.Address}}"
|
||||
connecting:
|
||||
other: "Verbindung wird hergestellt mit {{.Address}}"
|
||||
connected:
|
||||
other: "Verbunden mit dem Server."
|
||||
connection_cancelled:
|
||||
other: "Verbindung abgebrochen"
|
||||
failed_start_game:
|
||||
other: "Das Spiel konnte nicht gestartet werden: {{.Err}}"
|
||||
help_connect:
|
||||
other: "Starten Sie Minecraft und verbinden Sie sich mit der lokalen IP-Adresse dieses Computers, um fortzufahren"
|
||||
failed_to_spawn:
|
||||
other: "Spawn fehlgeschlagen"
|
||||
not_supported_anymore:
|
||||
other: "wird nicht mehr unterstützt"
|
||||
remote_address:
|
||||
other: "entfernte Serveradresse"
|
||||
ctrl_c_to_exit:
|
||||
other: "Drücken Sie Strg+C, um das Programm zu beenden"
|
||||
server_address_help:
|
||||
other: |
|
||||
akzeptierte Serveradressformate:
|
||||
123.234.123.234
|
||||
123.234.123.234:19132
|
||||
realm:<Realmname>
|
||||
realm:<Realmname>:<Id>
|
||||
|
||||
realm_list_line:
|
||||
other: "Name: {{.Name}}\tid: {{.Id}}"
|
||||
|
||||
failed_to_open_output:
|
||||
other: "Fehler beim Öffnen der Ausgabe"
|
||||
adding_world:
|
||||
other: "{{.World}} wird hinzugefügt"
|
||||
not_found:
|
||||
other: "{{.World}} nicht gefunden"
|
||||
need_to_specify_multiple_worlds:
|
||||
other: "Sie müssen mehr als 1 Welt angeben, um diese zu vereinen"
|
||||
|
||||
update_available:
|
||||
other: "Update verfügbar: {{.Version}} Verwenden Sie den Befehl 'update', um es zu installieren."
|
||||
no_update:
|
||||
other: "Kein Update verfügbar."
|
||||
updating:
|
||||
other: "Aktualisiere auf {{.Version}}"
|
||||
updated:
|
||||
other: "Aktualisiert!"
|
||||
|
||||
worldname_set:
|
||||
other: "worldName ist jetzt {{.Name}}"
|
||||
void_generator_true:
|
||||
other: "Verwende Void Generator"
|
||||
void_generator_false:
|
||||
other: "Verwende keinen Void Generator"
|
||||
subchunk_before_chunk:
|
||||
other: "Der Server hat den Chunk vor dem Subchunk nicht gesendet!"
|
||||
zoom_level:
|
||||
other: "Zoom: {{.Level}}"
|
||||
not_saving_empty:
|
||||
other: "Speichern wird übersprungen, da die Welt keine Chunks enthält."
|
||||
saving_world:
|
||||
one: "Speichere Welt {{.Name}} mit {{.Count}} Chunk"
|
||||
other: "Speichere Welt {{.Name}} mit {{.Count}} Chunks"
|
||||
unknown_gamerule:
|
||||
other: "unbekannte Gamerule: {{.Name}}"
|
||||
adding_pack:
|
||||
other: "Füge Ressourcenpaket {{.Name}} hinzu"
|
||||
using_customblocks:
|
||||
other: "Verwende Benutzerdefinierte Blöcke"
|
||||
guessing_version:
|
||||
other: "konnte die Spieleversion nicht bestimmen, gehe davon aus, dass es > 1.18 ist"
|
||||
use_setname:
|
||||
other: "verwende /setname <worldname>\num den Weltnamen zu setzen"
|
||||
using_under_118:
|
||||
other: "verwende legacy (< 1.18)"
|
||||
setname_desc:
|
||||
other: "setze benutzerdefinierten Namen für diese Welt"
|
||||
void_desc:
|
||||
other: "schalte ein/aus ob der Void Generator verwendet werden soll"
|
||||
popup_chunk_count:
|
||||
one: "{{.Count}} Chunk geladen\nName: {{.Name}}"
|
||||
other: "{{.Count}} Chunks geladen\nName: {{.Name}}"
|
||||
warn_window_closed_not_open:
|
||||
other: "Schloss Fenster, das nicht geöffnet war"
|
||||
saved_block_inv:
|
||||
other: "Blockinventar gespeichert"
|
||||
save_packs_with_world:
|
||||
other: "speichere Resourcepacks mit der Welt"
|
||||
enable_void:
|
||||
other: "speichere mit Void Generator"
|
||||
save_image:
|
||||
other: "speichert am Ende ein PNG der Karte"
|
||||
test_block_inv:
|
||||
other: "aktiviere experimentelles Speichern des Blockinventars"
|
||||
saved:
|
||||
other: "Gespeichert: {{.Name}}"
|
||||
empty_chunk:
|
||||
other: "Leerer Chunk."
|
||||
|
||||
only_with_geometry:
|
||||
other: "speichere nur Skins mit Geometrie"
|
||||
name_prefix:
|
||||
other: "speichere nur Spieler, die mit diesem beginnen"
|
||||
failed_write:
|
||||
other: "Fehler beim Schreiben von {{.Part}} {{.Path}}: {{.Err}}"
|
||||
packet_filter:
|
||||
other: "zu ignorierende Pakete"
|
||||
|
||||
save_encrypted:
|
||||
other: "speichere verschlüsselte Ressourcenpakete"
|
||||
only_keys:
|
||||
other: "speichere nur Schlüssel, entschlüssel das Ressourcenpaket nicht"
|
||||
decrypting_packs:
|
||||
other: "Entschlüsselung der Ressourcenpakete"
|
||||
no_resourcepacks:
|
||||
other: "Kein Ressourcenpaket gesendet"
|
||||
writing_keys:
|
||||
other: "Schreibe Schlüssel nach {{.Path}}"
|
||||
warn_key_exists:
|
||||
other: "Schlüssel {{.Id}} existiert bereits"
|
||||
compare_key:
|
||||
other: "uuid: {{.Id}}, Schlüssel in der DB: {{.Prev}} != neuer Schlüssel {{.Now}}"
|
||||
decrypting:
|
||||
other: "Entschlüsselung..."
|
||||
|
||||
list_realms_synopsis:
|
||||
other: "gibt alle Realm aus, auf die Sie Zugriff haben"
|
||||
capture_synopsis:
|
||||
other: "protokolliert Pakete in eine pcap-Datei"
|
||||
chat_log_synopsis:
|
||||
other: "protokolliert den Chat in eine Datei"
|
||||
debug_proxy_synopsis:
|
||||
other: "detaillierte Debug-Pakete"
|
||||
merge_synopsis:
|
||||
other: "führt 2 oder mehr Welten zusammen"
|
||||
update_synopsis:
|
||||
other: "aktualisiert sich selbst auf die neueste Version"
|
||||
skins_proxy_synopsis:
|
||||
other: "lädt Skins von Spielern auf einem Server über einen Proxy herunter"
|
||||
skins_synopsis:
|
||||
other: "lädt Skins von Spielern auf einem Server herunter"
|
||||
world_synopsis:
|
||||
other: "lädt eine Welt von einem Server herunter"
|
||||
pack_synopsis:
|
||||
other: "lädt Ressourcenpakete von einem Server herunter"
|
|
@ -0,0 +1,201 @@
|
|||
bedrocktool_version:
|
||||
other: "Bedrocktool Version {{.Version}}"
|
||||
available_commands:
|
||||
other: "Available Commands:"
|
||||
use_to_run_command:
|
||||
other: "Use 'bedrocktool <command>' to run a command"
|
||||
input_command:
|
||||
other: "Input Command: "
|
||||
fatal_error:
|
||||
other: "Fatal Error occurred."
|
||||
report_issue:
|
||||
other: |
|
||||
if you want to report this error, please open an issue at
|
||||
https://github.com/bedrock-tool/bedrocktool/issues
|
||||
And attach the error info, describe what you did to get this error.
|
||||
Thanks!
|
||||
used_extra_debug_report:
|
||||
other: |
|
||||
NOTE: you have used extra-debug which creates a packets.log and a packets.log.gpg file
|
||||
please if you want to submit an issue only upload the packets.log.gpg file.
|
||||
packets.log and packets.log.gpg may include account info (never login info) so it is safer to upload the encrypted file.
|
||||
that file can be read by the developer and not by people looking at the public issues page.
|
||||
enter_to_exit:
|
||||
other: "Press Enter to exit."
|
||||
should_login_xbox:
|
||||
other: "if it should login to xbox"
|
||||
enable_dns:
|
||||
other: "enable dns server for consoles"
|
||||
preload_packs:
|
||||
other: "preload resourcepacks for proxy"
|
||||
debug_mode:
|
||||
other: "debug mode"
|
||||
refreshed_token:
|
||||
other: "refreshed token"
|
||||
done:
|
||||
other: "done"
|
||||
|
||||
starting_dns:
|
||||
other: "Starting dns at {{.Ip}}:53"
|
||||
failed_to_start_dns:
|
||||
other: "Failed to start dns server: {{.Err}}"
|
||||
suggest_bedrockconnect:
|
||||
other: "you may have to use Bedrockconnect"
|
||||
|
||||
invalid_server:
|
||||
other: "Invalid Server Address"
|
||||
enter_server:
|
||||
other: "Enter Server: "
|
||||
disconnect:
|
||||
other: "Disconnect {{.Pk}}"
|
||||
preloading_packs:
|
||||
other: "Preloading Resourcepacks"
|
||||
failed_to_connect:
|
||||
other: "failed to connect to {{.Address}}: {{.Err}}"
|
||||
listening_on:
|
||||
other: "Listening on {{.Address}}"
|
||||
connecting:
|
||||
other: "Connecting to {{.Address}}"
|
||||
connected:
|
||||
other: "Connected to server."
|
||||
connection_cancelled:
|
||||
other: "connection cancelled"
|
||||
failed_start_game:
|
||||
other: "failed to start game: {{.Err}}"
|
||||
help_connect:
|
||||
other: "Open Minecraft and connect to this computers local ip address to continue"
|
||||
failed_to_spawn:
|
||||
other: "Failed to Spawn"
|
||||
not_supported_anymore:
|
||||
other: "not supported anymore"
|
||||
remote_address:
|
||||
other: "remote server address"
|
||||
ctrl_c_to_exit:
|
||||
other: "Press ctrl+c to exit"
|
||||
server_address_help:
|
||||
other: |
|
||||
accepted server address formats:
|
||||
123.234.123.234
|
||||
123.234.123.234:19132
|
||||
realm:<Realmname>
|
||||
realm:<Realmname>:<Id>
|
||||
|
||||
realm_list_line:
|
||||
other: "Name: {{.Name}}\tid: {{.Id}}"
|
||||
|
||||
failed_to_open_output:
|
||||
other: "Failed to open output"
|
||||
adding_world:
|
||||
other: "Adding {{.World}}"
|
||||
not_found:
|
||||
other: "{{.World}} Not Found"
|
||||
need_to_specify_multiple_worlds:
|
||||
other: "you need to specify more than 1 world to merge"
|
||||
|
||||
update_available:
|
||||
other: "Update available: {{.Version}} use the update command to install it."
|
||||
no_update:
|
||||
other: "No Updates available."
|
||||
updating:
|
||||
other: "Updating to {{.Version}}"
|
||||
updated:
|
||||
other: "Updated!"
|
||||
|
||||
worldname_set:
|
||||
other: "worldName is now {{.Name}}"
|
||||
void_generator_true:
|
||||
other: "Using Void Generator"
|
||||
void_generator_false:
|
||||
other: "Not using Void Generator"
|
||||
subchunk_before_chunk:
|
||||
other: "The server didnt send the chunk before the subchunk!"
|
||||
zoom_level:
|
||||
other: "Zoom: {{.Level}}"
|
||||
not_saving_empty:
|
||||
other: "Skipping save because the world didnt contain any chunks."
|
||||
saving_world:
|
||||
one: "Saving world {{.Name}} with {{.Count}} Chunk"
|
||||
other: "Saving world {{.Name}} with {{.Count}} Chunks"
|
||||
unknown_gamerule:
|
||||
other: "unknown gamerule: {{.Name}}"
|
||||
adding_pack:
|
||||
other: "Adding Resourcepack {{.Name}}"
|
||||
using_customblocks:
|
||||
other: "Using Custom Blocks"
|
||||
guessing_version:
|
||||
other: "couldnt determine game version, assuming > 1.18"
|
||||
use_setname:
|
||||
other: "use /setname <worldname>\nto set the world name"
|
||||
using_under_118:
|
||||
other: "using legacy (< 1.18)"
|
||||
setname_desc:
|
||||
other: "set user defined name for this world"
|
||||
void_desc:
|
||||
other: "toggle if void generator should be used"
|
||||
popup_chunk_count:
|
||||
one: "{{.Count}} Chunk loaded\nName: {{.Name}}"
|
||||
other: "{{.Count}} Chunks loaded\nName: {{.Name}}"
|
||||
warn_window_closed_not_open:
|
||||
other: "Closed window that wasnt open"
|
||||
saved_block_inv:
|
||||
other: "Saved Block Inventory"
|
||||
save_packs_with_world:
|
||||
other: "save resourcepacks to the worlds"
|
||||
enable_void:
|
||||
other: "save with void generator"
|
||||
save_image:
|
||||
other: "saves an png of the map at the end"
|
||||
test_block_inv:
|
||||
other: "enable experimental block inventory saving"
|
||||
saved:
|
||||
other: "Saved: {{.Name}}"
|
||||
empty_chunk:
|
||||
other: "Empty Chunk."
|
||||
|
||||
only_with_geometry:
|
||||
other: "only save skins with geometry"
|
||||
name_prefix:
|
||||
other: "only save players starting with this"
|
||||
failed_write:
|
||||
other: "failed to write {{.Part}} {{.Path}}: {{.Err}}"
|
||||
packet_filter:
|
||||
other: "packets to not show"
|
||||
|
||||
save_encrypted:
|
||||
other: "save encrypted resourcepacks"
|
||||
only_keys:
|
||||
other: "only dump keys, dont decrypt the pack"
|
||||
decrypting_packs:
|
||||
other: "Decrypting Resource Packs"
|
||||
no_resourcepacks:
|
||||
other: "No Resourcepack sent"
|
||||
writing_keys:
|
||||
other: "Writing keys to {{.Path}}"
|
||||
warn_key_exists:
|
||||
other: "key {{.Id}} exists already"
|
||||
compare_key:
|
||||
other: "uuid: {{.Id}}, key in db: {{.Prev}} != new key {{.Now}}"
|
||||
decrypting:
|
||||
other: "Decrypting..."
|
||||
|
||||
|
||||
list_realms_synopsis:
|
||||
other: "prints all realms you have access to"
|
||||
capture_synopsis:
|
||||
other: "capture packets in a pcap file"
|
||||
chat_log_synopsis:
|
||||
other: "logs chat to a file"
|
||||
debug_proxy_synopsis:
|
||||
other: "verbose debug packets"
|
||||
merge_synopsis:
|
||||
other: "merge 2 or more worlds"
|
||||
update_synopsis:
|
||||
other: "self updates to latest version"
|
||||
skins_proxy_synopsis:
|
||||
other: "download skins from players on a server with proxy"
|
||||
skins_synopsis:
|
||||
other: "download skins from players on a server"
|
||||
world_synopsis:
|
||||
other: "download a world from a server"
|
||||
pack_synopsis:
|
||||
other: "download resource packs from a server"
|
|
@ -0,0 +1,83 @@
|
|||
package locale
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/cloudfoundry-attic/jibber_jabber"
|
||||
"github.com/nicksnyder/go-i18n/v2/i18n"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/text/language"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type Strmap map[string]interface{}
|
||||
|
||||
//go:embed *.yaml
|
||||
var localesFS embed.FS
|
||||
var lang *i18n.Localizer
|
||||
|
||||
func load_language(bundle *i18n.Bundle, tag language.Tag) error {
|
||||
_, err := bundle.LoadMessageFileFS(localesFS, fmt.Sprintf("%s.yaml", tag.String()))
|
||||
return err
|
||||
}
|
||||
|
||||
func init() {
|
||||
var defaultTag language.Tag = language.English
|
||||
var err error
|
||||
|
||||
var languageName string
|
||||
f := flag.NewFlagSet("bedrocktool", flag.ContinueOnError)
|
||||
f.SetOutput(io.Discard)
|
||||
f.StringVar(&languageName, "lang", "", "")
|
||||
f.Parse(os.Args[1:])
|
||||
|
||||
// get default language
|
||||
if languageName == "" {
|
||||
languageName, _ = jibber_jabber.DetectLanguage()
|
||||
}
|
||||
defaultTag, err = language.Parse(languageName)
|
||||
if err != nil {
|
||||
logrus.Warn("failed to parse language name")
|
||||
}
|
||||
|
||||
bundle := i18n.NewBundle(defaultTag)
|
||||
bundle.RegisterUnmarshalFunc("yaml", yaml.Unmarshal)
|
||||
|
||||
err = load_language(bundle, defaultTag)
|
||||
if err != nil {
|
||||
//logrus.Warnf("Couldnt load Language %s", languageName)
|
||||
err = load_language(bundle, language.English)
|
||||
if err != nil {
|
||||
logrus.Error("failed to load english language")
|
||||
}
|
||||
}
|
||||
|
||||
lang = i18n.NewLocalizer(bundle, "en")
|
||||
}
|
||||
|
||||
func Loc(id string, tmpl Strmap) string {
|
||||
s, err := lang.Localize(&i18n.LocalizeConfig{
|
||||
MessageID: id,
|
||||
TemplateData: tmpl,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Sprintf("failed to translate! %s", id)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func Locm(id string, tmpl Strmap, count int) string {
|
||||
s, err := lang.Localize(&i18n.LocalizeConfig{
|
||||
MessageID: id,
|
||||
TemplateData: tmpl,
|
||||
PluralCount: count,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Sprintf("failed to translate! %s", id)
|
||||
}
|
||||
return s
|
||||
}
|
|
@ -0,0 +1,202 @@
|
|||
bedrocktool_version:
|
||||
other: "Bewdwocktoo Vewsion {{.Version}}"
|
||||
available_commands:
|
||||
other: "Avaiwabwu Commands:"
|
||||
use_to_run_command:
|
||||
other: "Use 'bewdwocktoo <command>' to wun a command"
|
||||
input_command:
|
||||
other: "Input Command: "
|
||||
fatal_error:
|
||||
other: "Fatal Ewwow occuwwed."
|
||||
report_issue:
|
||||
other: |
|
||||
if you want to weport this ewwow, pwease open an issue at
|
||||
https://github.com/bedwock-tool/bewdwocktoo/issues
|
||||
And attach the ewwow info, describe what you did to get this ewwow.
|
||||
Thanks!
|
||||
used_extra_debug_report:
|
||||
other: |
|
||||
NOTE: you have used extra-debug which creates a packets.log and a packets.log.gpg file
|
||||
please if you want to submit an issue only upload the packets.log.gpg file.
|
||||
packets.log and packets.log.gpg may include account info (never login info) so it is safer to upload the encrypted file.
|
||||
that file can be read by the developer and not by people looking at the public issues page.
|
||||
enter_to_exit:
|
||||
other: "Press Entew to exit."
|
||||
should_login_xbox:
|
||||
other: "if it should login to xbox"
|
||||
enable_dns:
|
||||
other: "enable dns sewvew for consoles"
|
||||
preload_packs:
|
||||
other: "pwewoad wewesouwcepacks for pwoxy"
|
||||
debug_mode:
|
||||
other: "debug mode"
|
||||
refreshed_token:
|
||||
other: "refweshed token"
|
||||
done:
|
||||
other: done
|
||||
|
||||
starting_dns:
|
||||
other: "Stawting dns at {{.Ip}}:53"
|
||||
failed_to_start_dns:
|
||||
other: "Failed to stawt dns sewvew: {{.Err}}"
|
||||
suggest_bedrockconnect:
|
||||
other: "you may have to use Bedwockconnect"
|
||||
|
||||
invalid_server:
|
||||
other: "Invalid Sewvew Address"
|
||||
enter_server:
|
||||
other: "Entew Sewvew: "
|
||||
disconnect:
|
||||
other: "Discownect {{.Pk}}"
|
||||
preloading_packs:
|
||||
other: "Pwewoading Wewesouwcepacks"
|
||||
failed_to_connect:
|
||||
other: "failed to connect to {{.Address}}: {{.Err}}"
|
||||
listening_on:
|
||||
other: "Listening on {{.Address}}"
|
||||
connecting:
|
||||
other: "Connecting to {{.Address}}"
|
||||
connected:
|
||||
other: "Connected to sewvew."
|
||||
connection_cancelled:
|
||||
other: "connection cancelled"
|
||||
failed_start_game:
|
||||
other: "failed to stawt game: {{.Err}}"
|
||||
help_connect:
|
||||
other: "Open Minecwaft and connect to this computews local ip address to continue"
|
||||
failed_to_spawn:
|
||||
other: "Failed to Spawn"
|
||||
not_supported_anymore:
|
||||
other: "not supported anymore"
|
||||
remote_address:
|
||||
other: "wemote sewvew address"
|
||||
ctrl_c_to_exit:
|
||||
other: "Press ctwl+c to exit"
|
||||
server_address_help:
|
||||
other: |
|
||||
accepted sewvew address formats:
|
||||
123.234.123.234
|
||||
123.234.123.234:19132
|
||||
wealm:<Wewalmname>
|
||||
wealm:<Wewalmname>:<Id>
|
||||
|
||||
realm_list_line:
|
||||
other: "Name: {{.Name}}\tid: {{.Id}}"
|
||||
|
||||
failed_to_open_output:
|
||||
other: "Failed to open output"
|
||||
adding_world:
|
||||
other: "Adding {{.World}}"
|
||||
not_found:
|
||||
other: "{{.World}} Not Found"
|
||||
need_to_specify_multiple_worlds:
|
||||
other: "you need to specify mowe than 1 world to merge"
|
||||
|
||||
update_available:
|
||||
other: "Update avaiwabwu: {{.Version}} use the update command to install it."
|
||||
no_update:
|
||||
other: "No Updates avaiwabwu."
|
||||
updating:
|
||||
other: "Updating to {{.Version}}"
|
||||
updated:
|
||||
other: "Updated!"
|
||||
|
||||
worldname_set:
|
||||
other: "wowldName is now {{.Name}}"
|
||||
void_generator_true:
|
||||
other: "Using Void Genewowator"
|
||||
void_generator_false:
|
||||
other: "Not using Void Genewowator"
|
||||
subchunk_before_chunk:
|
||||
other: "The sewvew didnt send the chunk befowe the subchunk!"
|
||||
zoom_level:
|
||||
other: "Zoom: {{.Level}}"
|
||||
not_saving_empty:
|
||||
other: "Skipping save because the wowld didnt contain any chunks."
|
||||
saving_world:
|
||||
one: "Saving wowld {{.Name}} with {{.Count}} Chunk"
|
||||
other: "Saving wowld {{.Name}} with {{.Count}} Chunks"
|
||||
unknown_gamerule:
|
||||
other: "unknown gamerule: {{.Name}}"
|
||||
adding_pack:
|
||||
other: "Adding Wewesouwcepack {{.Name}}"
|
||||
using_customblocks:
|
||||
other: "Using Custowm Blocks"
|
||||
guessing_version:
|
||||
other: "couldnt detewmine game version, assuming > 1.18"
|
||||
use_setname:
|
||||
other: "use /setname <wowldname>\nto set the wowld name"
|
||||
using_under_118:
|
||||
other: "using wegacy (< 1.18)"
|
||||
setname_desc:
|
||||
other: "set usew defined name for this wowld"
|
||||
void_desc:
|
||||
other: "toggwe if void genewowator should be used"
|
||||
popup_chunk_count:
|
||||
one: "{{.Count}} Chunk loaded\nName: {{.Name}}"
|
||||
other: "{{.Count}} Chunks loaded\nName: {{.Name}}"
|
||||
warn_window_closed_not_open:
|
||||
other: "Closed window that wasnt open"
|
||||
saved_block_inv:
|
||||
other: "Saved Bwock Inventory"
|
||||
save_packs_with_world:
|
||||
other: "save wewesouwcepacks to the wowlds"
|
||||
enable_void:
|
||||
other: "save with void genewowator"
|
||||
save_image:
|
||||
other: "saves an png of the map at the end"
|
||||
test_block_inv:
|
||||
other: "enable experimental bwock inventory saving"
|
||||
saved:
|
||||
other: "Saved: {{.Name}}"
|
||||
empty_chunk:
|
||||
other: "Empty Chunk."
|
||||
|
||||
|
||||
only_with_geometry:
|
||||
other: "only save skins with geometry"
|
||||
name_prefix:
|
||||
other: "only save playews stawting with this"
|
||||
failed_write:
|
||||
other: "failed to write {{.Part}} {{.Path}}: {{.Err}}"
|
||||
packet_filter:
|
||||
other: "packets to not show"
|
||||
|
||||
save_encrypted:
|
||||
other: "save encrypted wewesouwcepacks"
|
||||
only_keys:
|
||||
other: "only dump keys, dont decrypt the pack"
|
||||
decrypting_packs:
|
||||
other: "Decrypting Wewesouwce Packs"
|
||||
no_resourcepacks:
|
||||
other: "No Wewesouwcepack sent"
|
||||
writing_keys:
|
||||
other: "Writing keys to {{.Path}}"
|
||||
warn_key_exists:
|
||||
other: "key {{.Id}} exists already"
|
||||
compare_key:
|
||||
other: "uuid: {{.Id}}, key in db: {{.Prev}} != new key {{.Now}}"
|
||||
decrypting:
|
||||
other: "Decrypting..."
|
||||
|
||||
|
||||
list_realms_synopsis:
|
||||
other: "pwints all wealms you have access to"
|
||||
capture_synopsis:
|
||||
other: "capture packets in a pcap file"
|
||||
chat_log_synopsis:
|
||||
other: "logs chat to a file"
|
||||
debug_proxy_synopsis:
|
||||
other: "vewbose debug packets"
|
||||
merge_synopsis:
|
||||
other: "mewge 2 or mowe wowlds"
|
||||
update_synopsis:
|
||||
other: "self updates to latest version"
|
||||
skins_proxy_synopsis:
|
||||
other: "download skins from playews on a sewvew with pwoxy"
|
||||
skins_synopsis:
|
||||
other: "download skins from playews on a sewvew"
|
||||
world_synopsis:
|
||||
other: "download a wowld from a sewvew"
|
||||
pack_synopsis:
|
||||
other: "download wewesouwce packs from a sewvew"
|
|
@ -1,5 +0,0 @@
|
|||
{
|
||||
"Replace": {
|
||||
"utils/resourcepack.go": "utils/resourcepack-ace.go.ignore"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,106 @@
|
|||
package subcommands
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
"github.com/bedrock-tool/bedrocktool/utils"
|
||||
"github.com/sandertv/go-raknet"
|
||||
"github.com/sandertv/gophertunnel/minecraft/protocol"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type BlindProxyCMD struct {
|
||||
ServerAddress string
|
||||
}
|
||||
|
||||
func (*BlindProxyCMD) Name() string { return "blind-proxy" }
|
||||
func (*BlindProxyCMD) Synopsis() string { return "raknet proxy" }
|
||||
func (c *BlindProxyCMD) SetFlags(f *flag.FlagSet) {
|
||||
f.StringVar(&c.ServerAddress, "address", "", "server address")
|
||||
}
|
||||
|
||||
func packet_forward(src, dst *raknet.Conn) error {
|
||||
for {
|
||||
data, err := src.ReadPacket()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = dst.Write(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *BlindProxyCMD) Execute(ctx context.Context, ui utils.UI) error {
|
||||
address, hostname, err := utils.ServerInput(ctx, c.ServerAddress)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
listener, err := raknet.Listen("0.0.0.0:19132")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer listener.Close()
|
||||
logrus.Info("Listening on 0.0.0.0:19132")
|
||||
|
||||
listener.PongData([]byte(fmt.Sprintf("MCPE;%v;%v;%v;%v;%v;%v;Gophertunnel;%v;%v;%v;%v;",
|
||||
"Proxy For "+hostname, protocol.CurrentProtocol, protocol.CurrentVersion, 0, 1,
|
||||
listener.ID(), "Creative", 1, listener.Addr().(*net.UDPAddr).Port, listener.Addr().(*net.UDPAddr).Port,
|
||||
)))
|
||||
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
listener.Close()
|
||||
}()
|
||||
|
||||
clientConn, err := listener.Accept()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer clientConn.Close()
|
||||
logrus.Info("Client Connected")
|
||||
|
||||
serverConn, err := raknet.DialContext(ctx, address)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer serverConn.Close()
|
||||
logrus.Info("Server Connected")
|
||||
|
||||
logrus.Info("Forwarding Packets")
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
_err := packet_forward(clientConn.(*raknet.Conn), serverConn)
|
||||
if _err != nil {
|
||||
err = _err
|
||||
}
|
||||
}()
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
_err := packet_forward(serverConn, clientConn.(*raknet.Conn))
|
||||
if _err != nil {
|
||||
err = _err
|
||||
}
|
||||
}()
|
||||
|
||||
wg.Wait()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
utils.RegisterCommand(&BlindProxyCMD{})
|
||||
}
|
|
@ -11,9 +11,9 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/bedrock-tool/bedrocktool/locale"
|
||||
"github.com/bedrock-tool/bedrocktool/utils"
|
||||
|
||||
"github.com/google/subcommands"
|
||||
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
@ -22,66 +22,67 @@ func init() {
|
|||
utils.RegisterCommand(&CaptureCMD{})
|
||||
}
|
||||
|
||||
var dump_lock sync.Mutex
|
||||
var dumpLock sync.Mutex
|
||||
|
||||
func dump_packet(f io.WriteCloser, toServer bool, payload []byte) {
|
||||
dump_lock.Lock()
|
||||
defer dump_lock.Unlock()
|
||||
func dumpPacket(f io.WriteCloser, toServer bool, payload []byte) {
|
||||
dumpLock.Lock()
|
||||
defer dumpLock.Unlock()
|
||||
f.Write([]byte{0xAA, 0xAA, 0xAA, 0xAA})
|
||||
packet_size := uint32(len(payload))
|
||||
binary.Write(f, binary.LittleEndian, packet_size)
|
||||
packetSize := uint32(len(payload))
|
||||
binary.Write(f, binary.LittleEndian, packetSize)
|
||||
binary.Write(f, binary.LittleEndian, toServer)
|
||||
_, err := f.Write(payload)
|
||||
binary.Write(f, binary.LittleEndian, time.Now().UnixMilli())
|
||||
n, err := f.Write(payload)
|
||||
if err != nil {
|
||||
logrus.Error(err)
|
||||
}
|
||||
if n < int(packetSize) {
|
||||
f.Write(make([]byte, int(packetSize)-n))
|
||||
}
|
||||
f.Write([]byte{0xBB, 0xBB, 0xBB, 0xBB})
|
||||
}
|
||||
|
||||
type CaptureCMD struct {
|
||||
server_address string
|
||||
ServerAddress string
|
||||
}
|
||||
|
||||
func (*CaptureCMD) Name() string { return "capture" }
|
||||
func (*CaptureCMD) Synopsis() string { return "capture packets in a pcap file" }
|
||||
|
||||
func (p *CaptureCMD) SetFlags(f *flag.FlagSet) {
|
||||
f.StringVar(&p.server_address, "address", "", "remote server address")
|
||||
func (*CaptureCMD) Synopsis() string { return locale.Loc("capture_synopsis", nil) }
|
||||
func (c *CaptureCMD) SetFlags(f *flag.FlagSet) {
|
||||
f.StringVar(&c.ServerAddress, "address", "", "remote server address")
|
||||
}
|
||||
|
||||
func (c *CaptureCMD) Usage() string {
|
||||
return c.Name() + ": " + c.Synopsis() + "\n" + utils.SERVER_ADDRESS_HELP
|
||||
}
|
||||
|
||||
func (c *CaptureCMD) Execute(ctx context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
|
||||
address, hostname, err := utils.ServerInput(ctx, c.server_address)
|
||||
func (c *CaptureCMD) Execute(ctx context.Context, ui utils.UI) error {
|
||||
address, hostname, err := utils.ServerInput(ctx, c.ServerAddress)
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
return 1
|
||||
return err
|
||||
}
|
||||
|
||||
fio, err := os.Create(hostname + "-" + time.Now().Format("2006-01-02_15-04-05") + ".pcap2")
|
||||
os.Mkdir("captures", 0o775)
|
||||
fio, err := os.Create("captures/" + hostname + "-" + time.Now().Format("2006-01-02_15-04-05") + ".pcap2")
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
return 1
|
||||
return err
|
||||
}
|
||||
defer fio.Close()
|
||||
utils.WriteReplayHeader(fio)
|
||||
|
||||
proxy := utils.NewProxy(logrus.StandardLogger())
|
||||
proxy, err := utils.NewProxy()
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
proxy.PacketFunc = func(header packet.Header, payload []byte, src, dst net.Addr) {
|
||||
from_client := src.String() == proxy.Client.LocalAddr().String()
|
||||
IsfromClient := src.String() == proxy.Client.LocalAddr().String()
|
||||
|
||||
buf := bytes.NewBuffer(nil)
|
||||
header.Write(buf)
|
||||
buf.Write(payload)
|
||||
dump_packet(fio, from_client, buf.Bytes())
|
||||
dumpPacket(fio, IsfromClient, buf.Bytes())
|
||||
}
|
||||
|
||||
err = proxy.Run(ctx, address)
|
||||
time.Sleep(2 * time.Second)
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
return 1
|
||||
return err
|
||||
}
|
||||
return 0
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -7,49 +7,46 @@ import (
|
|||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/bedrock-tool/bedrocktool/locale"
|
||||
"github.com/bedrock-tool/bedrocktool/utils"
|
||||
|
||||
"github.com/google/subcommands"
|
||||
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type ChatLogCMD struct {
|
||||
Address string
|
||||
verbose bool
|
||||
ServerAddress string
|
||||
Verbose bool
|
||||
}
|
||||
|
||||
func (*ChatLogCMD) Name() string { return "chat-log" }
|
||||
func (*ChatLogCMD) Synopsis() string { return "logs chat to a file" }
|
||||
|
||||
func (*ChatLogCMD) Synopsis() string { return locale.Loc("chat_log_synopsis", nil) }
|
||||
func (c *ChatLogCMD) SetFlags(f *flag.FlagSet) {
|
||||
f.StringVar(&c.Address, "address", "", "remote server address")
|
||||
f.BoolVar(&c.verbose, "v", false, "verbose")
|
||||
f.StringVar(&c.ServerAddress, "address", "", "remote server address")
|
||||
f.BoolVar(&c.Verbose, "v", false, "verbose")
|
||||
}
|
||||
|
||||
func (c *ChatLogCMD) Usage() string {
|
||||
return c.Name() + ": " + c.Synopsis() + "\n" + utils.SERVER_ADDRESS_HELP
|
||||
}
|
||||
|
||||
func (c *ChatLogCMD) Execute(ctx context.Context, flags *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
|
||||
address, hostname, err := utils.ServerInput(ctx, c.Address)
|
||||
func (c *ChatLogCMD) Execute(ctx context.Context, ui utils.UI) error {
|
||||
address, hostname, err := utils.ServerInput(ctx, c.ServerAddress)
|
||||
if err != nil {
|
||||
logrus.Error(err)
|
||||
return 1
|
||||
return err
|
||||
}
|
||||
|
||||
filename := fmt.Sprintf("%s_%s_chat.log", hostname, time.Now().Format("2006-01-02_15-04-05_Z07"))
|
||||
f, err := os.Create(filename)
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
proxy := utils.NewProxy(logrus.StandardLogger())
|
||||
proxy.PacketCB = func(pk packet.Packet, proxy *utils.ProxyContext, toServer bool) (packet.Packet, error) {
|
||||
proxy, err := utils.NewProxy()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
proxy.PacketCB = func(pk packet.Packet, proxy *utils.ProxyContext, toServer bool, _ time.Time) (packet.Packet, error) {
|
||||
if text, ok := pk.(*packet.Text); ok {
|
||||
logLine := text.Message
|
||||
if c.verbose {
|
||||
if c.Verbose {
|
||||
logLine += fmt.Sprintf(" (TextType: %d | XUID: %s | PlatformChatID: %s)", text.TextType, text.XUID, text.PlatformChatID)
|
||||
}
|
||||
f.WriteString(fmt.Sprintf("[%s] ", time.Now().Format(time.RFC3339)))
|
||||
|
@ -62,12 +59,8 @@ func (c *ChatLogCMD) Execute(ctx context.Context, flags *flag.FlagSet, _ ...inte
|
|||
return pk, nil
|
||||
}
|
||||
|
||||
if err := proxy.Run(ctx, address); err != nil {
|
||||
logrus.Error(err)
|
||||
return 1
|
||||
}
|
||||
|
||||
return 0
|
||||
err = proxy.Run(ctx, address)
|
||||
return err
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
|
|
@ -5,39 +5,31 @@ import (
|
|||
"flag"
|
||||
"strings"
|
||||
|
||||
"github.com/bedrock-tool/bedrocktool/locale"
|
||||
"github.com/bedrock-tool/bedrocktool/utils"
|
||||
|
||||
"github.com/google/subcommands"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type DebugProxyCMD struct {
|
||||
Address string
|
||||
filter string
|
||||
ServerAddress string
|
||||
Filter string
|
||||
}
|
||||
|
||||
func (*DebugProxyCMD) Name() string { return "debug-proxy" }
|
||||
func (*DebugProxyCMD) Synopsis() string { return "verbose debug packets" }
|
||||
|
||||
func (*DebugProxyCMD) Synopsis() string { return locale.Loc("debug_proxy_synopsis", nil) }
|
||||
func (c *DebugProxyCMD) SetFlags(f *flag.FlagSet) {
|
||||
f.StringVar(&c.Address, "address", "", "remote server address")
|
||||
f.StringVar(&c.filter, "filter", "", "packets to not show")
|
||||
f.StringVar(&c.ServerAddress, "address", "", locale.Loc("remote_address", nil))
|
||||
f.StringVar(&c.Filter, "filter", "", locale.Loc("packet_filter", nil))
|
||||
}
|
||||
|
||||
func (c *DebugProxyCMD) Usage() string {
|
||||
return c.Name() + ": " + c.Synopsis() + "\n" + utils.SERVER_ADDRESS_HELP
|
||||
}
|
||||
|
||||
func (c *DebugProxyCMD) Execute(ctx context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
|
||||
address, _, err := utils.ServerInput(ctx, c.Address)
|
||||
func (c *DebugProxyCMD) Execute(ctx context.Context, ui utils.UI) error {
|
||||
address, _, err := utils.ServerInput(ctx, c.ServerAddress)
|
||||
if err != nil {
|
||||
logrus.Error(err)
|
||||
return 1
|
||||
return err
|
||||
}
|
||||
|
||||
utils.G_debug = true
|
||||
utils.Options.Debug = true
|
||||
|
||||
filters := strings.Split(c.filter, ",")
|
||||
filters := strings.Split(c.Filter, ",")
|
||||
if len(filters) > 0 {
|
||||
for _, v := range filters {
|
||||
if len(v) == 0 {
|
||||
|
@ -52,12 +44,12 @@ func (c *DebugProxyCMD) Execute(ctx context.Context, f *flag.FlagSet, _ ...inter
|
|||
}
|
||||
}
|
||||
|
||||
proxy := utils.NewProxy(logrus.StandardLogger())
|
||||
if err := proxy.Run(ctx, address); err != nil {
|
||||
logrus.Error(err)
|
||||
return 1
|
||||
proxy, err := utils.NewProxy()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return 0
|
||||
err = proxy.Run(ctx, address)
|
||||
return err
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
//go:build false
|
||||
|
||||
package subcommands
|
||||
|
||||
import (
|
||||
|
@ -7,8 +9,10 @@ import (
|
|||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/bedrock-tool/bedrocktool/locale"
|
||||
"github.com/bedrock-tool/bedrocktool/utils"
|
||||
|
||||
"github.com/df-mc/dragonfly/server/entity"
|
||||
"github.com/df-mc/dragonfly/server/world/mcdb"
|
||||
"github.com/df-mc/goleveldb/leveldb/opt"
|
||||
"github.com/google/subcommands"
|
||||
|
@ -22,7 +26,7 @@ type MergeCMD struct {
|
|||
}
|
||||
|
||||
func (*MergeCMD) Name() string { return "merge" }
|
||||
func (*MergeCMD) Synopsis() string { return "merge 2 or more worlds" }
|
||||
func (*MergeCMD) Synopsis() string { return locale.Loc("merge_synopsis", nil) }
|
||||
|
||||
func (c *MergeCMD) SetFlags(f *flag.FlagSet) {
|
||||
f.BoolVar(&c.legacy, "legacy", false, "if the worlds are before 1.18")
|
||||
|
@ -34,91 +38,91 @@ func (c *MergeCMD) Usage() string {
|
|||
|
||||
func (c *MergeCMD) Execute(ctx context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
|
||||
if f.NArg() == 0 {
|
||||
logrus.Error("you need to specify 1 or more worlds")
|
||||
logrus.Error(locale.Loc("need_to_specify_multiple_worlds", nil))
|
||||
return 1
|
||||
}
|
||||
c.worlds = f.Args()
|
||||
out_name := c.worlds[0] + "-merged"
|
||||
outName := c.worlds[0] + "-merged"
|
||||
|
||||
prov_out, err := mcdb.New(logrus.StandardLogger(), out_name, opt.DefaultCompression)
|
||||
provOut, err := mcdb.New(logrus.StandardLogger(), outName, opt.DefaultCompression)
|
||||
if err != nil {
|
||||
logrus.Errorf("failed to open output %s", err)
|
||||
logrus.Errorf(locale.Loc("failed_to_open_output", locale.Strmap{"Err": err}))
|
||||
return 1
|
||||
}
|
||||
|
||||
for i, world_name := range c.worlds {
|
||||
for i, worldName := range c.worlds {
|
||||
first := i == 0
|
||||
logrus.Infof("Adding %s", world_name)
|
||||
s, err := os.Stat(world_name)
|
||||
logrus.Infof(locale.Loc("adding_world", locale.Strmap{"World": worldName}))
|
||||
s, err := os.Stat(worldName)
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
logrus.Fatalf("%s not found", world_name)
|
||||
logrus.Fatalf(locale.Loc("not_found", locale.Strmap{"Name": worldName}), worldName)
|
||||
}
|
||||
if !s.IsDir() { // if its a zip temporarily unpack it to read it
|
||||
f, _ := os.Open(world_name)
|
||||
world_name += "_unpack"
|
||||
utils.UnpackZip(f, s.Size(), world_name)
|
||||
f, _ := os.Open(worldName)
|
||||
worldName += "_unpack"
|
||||
utils.UnpackZip(f, s.Size(), worldName)
|
||||
}
|
||||
// merge it into the state
|
||||
err = c.merge_worlds(prov_out, world_name, first)
|
||||
err = c.mergeWorlds(provOut, worldName, first)
|
||||
if err != nil {
|
||||
logrus.Errorf("%s %s", world_name, err)
|
||||
logrus.Errorf("%s %s", worldName, err)
|
||||
return 1
|
||||
}
|
||||
if !s.IsDir() { // remove temp folder again
|
||||
os.RemoveAll(world_name)
|
||||
os.RemoveAll(worldName)
|
||||
}
|
||||
}
|
||||
|
||||
if err = prov_out.Close(); err != nil {
|
||||
if err = provOut.Close(); err != nil {
|
||||
logrus.Error(err)
|
||||
return 1
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
if err := utils.ZipFolder(out_name+".mcworld", out_name); err != nil {
|
||||
if err := utils.ZipFolder(outName+".mcworld", outName); err != nil {
|
||||
logrus.Infof("zipping: %s", err)
|
||||
return 1
|
||||
}
|
||||
|
||||
os.RemoveAll(out_name)
|
||||
os.RemoveAll(outName)
|
||||
return 0
|
||||
}
|
||||
|
||||
func (c *MergeCMD) merge_worlds(prov_out *mcdb.Provider, folder string, first bool) error {
|
||||
prov_in, err := mcdb.New(logrus.StandardLogger(), folder, opt.DefaultCompression)
|
||||
func (c *MergeCMD) mergeWorlds(provOut *mcdb.Provider, folder string, first bool) error {
|
||||
provIn, err := mcdb.New(logrus.StandardLogger(), folder, opt.DefaultCompression)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
count := 0
|
||||
existing := prov_out.Chunks(c.legacy)
|
||||
new := prov_in.Chunks(c.legacy)
|
||||
existing := provOut.Chunks(c.legacy)
|
||||
new := provIn.Chunks(c.legacy)
|
||||
for i := range new {
|
||||
if _, ok := existing[i]; !ok {
|
||||
d := i.D
|
||||
// chunks
|
||||
ch, _, err := prov_in.LoadChunk(i.P, d)
|
||||
ch, _, err := provIn.LoadChunk(i.P, d)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := prov_out.SaveChunk(i.P, ch, i.D); err != nil {
|
||||
if err := provOut.SaveChunk(i.P, ch, i.D); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// blockNBT
|
||||
n, err := prov_in.LoadBlockNBT(i.P, i.D)
|
||||
n, err := provIn.LoadBlockNBT(i.P, i.D)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := prov_out.SaveBlockNBT(i.P, n, i.D); err != nil {
|
||||
if err := provOut.SaveBlockNBT(i.P, n, i.D); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// entities
|
||||
entities, err := prov_in.LoadEntities(i.P, i.D)
|
||||
entities, err := provIn.LoadEntities(i.P, i.D, entity.DefaultRegistry)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := prov_out.SaveEntities(i.P, entities, i.D); err != nil {
|
||||
if err := provOut.SaveEntities(i.P, entities, i.D); err != nil {
|
||||
return err
|
||||
}
|
||||
count += 1
|
||||
|
@ -127,9 +131,9 @@ func (c *MergeCMD) merge_worlds(prov_out *mcdb.Provider, folder string, first bo
|
|||
|
||||
if first {
|
||||
logrus.Debug("Applying Settings and level.dat")
|
||||
prov_out.SaveSettings(prov_in.Settings())
|
||||
out_ld := prov_out.LevelDat()
|
||||
copier.Copy(out_ld, prov_in.LevelDat())
|
||||
provOut.SaveSettings(provIn.Settings())
|
||||
outLd := provOut.LevelDat()
|
||||
copier.Copy(outLd, provIn.LevelDat())
|
||||
}
|
||||
logrus.Infof("Added: %d", count)
|
||||
return nil
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
package subcommands
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
|
||||
"github.com/bedrock-tool/bedrocktool/utils"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type RealmAddressCMD struct {
|
||||
realm string
|
||||
}
|
||||
|
||||
func (*RealmAddressCMD) Name() string { return "realm-address" }
|
||||
func (*RealmAddressCMD) Synopsis() string { return "gets realms address" }
|
||||
func (c *RealmAddressCMD) SetFlags(f *flag.FlagSet) {
|
||||
f.StringVar(&c.realm, "realm", "", "realm name <name:id> or just name")
|
||||
}
|
||||
|
||||
func (c *RealmAddressCMD) Execute(ctx context.Context, ui utils.UI) error {
|
||||
address, _, err := utils.ServerInput(ctx, "realm:"+c.realm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logrus.Infof("Address: %s", address)
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
utils.RegisterCommand(&RealmAddressCMD{})
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
//go:build packs
|
||||
|
||||
package subcommands
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
|
||||
"github.com/bedrock-tool/bedrocktool/locale"
|
||||
resourcepackd "github.com/bedrock-tool/bedrocktool/subcommands/resourcepack-d"
|
||||
"github.com/bedrock-tool/bedrocktool/utils"
|
||||
)
|
||||
|
||||
type ResourcePackCMD struct {
|
||||
ServerAddress string
|
||||
SaveEncrypted bool
|
||||
OnlyKeys bool
|
||||
}
|
||||
|
||||
func (*ResourcePackCMD) Name() string { return "packs" }
|
||||
func (*ResourcePackCMD) Synopsis() string { return locale.Loc("pack_synopsis", nil) }
|
||||
|
||||
func (c *ResourcePackCMD) SetFlags(f *flag.FlagSet) {
|
||||
f.StringVar(&c.ServerAddress, "address", "", locale.Loc("remote_address", nil))
|
||||
f.BoolVar(&c.SaveEncrypted, "save-encrypted", false, locale.Loc("save_encrypted", nil))
|
||||
f.BoolVar(&c.OnlyKeys, "only-keys", false, locale.Loc("only_keys", nil))
|
||||
}
|
||||
|
||||
func (c *ResourcePackCMD) Execute(ctx context.Context, ui utils.UI) error {
|
||||
return resourcepackd.Execute_cmd(ctx, c.ServerAddress, c.OnlyKeys, c.SaveEncrypted)
|
||||
}
|
||||
|
||||
func init() {
|
||||
utils.RegisterCommand(&ResourcePackCMD{})
|
||||
|
||||
}
|
Binary file not shown.
|
@ -0,0 +1,28 @@
|
|||
//go:build !packs
|
||||
|
||||
package subcommands
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"flag"
|
||||
|
||||
"github.com/bedrock-tool/bedrocktool/utils"
|
||||
)
|
||||
|
||||
type ResourcePackCMD struct {
|
||||
ServerAddress string
|
||||
SaveEncrypted bool
|
||||
OnlyKeys bool
|
||||
}
|
||||
|
||||
func (*ResourcePackCMD) Name() string { return "packs" }
|
||||
func (*ResourcePackCMD) Synopsis() string { return "NOT COMPILED" }
|
||||
func (*ResourcePackCMD) SetFlags(f *flag.FlagSet) {}
|
||||
func (*ResourcePackCMD) Execute(ctx context.Context, ui utils.UI) error {
|
||||
return errors.New("not compiled")
|
||||
}
|
||||
|
||||
func init() {
|
||||
utils.RegisterCommand(&ResourcePackCMD{})
|
||||
}
|
|
@ -0,0 +1,153 @@
|
|||
package skins
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/png"
|
||||
"os"
|
||||
|
||||
"github.com/bedrock-tool/bedrocktool/locale"
|
||||
"github.com/google/uuid"
|
||||
"github.com/sandertv/gophertunnel/minecraft/protocol"
|
||||
)
|
||||
|
||||
type Skin struct {
|
||||
*protocol.Skin
|
||||
}
|
||||
|
||||
type SkinGeometry struct {
|
||||
Texturewidth int `json:"texturewidth"`
|
||||
Textureheight int `json:"textureheight"`
|
||||
VisibleBoundsWidth float64 `json:"visible_bounds_width"`
|
||||
VisibleBoundsHeight float64 `json:"visible_bounds_height"`
|
||||
VisibleBoundsOffset []float64 `json:"visible_bounds_offset"`
|
||||
Bones []any `json:"bones"`
|
||||
}
|
||||
|
||||
func (skin *Skin) Hash() uuid.UUID {
|
||||
h := sha256.New()
|
||||
h.Write(skin.SkinData)
|
||||
h.Write(skin.SkinGeometry)
|
||||
h.Write(skin.CapeData)
|
||||
h.Write([]byte(skin.SkinID))
|
||||
return uuid.NewSHA1(uuid.NameSpaceURL, h.Sum(nil))
|
||||
}
|
||||
|
||||
func (skin *Skin) getGeometry() (*SkinGeometry, string, error) {
|
||||
if !skin.HaveGeometry() {
|
||||
return nil, "", errors.New("no geometry")
|
||||
}
|
||||
|
||||
var data map[string]any
|
||||
if err := json.Unmarshal(skin.SkinGeometry, &data); err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
arr, ok := data["minecraft:geometry"].([]any)
|
||||
if !ok {
|
||||
return nil, "", errors.New("invalid geometry")
|
||||
}
|
||||
geom, ok := arr[0].(map[string]any)
|
||||
if !ok {
|
||||
return nil, "", errors.New("invalid geometry")
|
||||
}
|
||||
|
||||
desc, ok := geom["description"].(map[string]any)
|
||||
if !ok {
|
||||
return nil, "", errors.New("invalid geometry")
|
||||
}
|
||||
|
||||
texture_width, _ := desc["texture_width"].(float64)
|
||||
texture_height, _ := desc["texture_height"].(float64)
|
||||
visible_bounds_width, _ := desc["visible_bounds_width"].(float64)
|
||||
visible_bounds_height, _ := desc["visible_bounds_height"].(float64)
|
||||
visibleOffset, _ := desc["visible_bounds_offset"].([]float64)
|
||||
|
||||
return &SkinGeometry{
|
||||
Texturewidth: int(texture_width),
|
||||
Textureheight: int(texture_height),
|
||||
VisibleBoundsWidth: visible_bounds_width,
|
||||
VisibleBoundsHeight: visible_bounds_height,
|
||||
VisibleBoundsOffset: visibleOffset,
|
||||
Bones: geom["bones"].([]any),
|
||||
}, desc["identifier"].(string), nil
|
||||
}
|
||||
|
||||
// WriteCape writes the cape as a png at output_path
|
||||
func (skin *Skin) WriteCapePng(output_path string) error {
|
||||
f, err := os.Create(output_path)
|
||||
if err != nil {
|
||||
return errors.New(locale.Loc("failed_write", locale.Strmap{"Part": "Cape", "Path": output_path, "Err": err}))
|
||||
}
|
||||
defer f.Close()
|
||||
cape_tex := image.NewRGBA(image.Rect(0, 0, int(skin.CapeImageWidth), int(skin.CapeImageHeight)))
|
||||
cape_tex.Pix = skin.CapeData
|
||||
|
||||
if err := png.Encode(f, cape_tex); err != nil {
|
||||
return fmt.Errorf(locale.Loc("failed_write", locale.Strmap{"Part": "Cape", "Err": err}))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteTexture writes the main texture for this skin to a file
|
||||
func (skin *Skin) writeSkinTexturePng(output_path string) error {
|
||||
f, err := os.Create(output_path)
|
||||
if err != nil {
|
||||
return errors.New(locale.Loc("failed_write", locale.Strmap{"Part": "Meta", "Path": output_path, "Err": err}))
|
||||
}
|
||||
defer f.Close()
|
||||
skin_tex := image.NewRGBA(image.Rect(0, 0, int(skin.SkinImageWidth), int(skin.SkinImageHeight)))
|
||||
skin_tex.Pix = skin.SkinData
|
||||
|
||||
if err := png.Encode(f, skin_tex); err != nil {
|
||||
return errors.New(locale.Loc("failed_write", locale.Strmap{"Part": "Texture", "Path": output_path, "Err": err}))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (skin *Skin) writeMetadataJson(output_path string) error {
|
||||
f, err := os.Create(output_path)
|
||||
if err != nil {
|
||||
return errors.New(locale.Loc("failed_write", locale.Strmap{"Part": "Meta", "Path": output_path, "Err": err}))
|
||||
}
|
||||
defer f.Close()
|
||||
d, err := json.MarshalIndent(SkinMeta{
|
||||
skin.SkinID,
|
||||
skin.PlayFabID,
|
||||
skin.PremiumSkin,
|
||||
skin.PersonaSkin,
|
||||
skin.CapeID,
|
||||
skin.SkinColour,
|
||||
skin.ArmSize,
|
||||
skin.Trusted,
|
||||
skin.PersonaPieces,
|
||||
}, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f.Write(d)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (skin *Skin) HaveGeometry() bool {
|
||||
return len(skin.SkinGeometry) > 0
|
||||
}
|
||||
|
||||
func (skin *Skin) HaveCape() bool {
|
||||
return len(skin.CapeData) > 0
|
||||
}
|
||||
|
||||
func (skin *Skin) HaveAnimations() bool {
|
||||
return len(skin.Animations) > 0
|
||||
}
|
||||
|
||||
func (skin *Skin) HaveTint() bool {
|
||||
return len(skin.PieceTintColours) > 0
|
||||
}
|
||||
|
||||
func (skin *Skin) Complex() bool {
|
||||
return skin.HaveGeometry() || skin.HaveCape() || skin.HaveAnimations() || skin.HaveTint()
|
||||
}
|
|
@ -0,0 +1,150 @@
|
|||
package skins
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"github.com/bedrock-tool/bedrocktool/utils"
|
||||
"github.com/google/uuid"
|
||||
"github.com/sandertv/gophertunnel/minecraft/resource"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type _skinWithIndex struct {
|
||||
i int
|
||||
skin *Skin
|
||||
}
|
||||
|
||||
func (s _skinWithIndex) Name(name string) string {
|
||||
return fmt.Sprintf("%s-%d", name, s.i)
|
||||
}
|
||||
|
||||
type SkinPack struct {
|
||||
skins map[uuid.UUID]_skinWithIndex
|
||||
Name string
|
||||
}
|
||||
|
||||
type skinEntry struct {
|
||||
LocalizationName string `json:"localization_name"`
|
||||
Geometry string `json:"geometry"`
|
||||
Texture string `json:"texture"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
func NewSkinPack(name, fpath string) *SkinPack {
|
||||
return &SkinPack{
|
||||
skins: make(map[uuid.UUID]_skinWithIndex),
|
||||
Name: name,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SkinPack) AddSkin(skin *Skin) bool {
|
||||
sh := skin.Hash()
|
||||
if _, ok := s.skins[sh]; !ok {
|
||||
s.skins[sh] = _skinWithIndex{len(s.skins) + 1, skin}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *SkinPack) Save(fpath, serverName string) error {
|
||||
os.MkdirAll(fpath, 0o755)
|
||||
|
||||
var skinsJson struct {
|
||||
Skins []skinEntry `json:"skins"`
|
||||
}
|
||||
geometryJson := map[string]SkinGeometry{}
|
||||
|
||||
for _, s2 := range s.skins { // write skin texture
|
||||
skinName := s2.Name(s.Name)
|
||||
|
||||
if err := s2.skin.writeSkinTexturePng(path.Join(fpath, skinName+".png")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := s2.skin.writeMetadataJson(path.Join(fpath, skinName+"_metadata.json")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if s2.skin.HaveCape() {
|
||||
if err := s2.skin.WriteCapePng(path.Join(fpath, skinName+"_cape.png")); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
entry := skinEntry{
|
||||
LocalizationName: skinName,
|
||||
Texture: skinName + ".png",
|
||||
Type: "free",
|
||||
}
|
||||
if s2.skin.ArmSize == "wide" {
|
||||
entry.Geometry = "minecraft.geometry.steve"
|
||||
} else {
|
||||
entry.Geometry = "minecraft.geometry.alex"
|
||||
}
|
||||
|
||||
if s2.skin.HaveGeometry() {
|
||||
geometry, geometryName, err := s2.skin.getGeometry()
|
||||
if err != nil {
|
||||
logrus.Warnf("failed to decode geometry %s", skinName)
|
||||
} else {
|
||||
geometryJson[geometryName] = *geometry
|
||||
entry.Geometry = geometryName
|
||||
}
|
||||
}
|
||||
skinsJson.Skins = append(skinsJson.Skins, entry)
|
||||
}
|
||||
|
||||
if len(geometryJson) > 0 { // geometry.json
|
||||
f, err := os.Create(path.Join(fpath, "geometry.json"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
e := json.NewEncoder(f)
|
||||
e.SetIndent("", "\t")
|
||||
if err := e.Encode(geometryJson); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
{ // skins.json
|
||||
f, err := os.Create(path.Join(fpath, "skins.json"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
e := json.NewEncoder(f)
|
||||
e.SetIndent("", "\t")
|
||||
if err := e.Encode(skinsJson); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
{ // manifest.json
|
||||
manifest := resource.Manifest{
|
||||
FormatVersion: 2,
|
||||
Header: resource.Header{
|
||||
Name: s.Name,
|
||||
Description: serverName + " " + s.Name,
|
||||
UUID: uuid.NewString(),
|
||||
Version: [3]int{1, 0, 0},
|
||||
MinimumGameVersion: [3]int{1, 17, 0},
|
||||
},
|
||||
Modules: []resource.Module{
|
||||
{
|
||||
UUID: uuid.NewString(),
|
||||
Description: s.Name + " Skinpack",
|
||||
Type: "skin_pack",
|
||||
Version: [3]int{1, 0, 0},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if err := utils.WriteManifest(&manifest, fpath); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -1,61 +0,0 @@
|
|||
package skins
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/bedrock-tool/bedrocktool/utils"
|
||||
|
||||
"github.com/google/subcommands"
|
||||
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type SkinProxyCMD struct {
|
||||
server_address string
|
||||
filter string
|
||||
only_with_geometry bool
|
||||
}
|
||||
|
||||
func (*SkinProxyCMD) Name() string { return "skins-proxy" }
|
||||
func (*SkinProxyCMD) Synopsis() string { return "download skins from players on a server with proxy" }
|
||||
|
||||
func (c *SkinProxyCMD) SetFlags(f *flag.FlagSet) {
|
||||
f.StringVar(&c.server_address, "address", "", "remote server address")
|
||||
f.StringVar(&c.filter, "filter", "", "player name filter prefix")
|
||||
f.BoolVar(&c.only_with_geometry, "only-geom", false, "only save skins with geometry")
|
||||
}
|
||||
|
||||
func (c *SkinProxyCMD) Usage() string {
|
||||
return c.Name() + ": " + c.Synopsis() + "\n" + utils.SERVER_ADDRESS_HELP
|
||||
}
|
||||
|
||||
func (c *SkinProxyCMD) Execute(ctx context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
|
||||
address, hostname, err := utils.ServerInput(ctx, c.server_address)
|
||||
if err != nil {
|
||||
logrus.Error(err)
|
||||
return 1
|
||||
}
|
||||
out_path := fmt.Sprintf("skins/%s", hostname)
|
||||
os.MkdirAll(out_path, 0o755)
|
||||
|
||||
proxy := utils.NewProxy(logrus.StandardLogger())
|
||||
proxy.PacketCB = func(pk packet.Packet, proxy *utils.ProxyContext, toServer bool) (packet.Packet, error) {
|
||||
if !toServer {
|
||||
process_packet_skins(proxy.Client, out_path, pk, c.filter, c.only_with_geometry)
|
||||
}
|
||||
return pk, nil
|
||||
}
|
||||
|
||||
if err := proxy.Run(ctx, address); err != nil {
|
||||
logrus.Error(err)
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func init() {
|
||||
utils.RegisterCommand(&SkinProxyCMD{})
|
||||
}
|
|
@ -1,32 +1,24 @@
|
|||
package skins
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/png"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/bedrock-tool/bedrocktool/locale"
|
||||
"github.com/bedrock-tool/bedrocktool/ui/messages"
|
||||
"github.com/bedrock-tool/bedrocktool/utils"
|
||||
|
||||
"github.com/flytam/filenamify"
|
||||
"github.com/google/subcommands"
|
||||
"github.com/sandertv/gophertunnel/minecraft"
|
||||
"github.com/google/uuid"
|
||||
"github.com/sandertv/gophertunnel/minecraft/protocol"
|
||||
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type Skin struct {
|
||||
protocol.Skin
|
||||
}
|
||||
|
||||
type SkinMeta struct {
|
||||
SkinID string
|
||||
PlayFabID string
|
||||
|
@ -39,267 +31,159 @@ type SkinMeta struct {
|
|||
PersonaPieces []protocol.PersonaPiece
|
||||
}
|
||||
|
||||
// WriteGeometry writes the geometry json for the skin to output_path
|
||||
func (skin *Skin) WriteGeometry(output_path string) error {
|
||||
f, err := os.Create(output_path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write Geometry %s: %s", output_path, err)
|
||||
}
|
||||
defer f.Close()
|
||||
io.Copy(f, bytes.NewReader(skin.SkinGeometry))
|
||||
return nil
|
||||
type skinsSession struct {
|
||||
PlayerNameFilter string
|
||||
OnlyIfHasGeometry bool
|
||||
ServerName string
|
||||
Proxy *utils.ProxyContext
|
||||
fpath string
|
||||
|
||||
playerSkinPacks map[uuid.UUID]*SkinPack
|
||||
playerNames map[uuid.UUID]string
|
||||
}
|
||||
|
||||
// WriteCape writes the cape as a png at output_path
|
||||
func (skin *Skin) WriteCape(output_path string) error {
|
||||
f, err := os.Create(output_path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write Cape %s: %s", output_path, err)
|
||||
}
|
||||
defer f.Close()
|
||||
cape_tex := image.NewRGBA(image.Rect(0, 0, int(skin.CapeImageWidth), int(skin.CapeImageHeight)))
|
||||
cape_tex.Pix = skin.CapeData
|
||||
func NewSkinsSession(proxy *utils.ProxyContext, serverName, fpath string) *skinsSession {
|
||||
return &skinsSession{
|
||||
ServerName: serverName,
|
||||
Proxy: proxy,
|
||||
fpath: fpath,
|
||||
|
||||
if err := png.Encode(f, cape_tex); err != nil {
|
||||
return fmt.Errorf("error writing skin: %s", err)
|
||||
playerSkinPacks: make(map[uuid.UUID]*SkinPack),
|
||||
playerNames: make(map[uuid.UUID]string),
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteAnimations writes skin animations to the folder
|
||||
func (skin *Skin) WriteAnimations(output_path string) error {
|
||||
logrus.Warnf("%s has animations (unimplemented)", output_path)
|
||||
return nil
|
||||
func (s *skinsSession) AddPlayerSkin(playerID uuid.UUID, playerName string, skin *Skin) (added bool) {
|
||||
p, ok := s.playerSkinPacks[playerID]
|
||||
if !ok {
|
||||
creating := fmt.Sprintf("Creating Skinpack for %s", playerName)
|
||||
s.Proxy.SendPopup(creating)
|
||||
logrus.Info(creating)
|
||||
p = NewSkinPack(playerName, s.fpath)
|
||||
s.playerSkinPacks[playerID] = p
|
||||
}
|
||||
if p.AddSkin(skin) {
|
||||
if ok {
|
||||
addedStr := fmt.Sprintf("Added a skin to %s", playerName)
|
||||
s.Proxy.SendPopup(addedStr)
|
||||
logrus.Info(addedStr)
|
||||
}
|
||||
added = true
|
||||
}
|
||||
if err := p.Save(path.Join(s.fpath, playerName), s.ServerName); err != nil {
|
||||
logrus.Error(err)
|
||||
}
|
||||
return added
|
||||
}
|
||||
|
||||
// WriteTexture writes the main texture for this skin to a file
|
||||
func (skin *Skin) WriteTexture(output_path string) error {
|
||||
f, err := os.Create(output_path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error writing Texture: %s", err)
|
||||
}
|
||||
defer f.Close()
|
||||
skin_tex := image.NewRGBA(image.Rect(0, 0, int(skin.SkinImageWidth), int(skin.SkinImageHeight)))
|
||||
skin_tex.Pix = skin.SkinData
|
||||
|
||||
if err := png.Encode(f, skin_tex); err != nil {
|
||||
return fmt.Errorf("error writing Texture: %s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (skin *Skin) WriteTint(output_path string) error {
|
||||
f, err := os.Create(output_path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write Tint %s: %s", output_path, err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
err = json.NewEncoder(f).Encode(skin.PieceTintColours)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write Tint %s: %s", output_path, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (skin *Skin) WriteMeta(output_path string) error {
|
||||
f, err := os.Create(output_path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write Tint %s: %s", output_path, err)
|
||||
}
|
||||
defer f.Close()
|
||||
d, err := json.MarshalIndent(SkinMeta{
|
||||
skin.SkinID,
|
||||
skin.PlayFabID,
|
||||
skin.PremiumSkin,
|
||||
skin.PersonaSkin,
|
||||
skin.CapeID,
|
||||
skin.SkinColour,
|
||||
skin.ArmSize,
|
||||
skin.Trusted,
|
||||
skin.PersonaPieces,
|
||||
}, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f.Write(d)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (skin *Skin) Complex() bool {
|
||||
have_geometry, have_cape, have_animations, have_tint := len(skin.SkinGeometry) > 0, len(skin.CapeData) > 0, len(skin.Animations) > 0, len(skin.PieceTintColours) > 0
|
||||
return have_geometry || have_cape || have_animations || have_tint
|
||||
}
|
||||
|
||||
// Write writes all data for this skin to a folder
|
||||
func (skin *Skin) Write(output_path, name string) error {
|
||||
name, _ = filenamify.FilenamifyV2(name)
|
||||
skin_dir := path.Join(output_path, name)
|
||||
|
||||
have_geometry, have_cape, have_animations, have_tint := len(skin.SkinGeometry) > 0, len(skin.CapeData) > 0, len(skin.Animations) > 0, len(skin.PieceTintColours) > 0
|
||||
os.MkdirAll(skin_dir, 0o755)
|
||||
if have_geometry {
|
||||
if err := skin.WriteGeometry(path.Join(skin_dir, "geometry.json")); err != nil {
|
||||
return err
|
||||
func (s *skinsSession) AddSkin(playerName string, playerID uuid.UUID, playerSkin *protocol.Skin) (string, *Skin, bool) {
|
||||
if playerName == "" {
|
||||
playerName = s.playerNames[playerID]
|
||||
if playerName == "" {
|
||||
playerName = playerID.String()
|
||||
}
|
||||
}
|
||||
if have_cape {
|
||||
if err := skin.WriteCape(path.Join(skin_dir, "cape.png")); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if have_animations {
|
||||
if err := skin.WriteAnimations(skin_dir); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if have_tint {
|
||||
if err := skin.WriteTint(path.Join(skin_dir, "tint.json")); err != nil {
|
||||
return err
|
||||
}
|
||||
if !strings.HasPrefix(playerName, s.PlayerNameFilter) {
|
||||
return "", nil, false
|
||||
}
|
||||
s.playerNames[playerID] = playerName
|
||||
|
||||
if err := skin.WriteMeta(path.Join(skin_dir, "metadata.json")); err != nil {
|
||||
return err
|
||||
skin := &Skin{playerSkin}
|
||||
if s.OnlyIfHasGeometry && !skin.HaveGeometry() {
|
||||
return "", nil, false
|
||||
}
|
||||
wasAdded := s.AddPlayerSkin(playerID, playerName, skin)
|
||||
|
||||
return skin.WriteTexture(skin_dir + "/skin.png")
|
||||
return playerName, skin, wasAdded
|
||||
}
|
||||
|
||||
// puts the skin at output_path if the filter matches it
|
||||
// internally converts the struct so it can use the extra methods
|
||||
func write_skin(output_path, name string, skin *protocol.Skin) {
|
||||
logrus.Infof("Writing skin for %s\n", name)
|
||||
_skin := &Skin{*skin}
|
||||
if err := _skin.Write(output_path, name); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error writing skin: %s\n", err)
|
||||
}
|
||||
type skinAdd struct {
|
||||
PlayerName string
|
||||
Skin *protocol.Skin
|
||||
}
|
||||
|
||||
var (
|
||||
skin_players = make(map[string]string)
|
||||
skin_player_counts = make(map[string]int)
|
||||
)
|
||||
|
||||
func popup_skin_saved(conn *minecraft.Conn, name string) {
|
||||
if conn != nil {
|
||||
(&utils.ProxyContext{Client: conn}).SendPopup(fmt.Sprintf("%s Skin was Saved", name))
|
||||
}
|
||||
}
|
||||
|
||||
func skin_meta_get_skinid(path string) string {
|
||||
cont, err := os.ReadFile(fmt.Sprintf("%s/metadata.json", path))
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
var meta SkinMeta
|
||||
if err := json.Unmarshal(cont, &meta); err != nil {
|
||||
return ""
|
||||
}
|
||||
return meta.SkinID
|
||||
}
|
||||
|
||||
func save_player_skin(conn *minecraft.Conn, out_path, player_name string, skin *protocol.Skin) {
|
||||
count := skin_player_counts[player_name]
|
||||
if count > 0 {
|
||||
meta_id := skin_meta_get_skinid(fmt.Sprintf("%s/%s_%d", out_path, player_name, count-1))
|
||||
if meta_id == skin.SkinID {
|
||||
return // skin same as before
|
||||
}
|
||||
}
|
||||
|
||||
skin_player_counts[player_name]++
|
||||
count++
|
||||
write_skin(out_path, fmt.Sprintf("%s_%d", player_name, count), skin)
|
||||
popup_skin_saved(conn, player_name)
|
||||
}
|
||||
|
||||
func process_packet_skins(conn *minecraft.Conn, out_path string, pk packet.Packet, filter string, only_if_geom bool) {
|
||||
switch _pk := pk.(type) {
|
||||
case *packet.PlayerSkin:
|
||||
player_name := skin_players[_pk.UUID.String()]
|
||||
if player_name == "" {
|
||||
player_name = _pk.UUID.String()
|
||||
}
|
||||
if !strings.HasPrefix(player_name, filter) {
|
||||
return
|
||||
}
|
||||
if only_if_geom && len(_pk.Skin.SkinGeometry) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
save_player_skin(conn, out_path, player_name, &_pk.Skin)
|
||||
func (s *skinsSession) ProcessPacket(pk packet.Packet) (out []skinAdd) {
|
||||
switch pk := pk.(type) {
|
||||
case *packet.PlayerList:
|
||||
if _pk.ActionType == 1 { // remove
|
||||
return
|
||||
if pk.ActionType == 1 { // remove
|
||||
return nil
|
||||
}
|
||||
for _, player := range _pk.Entries {
|
||||
player_name := utils.CleanupName(player.Username)
|
||||
if player_name == "" {
|
||||
player_name = player.UUID.String()
|
||||
for _, player := range pk.Entries {
|
||||
playerName, skin, wasAdded := s.AddSkin(utils.CleanupName(player.Username), player.UUID, &player.Skin)
|
||||
if wasAdded {
|
||||
out = append(out, skinAdd{
|
||||
PlayerName: playerName,
|
||||
Skin: skin.Skin,
|
||||
})
|
||||
}
|
||||
if !strings.HasPrefix(player_name, filter) {
|
||||
return
|
||||
}
|
||||
if only_if_geom && len(player.Skin.SkinGeometry) == 0 {
|
||||
return
|
||||
}
|
||||
skin_players[player.UUID.String()] = player_name
|
||||
save_player_skin(conn, out_path, player_name, &player.Skin)
|
||||
}
|
||||
case *packet.AddPlayer:
|
||||
if _, ok := s.playerNames[pk.UUID]; !ok {
|
||||
s.playerNames[pk.UUID] = utils.CleanupName(pk.Username)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
type SkinCMD struct {
|
||||
server_address string
|
||||
filter string
|
||||
ServerAddress string
|
||||
Filter string
|
||||
NoProxy bool
|
||||
}
|
||||
|
||||
func (*SkinCMD) Name() string { return "skins" }
|
||||
func (*SkinCMD) Synopsis() string { return "download all skins from players on a server" }
|
||||
func (*SkinCMD) Synopsis() string { return locale.Loc("skins_synopsis", nil) }
|
||||
|
||||
func (c *SkinCMD) SetFlags(f *flag.FlagSet) {
|
||||
f.StringVar(&c.server_address, "address", "", "remote server address")
|
||||
f.StringVar(&c.filter, "filter", "", "player name filter prefix")
|
||||
f.StringVar(&c.ServerAddress, "address", "", locale.Loc("remote_address", nil))
|
||||
f.StringVar(&c.Filter, "filter", "", locale.Loc("name_prefix", nil))
|
||||
f.BoolVar(&c.NoProxy, "no-proxy", false, "use headless version")
|
||||
}
|
||||
|
||||
func (c *SkinCMD) Usage() string {
|
||||
return c.Name() + ": " + c.Synopsis() + "\n"
|
||||
}
|
||||
|
||||
func (c *SkinCMD) Execute(ctx context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
|
||||
address, hostname, err := utils.ServerInput(ctx, c.server_address)
|
||||
func (c *SkinCMD) Execute(ctx context.Context, ui utils.UI) error {
|
||||
address, hostname, err := utils.ServerInput(ctx, c.ServerAddress)
|
||||
if err != nil {
|
||||
fmt.Fprint(os.Stderr, err)
|
||||
return 1
|
||||
return err
|
||||
}
|
||||
|
||||
serverConn, err := utils.ConnectServer(ctx, address, nil, false, nil)
|
||||
if err != nil {
|
||||
fmt.Fprint(os.Stderr, err)
|
||||
return 1
|
||||
proxy, _ := utils.NewProxy()
|
||||
proxy.WithClient = !c.NoProxy
|
||||
proxy.OnClientConnect = func(proxy *utils.ProxyContext, hasClient bool) {
|
||||
ui.Message(messages.SetUIState, messages.UIStateConnecting)
|
||||
}
|
||||
defer serverConn.Close()
|
||||
|
||||
out_path := fmt.Sprintf("skins/%s", hostname)
|
||||
|
||||
if err := serverConn.DoSpawnContext(ctx); err != nil {
|
||||
fmt.Fprint(os.Stderr, err)
|
||||
return 1
|
||||
}
|
||||
|
||||
logrus.Info("Connected")
|
||||
logrus.Info("Press ctrl+c to exit")
|
||||
|
||||
os.MkdirAll(out_path, 0o755)
|
||||
|
||||
for {
|
||||
pk, err := serverConn.ReadPacket()
|
||||
proxy.ConnectCB = func(proxy *utils.ProxyContext, err error) bool {
|
||||
if err != nil {
|
||||
return 1
|
||||
return false
|
||||
}
|
||||
process_packet_skins(nil, out_path, pk, c.filter, false)
|
||||
ui.Message(messages.SetUIState, messages.UIStateMain)
|
||||
logrus.Info(locale.Loc("ctrl_c_to_exit", nil))
|
||||
return true
|
||||
}
|
||||
|
||||
outPathBase := fmt.Sprintf("skins/%s", hostname)
|
||||
os.MkdirAll(outPathBase, 0o755)
|
||||
|
||||
s := NewSkinsSession(proxy, hostname, outPathBase)
|
||||
|
||||
proxy.PacketCB = func(pk packet.Packet, _ *utils.ProxyContext, toServer bool, _ time.Time) (packet.Packet, error) {
|
||||
if !toServer {
|
||||
for _, s := range s.ProcessPacket(pk) {
|
||||
ui.Message(messages.NewSkin, messages.NewSkinPayload{
|
||||
PlayerName: s.PlayerName,
|
||||
Skin: s.Skin,
|
||||
})
|
||||
}
|
||||
}
|
||||
return pk, nil
|
||||
}
|
||||
|
||||
if proxy.WithClient {
|
||||
ui.Message(messages.SetUIState, messages.UIStateConnect)
|
||||
} else {
|
||||
ui.Message(messages.SetUIState, messages.UIStateConnecting)
|
||||
}
|
||||
err = proxy.Run(ctx, address)
|
||||
return err
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
|
|
@ -1,45 +1,38 @@
|
|||
// Package subcommands ...
|
||||
package subcommands
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
|
||||
"github.com/bedrock-tool/bedrocktool/locale"
|
||||
"github.com/bedrock-tool/bedrocktool/utils"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/google/subcommands"
|
||||
)
|
||||
|
||||
type UpdateCMD struct{}
|
||||
|
||||
func (*UpdateCMD) Name() string { return "update" }
|
||||
func (*UpdateCMD) Synopsis() string { return "self updates to latest version" }
|
||||
|
||||
func (*UpdateCMD) Name() string { return "update" }
|
||||
func (*UpdateCMD) Synopsis() string { return locale.Loc("update_synopsis", nil) }
|
||||
func (c *UpdateCMD) SetFlags(f *flag.FlagSet) {}
|
||||
|
||||
func (c *UpdateCMD) Usage() string {
|
||||
return c.Name() + ": " + c.Synopsis() + "\n"
|
||||
}
|
||||
|
||||
func (c *UpdateCMD) Execute(ctx context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
|
||||
func (c *UpdateCMD) Execute(ctx context.Context, ui utils.UI) error {
|
||||
newVersion, err := utils.Updater.UpdateAvailable()
|
||||
if err != nil {
|
||||
logrus.Error(err)
|
||||
return 1
|
||||
return err
|
||||
}
|
||||
if newVersion == "" {
|
||||
logrus.Info("No Updates available.")
|
||||
return 0
|
||||
logrus.Info(locale.Loc("no_update", nil))
|
||||
return nil
|
||||
}
|
||||
logrus.Infof("Updating to %s", newVersion)
|
||||
logrus.Infof(locale.Loc("updating", locale.Strmap{"Version": newVersion}))
|
||||
|
||||
if err := utils.Updater.Update(); err != nil {
|
||||
logrus.Error(err)
|
||||
return 1
|
||||
return err
|
||||
}
|
||||
|
||||
logrus.Infof("Updated!")
|
||||
return 0
|
||||
logrus.Infof(locale.Loc("updated", nil))
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
|
|
@ -0,0 +1,126 @@
|
|||
package world
|
||||
|
||||
import (
|
||||
"github.com/bedrock-tool/bedrocktool/locale"
|
||||
"github.com/df-mc/dragonfly/server/world"
|
||||
"github.com/df-mc/dragonfly/server/world/chunk"
|
||||
"github.com/sandertv/gophertunnel/minecraft/protocol"
|
||||
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func (w *WorldState) processChangeDimension(pk *packet.ChangeDimension) {
|
||||
if len(w.chunks) > 0 {
|
||||
w.SaveAndReset()
|
||||
} else {
|
||||
logrus.Info(locale.Loc("not_saving_empty", nil))
|
||||
w.Reset()
|
||||
}
|
||||
dimensionID := pk.Dimension
|
||||
if w.ispre118 {
|
||||
dimensionID += 10
|
||||
}
|
||||
w.Dim = dimensionIDMap[uint8(dimensionID)]
|
||||
}
|
||||
|
||||
func (w *WorldState) processLevelChunk(pk *packet.LevelChunk) {
|
||||
_, exists := w.chunks[pk.Position]
|
||||
if exists {
|
||||
return
|
||||
}
|
||||
|
||||
// ignore empty chunks THANKS WEIRD SERVER SOFTWARE DEVS
|
||||
if len(pk.RawPayload) == 0 {
|
||||
logrus.Info(locale.Loc("empty_chunk", nil))
|
||||
return
|
||||
}
|
||||
ch, blockNBTs, err := chunk.NetworkDecode(world.AirRID(), pk.RawPayload, int(pk.SubChunkCount), w.Dim.Range(), w.ispre118, w.bp.HasBlocks())
|
||||
if err != nil {
|
||||
logrus.Error(err)
|
||||
return
|
||||
}
|
||||
if blockNBTs != nil {
|
||||
w.blockNBT[protocol.SubChunkPos{
|
||||
pk.Position.X(), 0, pk.Position.Z(),
|
||||
}] = blockNBTs
|
||||
}
|
||||
|
||||
// check if chunk is empty
|
||||
empty := true
|
||||
for _, sub := range ch.Sub() {
|
||||
if !sub.Empty() {
|
||||
empty = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
w.chunks[pk.Position] = ch
|
||||
|
||||
if pk.SubChunkRequestMode == protocol.SubChunkRequestModeLegacy {
|
||||
if !empty {
|
||||
w.mapUI.SetChunk(pk.Position, ch)
|
||||
}
|
||||
} else {
|
||||
// request all the subchunks
|
||||
max := w.Dim.Range().Height() / 16
|
||||
if pk.SubChunkRequestMode == protocol.SubChunkRequestModeLimited {
|
||||
max = int(pk.HighestSubChunk)
|
||||
}
|
||||
|
||||
w.proxy.Server.WritePacket(&packet.SubChunkRequest{
|
||||
Dimension: int32(w.Dim.EncodeDimension()),
|
||||
Position: protocol.SubChunkPos{
|
||||
pk.Position.X(), 0, pk.Position.Z(),
|
||||
},
|
||||
Offsets: offsetTable[:max],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (w *WorldState) processSubChunk(pk *packet.SubChunk) {
|
||||
posToRedraw := make(map[protocol.ChunkPos]bool)
|
||||
|
||||
for _, sub := range pk.SubChunkEntries {
|
||||
var (
|
||||
absX = pk.Position[0] + int32(sub.Offset[0])
|
||||
absY = pk.Position[1] + int32(sub.Offset[1])
|
||||
absZ = pk.Position[2] + int32(sub.Offset[2])
|
||||
subPos = protocol.SubChunkPos{absX, absY, absZ}
|
||||
pos = protocol.ChunkPos{absX, absZ}
|
||||
)
|
||||
ch, ok := w.chunks[pos]
|
||||
if !ok {
|
||||
logrus.Error(locale.Loc("subchunk_before_chunk", nil))
|
||||
continue
|
||||
}
|
||||
blockNBT, err := ch.ApplySubChunkEntry(uint8(absY), &sub)
|
||||
if err != nil {
|
||||
logrus.Error(err)
|
||||
}
|
||||
if blockNBT != nil {
|
||||
w.blockNBT[subPos] = blockNBT
|
||||
}
|
||||
|
||||
posToRedraw[pos] = true
|
||||
}
|
||||
|
||||
// redraw the chunks
|
||||
for pos := range posToRedraw {
|
||||
w.mapUI.SetChunk(pos, w.chunks[pos])
|
||||
}
|
||||
w.mapUI.SchedRedraw()
|
||||
}
|
||||
|
||||
func (w *WorldState) ProcessChunkPackets(pk packet.Packet) packet.Packet {
|
||||
switch pk := pk.(type) {
|
||||
case *packet.ChangeDimension:
|
||||
w.processChangeDimension(pk)
|
||||
case *packet.LevelChunk:
|
||||
w.processLevelChunk(pk)
|
||||
|
||||
w.proxy.SendPopup(locale.Locm("popup_chunk_count", locale.Strmap{"Count": len(w.chunks), "Name": w.WorldName}, len(w.chunks)))
|
||||
case *packet.SubChunk:
|
||||
w.processSubChunk(pk)
|
||||
}
|
||||
return pk
|
||||
}
|
|
@ -20,6 +20,9 @@ func blockColorAt(c *chunk.Chunk, x uint8, y int16, z uint8) (blockColor color.R
|
|||
} else {
|
||||
b, found := world.BlockByRuntimeID(rid)
|
||||
if found {
|
||||
if d, ok := b.(block.LightDiffuser); ok && d.LightDiffusionLevel() == 0 && y > int16(c.Range().Min()) {
|
||||
return blockColorAt(c, x, y-1, z)
|
||||
}
|
||||
if _, ok := b.(block.Water); ok {
|
||||
y2 := c.HeightMap().At(x, z)
|
||||
depth := y - y2
|
||||
|
@ -50,18 +53,18 @@ func blockColorAt(c *chunk.Chunk, x uint8, y int16, z uint8) (blockColor color.R
|
|||
|
||||
func chunkGetColorAt(c *chunk.Chunk, x uint8, y int16, z uint8) color.RGBA {
|
||||
p := cube.Pos{int(x), int(y), int(z)}
|
||||
have_up := false
|
||||
haveUp := false
|
||||
p.Side(cube.FaceUp).Neighbours(func(neighbour cube.Pos) {
|
||||
if neighbour.X() < 0 || neighbour.X() >= 16 || neighbour.Z() < 0 || neighbour.Z() >= 16 || neighbour.Y() > c.Range().Max() {
|
||||
return
|
||||
}
|
||||
if !have_up {
|
||||
block_rid := c.Block(uint8(neighbour[0]), int16(neighbour[1]), uint8(neighbour[2]), 0)
|
||||
if block_rid > 0 {
|
||||
b, found := world.BlockByRuntimeID(block_rid)
|
||||
if !haveUp {
|
||||
blockRid := c.Block(uint8(neighbour[0]), int16(neighbour[1]), uint8(neighbour[2]), 0)
|
||||
if blockRid > 0 {
|
||||
b, found := world.BlockByRuntimeID(blockRid)
|
||||
if found {
|
||||
if _, ok := b.(block.Air); !ok {
|
||||
have_up = true
|
||||
haveUp = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -70,7 +73,7 @@ func chunkGetColorAt(c *chunk.Chunk, x uint8, y int16, z uint8) color.RGBA {
|
|||
|
||||
col := blockColorAt(c, x, y, z)
|
||||
|
||||
if have_up {
|
||||
if haveUp {
|
||||
if col.R > 10 {
|
||||
col.R -= 10
|
||||
}
|
||||
|
|
|
@ -1,18 +1,19 @@
|
|||
package world
|
||||
package world_test
|
||||
|
||||
import (
|
||||
"image/png"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/bedrock-tool/bedrocktool/subcommands/world"
|
||||
"github.com/df-mc/dragonfly/server/block/cube"
|
||||
"github.com/df-mc/dragonfly/server/world/chunk"
|
||||
)
|
||||
|
||||
func Test(t *testing.T) {
|
||||
data, _ := os.ReadFile("chunk.bin")
|
||||
ch, _, _ := chunk.NetworkDecode(33, data, 6, cube.Range{0, 255}, true)
|
||||
i := Chunk2Img(ch)
|
||||
ch, _, _ := chunk.NetworkDecode(33, data, 6, cube.Range{0, 255}, true, false)
|
||||
i := world.Chunk2Img(ch)
|
||||
f, _ := os.Create("chunk.png")
|
||||
png.Encode(f, i)
|
||||
f.Close()
|
||||
|
@ -21,7 +22,7 @@ func Test(t *testing.T) {
|
|||
func Benchmark_chunk_decode(b *testing.B) {
|
||||
data, _ := os.ReadFile("chunk.bin")
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, _, err := chunk.NetworkDecode(33, data, 6, cube.Range{0, 255}, true)
|
||||
_, _, err := chunk.NetworkDecode(33, data, 6, cube.Range{0, 255}, true, false)
|
||||
if err != nil {
|
||||
b.Error(err)
|
||||
}
|
||||
|
@ -30,9 +31,9 @@ func Benchmark_chunk_decode(b *testing.B) {
|
|||
|
||||
func Benchmark_render_chunk(b *testing.B) {
|
||||
data, _ := os.ReadFile("chunk.bin")
|
||||
ch, _, _ := chunk.NetworkDecode(33, data, 6, cube.Range{0, 255}, true)
|
||||
ch, _, _ := chunk.NetworkDecode(33, data, 6, cube.Range{0, 255}, true, false)
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
Chunk2Img(ch)
|
||||
world.Chunk2Img(ch)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,185 @@
|
|||
package world
|
||||
|
||||
import (
|
||||
"github.com/bedrock-tool/bedrocktool/utils/behaviourpack"
|
||||
"github.com/df-mc/dragonfly/server/block/cube"
|
||||
"github.com/df-mc/dragonfly/server/world"
|
||||
"github.com/go-gl/mathgl/mgl32"
|
||||
"github.com/sandertv/gophertunnel/minecraft/protocol"
|
||||
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
|
||||
)
|
||||
|
||||
type entityState struct {
|
||||
RuntimeID uint64
|
||||
UniqueID int64
|
||||
EntityType string
|
||||
|
||||
Position mgl32.Vec3
|
||||
Pitch, Yaw float32
|
||||
HeadYaw, BodyYaw float32
|
||||
Velocity mgl32.Vec3
|
||||
|
||||
Metadata protocol.EntityMetadata
|
||||
}
|
||||
|
||||
type serverEntityType struct {
|
||||
world.SaveableEntityType
|
||||
Encoded string
|
||||
NBT map[string]any
|
||||
}
|
||||
|
||||
func (t serverEntityType) EncodeEntity() string {
|
||||
return t.Encoded
|
||||
}
|
||||
|
||||
func (t serverEntityType) BBox(e world.Entity) cube.BBox {
|
||||
return cube.Box(-0.5, 0, -0.5, 0.5, 1, 0.5)
|
||||
}
|
||||
|
||||
func (t serverEntityType) DecodeNBT(m map[string]any) world.Entity {
|
||||
return nil // not implemented, and never should
|
||||
}
|
||||
|
||||
func (t serverEntityType) EncodeNBT(e world.Entity) map[string]any {
|
||||
return t.NBT
|
||||
}
|
||||
|
||||
func (t serverEntityType) UniqueID() int64 {
|
||||
return t.NBT["UniqueID"].(int64)
|
||||
}
|
||||
|
||||
type serverEntity struct {
|
||||
world.Entity
|
||||
EntityType serverEntityType
|
||||
}
|
||||
|
||||
func (e serverEntity) Type() world.EntityType {
|
||||
return e.EntityType
|
||||
}
|
||||
|
||||
func (w *WorldState) processAddActor(pk *packet.AddActor) {
|
||||
e, ok := w.entities[pk.EntityRuntimeID]
|
||||
if !ok {
|
||||
e = &entityState{
|
||||
RuntimeID: pk.EntityRuntimeID,
|
||||
UniqueID: pk.EntityUniqueID,
|
||||
EntityType: pk.EntityType,
|
||||
Metadata: make(map[uint32]any),
|
||||
}
|
||||
w.entities[pk.EntityRuntimeID] = e
|
||||
|
||||
w.bp.AddEntity(behaviourpack.EntityIn{
|
||||
Identifier: pk.EntityType,
|
||||
Attr: pk.Attributes,
|
||||
Meta: pk.EntityMetadata,
|
||||
})
|
||||
}
|
||||
|
||||
e.Position = pk.Position
|
||||
e.Pitch = pk.Pitch
|
||||
e.Yaw = pk.Yaw
|
||||
e.BodyYaw = pk.BodyYaw
|
||||
e.HeadYaw = pk.HeadYaw
|
||||
e.Velocity = pk.Velocity
|
||||
|
||||
for k, v := range pk.EntityMetadata {
|
||||
e.Metadata[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
func entityMetadataToNBT(metadata protocol.EntityMetadata, nbt map[string]any) {
|
||||
if variant, ok := metadata[protocol.EntityDataKeyVariant]; ok {
|
||||
nbt["Variant"] = variant
|
||||
}
|
||||
if markVariant, ok := metadata[protocol.EntityDataKeyMarkVariant]; ok {
|
||||
nbt["MarkVariant"] = markVariant
|
||||
}
|
||||
if color, ok := metadata[protocol.EntityDataKeyColorIndex]; ok {
|
||||
nbt["Color"] = color
|
||||
}
|
||||
if color2, ok := metadata[protocol.EntityDataKeyColorTwoIndex]; ok {
|
||||
nbt["Color2"] = color2
|
||||
}
|
||||
|
||||
if name, ok := metadata[protocol.EntityDataKeyName]; ok {
|
||||
nbt["CustomName"] = name
|
||||
}
|
||||
if ShowNameTag, ok := metadata[protocol.EntityDataKeyAlwaysShowNameTag]; ok {
|
||||
if ShowNameTag != 0 {
|
||||
nbt["CustomNameVisible"] = true
|
||||
} else {
|
||||
nbt["CustomNameVisible"] = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func vec3float32(x mgl32.Vec3) []float32 {
|
||||
return []float32{float32(x[0]), float32(x[1]), float32(x[2])}
|
||||
}
|
||||
|
||||
func (s *entityState) ToServerEntity() serverEntity {
|
||||
e := serverEntity{
|
||||
EntityType: serverEntityType{
|
||||
Encoded: s.EntityType,
|
||||
NBT: map[string]any{
|
||||
"Pos": vec3float32(s.Position),
|
||||
"Rotation": []float32{s.Yaw, s.Pitch},
|
||||
"Motion": vec3float32(s.Velocity),
|
||||
"UniqueID": int64(s.UniqueID),
|
||||
},
|
||||
},
|
||||
}
|
||||
entityMetadataToNBT(s.Metadata, e.EntityType.NBT)
|
||||
return e
|
||||
}
|
||||
|
||||
func (w *WorldState) ProcessEntityPackets(pk packet.Packet) packet.Packet {
|
||||
switch pk := pk.(type) {
|
||||
case *packet.AddActor:
|
||||
w.processAddActor(pk)
|
||||
case *packet.RemoveActor:
|
||||
delete(w.entities, uint64(pk.EntityUniqueID))
|
||||
case *packet.SetActorData:
|
||||
e, ok := w.entities[pk.EntityRuntimeID]
|
||||
if ok {
|
||||
for k, v := range pk.EntityMetadata {
|
||||
e.Metadata[k] = v
|
||||
}
|
||||
}
|
||||
case *packet.SetActorMotion:
|
||||
e, ok := w.entities[pk.EntityRuntimeID]
|
||||
if ok {
|
||||
e.Velocity = pk.Velocity
|
||||
}
|
||||
case *packet.MoveActorDelta:
|
||||
e, ok := w.entities[pk.EntityRuntimeID]
|
||||
if ok {
|
||||
if pk.Flags&packet.MoveActorDeltaFlagHasX != 0 {
|
||||
e.Position[0] = pk.Position[0]
|
||||
}
|
||||
if pk.Flags&packet.MoveActorDeltaFlagHasY != 0 {
|
||||
e.Position[1] = pk.Position[1]
|
||||
}
|
||||
if pk.Flags&packet.MoveActorDeltaFlagHasZ != 0 {
|
||||
e.Position[2] = pk.Position[2]
|
||||
}
|
||||
if pk.Flags&packet.MoveActorDeltaFlagHasRotX != 0 {
|
||||
e.Pitch = pk.Rotation.X()
|
||||
}
|
||||
if pk.Flags&packet.MoveActorDeltaFlagHasRotY != 0 {
|
||||
e.Yaw = pk.Rotation.Y()
|
||||
}
|
||||
//if pk.Flags&packet.MoveActorDeltaFlagHasRotZ != 0 {
|
||||
// no roll
|
||||
//}
|
||||
}
|
||||
case *packet.MoveActorAbsolute:
|
||||
e, ok := w.entities[pk.EntityRuntimeID]
|
||||
if ok {
|
||||
e.Position = pk.Position
|
||||
e.Pitch = pk.Rotation.X()
|
||||
e.Yaw = pk.Rotation.Y()
|
||||
}
|
||||
}
|
||||
return pk
|
||||
}
|
|
@ -0,0 +1,167 @@
|
|||
package world
|
||||
|
||||
import (
|
||||
"github.com/bedrock-tool/bedrocktool/locale"
|
||||
"github.com/bedrock-tool/bedrocktool/utils/nbtconv"
|
||||
"github.com/df-mc/dragonfly/server/block"
|
||||
"github.com/df-mc/dragonfly/server/item"
|
||||
"github.com/df-mc/dragonfly/server/item/inventory"
|
||||
"github.com/df-mc/dragonfly/server/world"
|
||||
"github.com/sandertv/gophertunnel/minecraft/protocol"
|
||||
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type itemContainer struct {
|
||||
OpenPacket *packet.ContainerOpen
|
||||
Content *packet.InventoryContent
|
||||
}
|
||||
|
||||
func (w *WorldState) processItemPacketsServer(pk packet.Packet) packet.Packet {
|
||||
switch pk := pk.(type) {
|
||||
case *packet.ContainerOpen:
|
||||
if w.experimentInventory {
|
||||
// add to open containers
|
||||
existing, ok := w.openItemContainers[pk.WindowID]
|
||||
if !ok {
|
||||
existing = &itemContainer{}
|
||||
}
|
||||
w.openItemContainers[pk.WindowID] = &itemContainer{
|
||||
OpenPacket: pk,
|
||||
Content: existing.Content,
|
||||
}
|
||||
}
|
||||
case *packet.InventoryContent:
|
||||
if w.experimentInventory {
|
||||
// save content
|
||||
existing, ok := w.openItemContainers[byte(pk.WindowID)]
|
||||
if !ok {
|
||||
if pk.WindowID == 0x0 { // inventory
|
||||
w.openItemContainers[byte(pk.WindowID)] = &itemContainer{
|
||||
Content: pk,
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
existing.Content = pk
|
||||
}
|
||||
case *packet.ContainerClose:
|
||||
if w.experimentInventory {
|
||||
switch pk.WindowID {
|
||||
case protocol.WindowIDArmour: // todo handle
|
||||
case protocol.WindowIDOffHand: // todo handle
|
||||
case protocol.WindowIDUI:
|
||||
case protocol.WindowIDInventory: // todo handle
|
||||
default:
|
||||
// find container info
|
||||
existing, ok := w.openItemContainers[byte(pk.WindowID)]
|
||||
if !ok {
|
||||
logrus.Warn(locale.Loc("warn_window_closed_not_open", nil))
|
||||
break
|
||||
}
|
||||
|
||||
if existing.Content == nil {
|
||||
break
|
||||
}
|
||||
|
||||
pos := existing.OpenPacket.ContainerPosition
|
||||
cp := protocol.SubChunkPos{pos.X() << 4, pos.Z() << 4}
|
||||
|
||||
// create inventory
|
||||
inv := inventory.New(len(existing.Content.Content), nil)
|
||||
for i, c := range existing.Content.Content {
|
||||
item := stackToItem(c.Stack)
|
||||
inv.SetItem(i, item)
|
||||
}
|
||||
|
||||
// put into subchunk
|
||||
nbts := w.blockNBT[cp]
|
||||
for i, v := range nbts {
|
||||
NBTPos := protocol.BlockPos{v["x"].(int32), v["y"].(int32), v["z"].(int32)}
|
||||
if NBTPos == pos {
|
||||
w.blockNBT[cp][i]["Items"] = nbtconv.InvToNBT(inv)
|
||||
}
|
||||
}
|
||||
|
||||
w.proxy.SendMessage(locale.Loc("saved_block_inv", nil))
|
||||
|
||||
// remove it again
|
||||
delete(w.openItemContainers, byte(pk.WindowID))
|
||||
}
|
||||
}
|
||||
case *packet.ItemComponent:
|
||||
w.bp.ApplyComponentEntries(pk.Items)
|
||||
}
|
||||
return pk
|
||||
}
|
||||
|
||||
func (w *WorldState) processItemPacketsClient(pk packet.Packet, forward *bool) packet.Packet {
|
||||
switch pk := pk.(type) {
|
||||
case *packet.ItemStackRequest:
|
||||
var requests []protocol.ItemStackRequest
|
||||
for _, isr := range pk.Requests {
|
||||
for _, sra := range isr.Actions {
|
||||
if sra, ok := sra.(*protocol.TakeStackRequestAction); ok {
|
||||
if sra.Source.StackNetworkID == MapItemPacket.Content[0].StackNetworkID {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if sra, ok := sra.(*protocol.DropStackRequestAction); ok {
|
||||
if sra.Source.StackNetworkID == MapItemPacket.Content[0].StackNetworkID {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if sra, ok := sra.(*protocol.DestroyStackRequestAction); ok {
|
||||
if sra.Source.StackNetworkID == MapItemPacket.Content[0].StackNetworkID {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if sra, ok := sra.(*protocol.PlaceInContainerStackRequestAction); ok {
|
||||
if sra.Source.StackNetworkID == MapItemPacket.Content[0].StackNetworkID {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if sra, ok := sra.(*protocol.TakeOutContainerStackRequestAction); ok {
|
||||
if sra.Source.StackNetworkID == MapItemPacket.Content[0].StackNetworkID {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if sra, ok := sra.(*protocol.DestroyStackRequestAction); ok {
|
||||
if sra.Source.StackNetworkID == MapItemPacket.Content[0].StackNetworkID {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
requests = append(requests, isr)
|
||||
}
|
||||
pk.Requests = requests
|
||||
case *packet.MobEquipment:
|
||||
if pk.NewItem.Stack.NBTData["map_uuid"] == int64(ViewMapID) {
|
||||
*forward = false
|
||||
}
|
||||
}
|
||||
return pk
|
||||
}
|
||||
|
||||
// stackToItem converts a network ItemStack representation back to an item.Stack.
|
||||
func stackToItem(it protocol.ItemStack) item.Stack {
|
||||
t, ok := world.ItemByRuntimeID(it.NetworkID, int16(it.MetadataValue))
|
||||
if !ok {
|
||||
t = block.Air{}
|
||||
}
|
||||
if it.BlockRuntimeID > 0 {
|
||||
// It shouldn't matter if it (for whatever reason) wasn't able to get the block runtime ID,
|
||||
// since on the next line, we assert that the block is an item. If it didn't succeed, it'll
|
||||
// return air anyway.
|
||||
b, _ := world.BlockByRuntimeID(uint32(it.BlockRuntimeID))
|
||||
if t, ok = b.(world.Item); !ok {
|
||||
t = block.Air{}
|
||||
}
|
||||
}
|
||||
//noinspection SpellCheckingInspection
|
||||
if nbter, ok := t.(world.NBTer); ok && len(it.NBTData) != 0 {
|
||||
t = nbter.DecodeNBT(it.NBTData).(world.Item)
|
||||
}
|
||||
s := item.NewStack(t, int(it.Count))
|
||||
return nbtconv.ReadItem(it.NBTData, &s)
|
||||
}
|
|
@ -1,13 +1,14 @@
|
|||
package world
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"image"
|
||||
"image/draw"
|
||||
"math"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/bedrock-tool/bedrocktool/locale"
|
||||
"github.com/bedrock-tool/bedrocktool/ui/messages"
|
||||
"github.com/bedrock-tool/bedrocktool/utils"
|
||||
"golang.design/x/lockfree"
|
||||
|
||||
|
@ -15,47 +16,49 @@ import (
|
|||
"github.com/sandertv/gophertunnel/minecraft/protocol"
|
||||
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/image/bmp"
|
||||
)
|
||||
|
||||
const VIEW_MAP_ID = 0x424242
|
||||
const ViewMapID = 0x424242
|
||||
|
||||
// packet to tell the client that it has a map with id 0x424242 in the offhand
|
||||
var MAP_ITEM_PACKET packet.InventoryContent = packet.InventoryContent{
|
||||
// MapItemPacket tells the client that it has a map with id 0x424242 in the offhand
|
||||
var MapItemPacket packet.InventoryContent = packet.InventoryContent{
|
||||
WindowID: 119,
|
||||
Content: []protocol.ItemInstance{
|
||||
{
|
||||
StackNetworkID: 1,
|
||||
StackNetworkID: 1, // random if auth inv
|
||||
Stack: protocol.ItemStack{
|
||||
ItemType: protocol.ItemType{
|
||||
NetworkID: 420,
|
||||
NetworkID: 420, // overwritten in onconnect
|
||||
MetadataValue: 0,
|
||||
},
|
||||
BlockRuntimeID: 0,
|
||||
Count: 1,
|
||||
NBTData: map[string]interface{}{
|
||||
"map_uuid": int64(VIEW_MAP_ID),
|
||||
"map_name_index": int64(1),
|
||||
"map_uuid": int64(ViewMapID),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func (m *MapUI) get_bounds() (min, max protocol.ChunkPos) {
|
||||
func (m *MapUI) GetBounds() (min, max protocol.ChunkPos) {
|
||||
// get the chunk coord bounds
|
||||
i := 0
|
||||
for _ch := range m.renderedChunks {
|
||||
if _ch.X() < min.X() {
|
||||
if _ch.X() < min.X() || i == 0 {
|
||||
min[0] = _ch.X()
|
||||
}
|
||||
if _ch.Z() < min.Z() {
|
||||
if _ch.Z() < min.Z() || i == 0 {
|
||||
min[1] = _ch.Z()
|
||||
}
|
||||
if _ch.X() > max.X() {
|
||||
if _ch.X() > max.X() || i == 0 {
|
||||
max[0] = _ch.X()
|
||||
}
|
||||
if _ch.Z() > max.Z() {
|
||||
if _ch.Z() > max.Z() || i == 0 {
|
||||
max[1] = _ch.Z()
|
||||
}
|
||||
i++
|
||||
}
|
||||
return
|
||||
}
|
||||
|
@ -71,6 +74,8 @@ type MapUI struct {
|
|||
renderQueue *lockfree.Queue
|
||||
renderedChunks map[protocol.ChunkPos]*image.RGBA // prerendered chunks
|
||||
needRedraw bool // when the map has updated this is true
|
||||
showOnGui bool
|
||||
l sync.RWMutex
|
||||
|
||||
ticker *time.Ticker
|
||||
w *WorldState
|
||||
|
@ -89,6 +94,27 @@ func NewMapUI(w *WorldState) *MapUI {
|
|||
}
|
||||
|
||||
func (m *MapUI) Start() {
|
||||
r := m.w.gui.Message("can_show_images", nil)
|
||||
if r.Ok {
|
||||
m.showOnGui = true
|
||||
}
|
||||
|
||||
// init map
|
||||
if m.w.proxy.Client != nil {
|
||||
if err := m.w.proxy.Client.WritePacket(&packet.ClientBoundMapItemData{
|
||||
MapID: ViewMapID,
|
||||
Scale: 4,
|
||||
MapsIncludedIn: []int64{ViewMapID},
|
||||
Width: 0,
|
||||
Height: 0,
|
||||
Pixels: nil,
|
||||
UpdateFlags: packet.MapUpdateFlagInitialisation,
|
||||
}); err != nil {
|
||||
logrus.Error(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
m.ticker = time.NewTicker(33 * time.Millisecond)
|
||||
go func() {
|
||||
for range m.ticker.C {
|
||||
|
@ -98,11 +124,12 @@ func (m *MapUI) Start() {
|
|||
|
||||
if m.w.proxy.Client != nil {
|
||||
if err := m.w.proxy.Client.WritePacket(&packet.ClientBoundMapItemData{
|
||||
MapID: VIEW_MAP_ID,
|
||||
MapID: ViewMapID,
|
||||
Scale: 4,
|
||||
Width: 128,
|
||||
Height: 128,
|
||||
Pixels: utils.Img2rgba(m.img),
|
||||
UpdateFlags: 2,
|
||||
UpdateFlags: packet.MapUpdateFlagTexture,
|
||||
}); err != nil {
|
||||
logrus.Error(err)
|
||||
return
|
||||
|
@ -111,6 +138,21 @@ func (m *MapUI) Start() {
|
|||
}
|
||||
}
|
||||
}()
|
||||
go func() { // send map item
|
||||
t := time.NewTicker(1 * time.Second)
|
||||
for range t.C {
|
||||
if m.w.ctx.Err() != nil {
|
||||
return
|
||||
}
|
||||
if m.w.proxy.Client != nil {
|
||||
err := m.w.proxy.Client.WritePacket(&MapItemPacket)
|
||||
if err != nil {
|
||||
logrus.Error(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (m *MapUI) Stop() {
|
||||
|
@ -121,7 +163,9 @@ func (m *MapUI) Stop() {
|
|||
|
||||
// Reset resets the map to inital state
|
||||
func (m *MapUI) Reset() {
|
||||
m.l.Lock()
|
||||
m.renderedChunks = make(map[protocol.ChunkPos]*image.RGBA)
|
||||
m.l.Unlock()
|
||||
m.SchedRedraw()
|
||||
}
|
||||
|
||||
|
@ -139,23 +183,10 @@ func (m *MapUI) SchedRedraw() {
|
|||
m.needRedraw = true
|
||||
}
|
||||
|
||||
// draw_img_scaled_pos draws src onto dst at bottom_left, scaled to size
|
||||
func draw_img_scaled_pos(dst *image.RGBA, src *image.RGBA, bottom_left image.Point, size_scaled int) {
|
||||
sbx := src.Bounds().Dx()
|
||||
ratio := int(float64(sbx) / float64(size_scaled))
|
||||
|
||||
for x_out := bottom_left.X; x_out < bottom_left.X+size_scaled; x_out++ {
|
||||
for y_out := bottom_left.Y; y_out < bottom_left.Y+size_scaled; y_out++ {
|
||||
x_in := (x_out - bottom_left.X) * ratio
|
||||
y_in := (y_out - bottom_left.Y) * ratio
|
||||
c := src.At(x_in, y_in)
|
||||
dst.Set(x_out, y_out, c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// draw chunk images to the map image
|
||||
// Redraw draws chunk images to the map image
|
||||
func (m *MapUI) Redraw() {
|
||||
m.l.Lock()
|
||||
updatedChunks := []protocol.ChunkPos{}
|
||||
for {
|
||||
r, ok := m.renderQueue.Dequeue().(*RenderElem)
|
||||
if !ok {
|
||||
|
@ -164,9 +195,11 @@ func (m *MapUI) Redraw() {
|
|||
if r.ch != nil {
|
||||
m.renderedChunks[r.pos] = Chunk2Img(r.ch)
|
||||
} else {
|
||||
m.renderedChunks[r.pos] = black_16x16
|
||||
m.renderedChunks[r.pos] = black16x16
|
||||
}
|
||||
updatedChunks = append(updatedChunks, r.pos)
|
||||
}
|
||||
m.l.Unlock()
|
||||
|
||||
middle := protocol.ChunkPos{
|
||||
int32(m.w.PlayerPos.Position.X()),
|
||||
|
@ -174,60 +207,62 @@ func (m *MapUI) Redraw() {
|
|||
}
|
||||
|
||||
// total_width := 32 * math.Ceil(float64(chunks_x)/32)
|
||||
chunks_per_line := float64(128 / m.zoomLevel)
|
||||
px_per_block := 128 / chunks_per_line / 16 // how many pixels per block
|
||||
sz_chunk := int(math.Floor(px_per_block * 16))
|
||||
chunksPerLine := float64(128 / m.zoomLevel)
|
||||
pxPerBlock := 128 / chunksPerLine / 16 // how many pixels per block
|
||||
pxSizeChunk := int(math.Floor(pxPerBlock * 16))
|
||||
|
||||
for i := 0; i < len(m.img.Pix); i++ { // clear canvas
|
||||
m.img.Pix[i] = 0
|
||||
}
|
||||
|
||||
m.l.RLock()
|
||||
for _ch := range m.renderedChunks {
|
||||
relative_middle_x := float64(_ch.X()*16 - middle.X())
|
||||
relative_middle_z := float64(_ch.Z()*16 - middle.Z())
|
||||
px_pos := image.Point{ // bottom left corner of the chunk on the map
|
||||
X: int(math.Floor(relative_middle_x*px_per_block)) + 64,
|
||||
Y: int(math.Floor(relative_middle_z*px_per_block)) + 64,
|
||||
relativeMiddleX := float64(_ch.X()*16 - middle.X())
|
||||
relativeMiddleZ := float64(_ch.Z()*16 - middle.Z())
|
||||
px := image.Point{ // bottom left corner of the chunk on the map
|
||||
X: int(math.Floor(relativeMiddleX*pxPerBlock)) + 64,
|
||||
Y: int(math.Floor(relativeMiddleZ*pxPerBlock)) + 64,
|
||||
}
|
||||
|
||||
if !m.img.Rect.Intersect(image.Rect(px_pos.X, px_pos.Y, px_pos.X+sz_chunk, px_pos.Y+sz_chunk)).Empty() {
|
||||
draw_img_scaled_pos(m.img, m.renderedChunks[_ch], px_pos, sz_chunk)
|
||||
if !m.img.Rect.Intersect(image.Rect(px.X, px.Y, px.X+pxSizeChunk, px.Y+pxSizeChunk)).Empty() {
|
||||
utils.DrawImgScaledPos(m.img, m.renderedChunks[_ch], px, pxSizeChunk)
|
||||
}
|
||||
}
|
||||
|
||||
draw_full := false
|
||||
|
||||
if draw_full {
|
||||
img2 := m.ToImage()
|
||||
buf := bytes.NewBuffer(nil)
|
||||
bmp.Encode(buf, img2)
|
||||
os.WriteFile("test.bmp", buf.Bytes(), 0o777)
|
||||
ChunkCount := len(m.renderedChunks)
|
||||
if m.showOnGui {
|
||||
min, max := m.GetBounds()
|
||||
m.w.gui.Message(messages.UpdateMap, messages.UpdateMapPayload{
|
||||
ChunkCount: ChunkCount,
|
||||
Rotation: m.w.PlayerPos.Yaw,
|
||||
UpdatedTiles: updatedChunks,
|
||||
Tiles: m.renderedChunks,
|
||||
BoundsMin: min,
|
||||
BoundsMax: max,
|
||||
})
|
||||
}
|
||||
m.l.RUnlock()
|
||||
}
|
||||
|
||||
func (m *MapUI) ToImage() *image.RGBA {
|
||||
// get the chunk coord bounds
|
||||
min, max := m.get_bounds()
|
||||
chunks_x := int(max[0] - min[0] + 1) // how many chunk lengths is x coordinate
|
||||
chunks_y := int(max[1] - min[1] + 1)
|
||||
min, max := m.GetBounds()
|
||||
chunksX := int(max[0] - min[0] + 1) // how many chunk lengths is x coordinate
|
||||
chunksY := int(max[1] - min[1] + 1)
|
||||
|
||||
img2 := image.NewRGBA(image.Rect(0, 0, chunks_x*16, chunks_y*16))
|
||||
img2 := image.NewRGBA(image.Rect(0, 0, chunksX*16, chunksY*16))
|
||||
|
||||
middle_block_x := chunks_x / 2 * 16
|
||||
middle_block_y := chunks_y / 2 * 16
|
||||
|
||||
for pos := range m.renderedChunks {
|
||||
px_pos := image.Point{
|
||||
X: int(pos.X()*16) - middle_block_x + img2.Rect.Dx(),
|
||||
Y: int(pos.Z()*16) - middle_block_y + img2.Rect.Dy(),
|
||||
}
|
||||
m.l.RLock()
|
||||
for pos, tile := range m.renderedChunks {
|
||||
px := image.Pt(
|
||||
int((pos.X()-min.X())*16),
|
||||
int((pos.Z()-min.Z())*16),
|
||||
)
|
||||
draw.Draw(img2, image.Rect(
|
||||
px_pos.X,
|
||||
px_pos.Y,
|
||||
px_pos.X+16,
|
||||
px_pos.Y+16,
|
||||
), m.renderedChunks[pos], image.Point{}, draw.Src)
|
||||
px.X, px.Y,
|
||||
px.X+16, px.Y+16,
|
||||
), tile, image.Point{}, draw.Src)
|
||||
}
|
||||
m.l.RUnlock()
|
||||
return img2
|
||||
}
|
||||
|
||||
|
@ -235,3 +270,27 @@ func (m *MapUI) SetChunk(pos protocol.ChunkPos, ch *chunk.Chunk) {
|
|||
m.renderQueue.Enqueue(&RenderElem{pos, ch})
|
||||
m.SchedRedraw()
|
||||
}
|
||||
|
||||
func (w *WorldState) ProcessAnimate(pk *packet.Animate) {
|
||||
if pk.ActionType == packet.AnimateActionSwingArm {
|
||||
w.mapUI.ChangeZoom()
|
||||
w.proxy.SendPopup(locale.Loc("zoom_level", locale.Strmap{"Level": w.mapUI.zoomLevel}))
|
||||
}
|
||||
}
|
||||
|
||||
func (w *WorldState) processMapPacketsClient(pk packet.Packet, forward *bool) packet.Packet {
|
||||
switch pk := pk.(type) {
|
||||
case *packet.MovePlayer:
|
||||
w.SetPlayerPos(pk.Position, pk.Pitch, pk.Yaw, pk.HeadYaw)
|
||||
case *packet.PlayerAuthInput:
|
||||
w.SetPlayerPos(pk.Position, pk.Pitch, pk.Yaw, pk.HeadYaw)
|
||||
case *packet.MapInfoRequest:
|
||||
if pk.MapID == ViewMapID {
|
||||
w.mapUI.SchedRedraw()
|
||||
*forward = false
|
||||
}
|
||||
case *packet.Animate:
|
||||
w.ProcessAnimate(pk)
|
||||
}
|
||||
return pk
|
||||
}
|
||||
|
|
|
@ -3,36 +3,32 @@ package world
|
|||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/draw"
|
||||
"image/png"
|
||||
"math/rand"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/bedrock-tool/bedrocktool/locale"
|
||||
"github.com/bedrock-tool/bedrocktool/ui/messages"
|
||||
"github.com/bedrock-tool/bedrocktool/utils"
|
||||
"github.com/bedrock-tool/bedrocktool/utils/nbtconv"
|
||||
"github.com/bedrock-tool/bedrocktool/utils/behaviourpack"
|
||||
|
||||
"github.com/df-mc/dragonfly/server/block"
|
||||
"github.com/df-mc/dragonfly/server/block/cube"
|
||||
"github.com/df-mc/dragonfly/server/item"
|
||||
"github.com/df-mc/dragonfly/server/item/inventory"
|
||||
"github.com/df-mc/dragonfly/server/world"
|
||||
"github.com/df-mc/dragonfly/server/world/chunk"
|
||||
"github.com/df-mc/dragonfly/server/world/mcdb"
|
||||
"github.com/df-mc/goleveldb/leveldb/opt"
|
||||
"github.com/go-gl/mathgl/mgl32"
|
||||
"github.com/google/subcommands"
|
||||
"github.com/sandertv/gophertunnel/minecraft/protocol"
|
||||
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
|
||||
"github.com/sandertv/gophertunnel/minecraft/resource"
|
||||
"github.com/sirupsen/logrus"
|
||||
//_ "github.com/df-mc/dragonfly/server/block" // to load blocks
|
||||
//_ "net/http/pprof"
|
||||
)
|
||||
|
||||
type TPlayerPos struct {
|
||||
|
@ -42,54 +38,61 @@ type TPlayerPos struct {
|
|||
HeadYaw float32
|
||||
}
|
||||
|
||||
type itemContainer struct {
|
||||
OpenPacket *packet.ContainerOpen
|
||||
Content *packet.InventoryContent
|
||||
}
|
||||
|
||||
// the state used for drawing and saving
|
||||
|
||||
type WorldState struct {
|
||||
ctx context.Context
|
||||
ispre118 bool
|
||||
voidgen bool
|
||||
ctx context.Context
|
||||
proxy *utils.ProxyContext
|
||||
mapUI *MapUI
|
||||
gui utils.UI
|
||||
bp *behaviourpack.BehaviourPack
|
||||
|
||||
// save state
|
||||
chunks map[protocol.ChunkPos]*chunk.Chunk
|
||||
blockNBT map[protocol.SubChunkPos][]map[string]any
|
||||
openItemContainers map[byte]*itemContainer
|
||||
airRid uint32
|
||||
|
||||
Dim world.Dimension
|
||||
WorldName string
|
||||
ServerName string
|
||||
worldCounter int
|
||||
packs map[string]*resource.Pack
|
||||
entities map[uint64]*entityState
|
||||
Dim world.Dimension
|
||||
PlayerPos TPlayerPos
|
||||
worldCounter int
|
||||
WorldName string
|
||||
ServerName string
|
||||
ispre118 bool
|
||||
|
||||
// settings
|
||||
voidGen bool
|
||||
withPacks bool
|
||||
saveImage bool
|
||||
experimentInventory bool
|
||||
|
||||
PlayerPos TPlayerPos
|
||||
proxy *utils.ProxyContext
|
||||
|
||||
// ui
|
||||
ui *MapUI
|
||||
}
|
||||
|
||||
func NewWorldState() *WorldState {
|
||||
func NewWorldState(ctx context.Context, proxy *utils.ProxyContext, ServerName string, ui utils.UI) *WorldState {
|
||||
w := &WorldState{
|
||||
ctx: ctx,
|
||||
proxy: proxy,
|
||||
mapUI: nil,
|
||||
gui: ui,
|
||||
bp: behaviourpack.New(ServerName),
|
||||
ServerName: ServerName,
|
||||
|
||||
chunks: make(map[protocol.ChunkPos]*chunk.Chunk),
|
||||
blockNBT: make(map[protocol.SubChunkPos][]map[string]any),
|
||||
openItemContainers: make(map[byte]*itemContainer),
|
||||
entities: make(map[uint64]*entityState),
|
||||
Dim: nil,
|
||||
WorldName: "world",
|
||||
PlayerPos: TPlayerPos{},
|
||||
airRid: 6692,
|
||||
}
|
||||
w.ui = NewMapUI(w)
|
||||
w.mapUI = NewMapUI(w)
|
||||
|
||||
w.gui.Message(messages.Init, messages.InitPayload{
|
||||
Handler: w.uiMessage,
|
||||
})
|
||||
|
||||
return w
|
||||
}
|
||||
|
||||
var dimension_ids = map[uint8]world.Dimension{
|
||||
var dimensionIDMap = map[uint8]world.Dimension{
|
||||
0: world.Overworld,
|
||||
1: world.Nether,
|
||||
2: world.End,
|
||||
|
@ -100,190 +103,104 @@ var dimension_ids = map[uint8]world.Dimension{
|
|||
}
|
||||
|
||||
var (
|
||||
black_16x16 = image.NewRGBA(image.Rect(0, 0, 16, 16))
|
||||
Offset_table [24]protocol.SubChunkOffset
|
||||
black16x16 = image.NewRGBA(image.Rect(0, 0, 16, 16))
|
||||
offsetTable [24]protocol.SubChunkOffset
|
||||
)
|
||||
|
||||
func init() {
|
||||
for i := range Offset_table {
|
||||
Offset_table[i] = protocol.SubChunkOffset{0, int8(i), 0}
|
||||
for i := range offsetTable {
|
||||
offsetTable[i] = protocol.SubChunkOffset{0, int8(i), 0}
|
||||
}
|
||||
for i := 3; i < len(black16x16.Pix); i += 4 {
|
||||
black16x16.Pix[i] = 255
|
||||
}
|
||||
draw.Draw(black_16x16, image.Rect(0, 0, 16, 16), image.Black, image.Point{}, draw.Src)
|
||||
utils.RegisterCommand(&WorldCMD{})
|
||||
}
|
||||
|
||||
type WorldCMD struct {
|
||||
Address string
|
||||
packs bool
|
||||
enableVoid bool
|
||||
saveImage bool
|
||||
experimentInventory bool
|
||||
ServerAddress string
|
||||
Packs bool
|
||||
EnableVoid bool
|
||||
SaveImage bool
|
||||
ExperimentInventory bool
|
||||
}
|
||||
|
||||
func (*WorldCMD) Name() string { return "worlds" }
|
||||
func (*WorldCMD) Synopsis() string { return "download a world from a server" }
|
||||
func (*WorldCMD) Synopsis() string { return locale.Loc("world_synopsis", nil) }
|
||||
|
||||
func (p *WorldCMD) SetFlags(f *flag.FlagSet) {
|
||||
f.StringVar(&p.Address, "address", "", "remote server address")
|
||||
f.BoolVar(&p.packs, "packs", false, "save resourcepacks to the worlds")
|
||||
f.BoolVar(&p.enableVoid, "void", true, "if false, saves with default flat generator")
|
||||
f.BoolVar(&p.saveImage, "image", false, "saves an png of the map at the end")
|
||||
f.BoolVar(&p.experimentInventory, "inv", false, "enable experimental block inventory saving")
|
||||
func (c *WorldCMD) SetFlags(f *flag.FlagSet) {
|
||||
f.StringVar(&c.ServerAddress, "address", "", locale.Loc("remote_address", nil))
|
||||
f.BoolVar(&c.Packs, "packs", false, locale.Loc("save_packs_with_world", nil))
|
||||
f.BoolVar(&c.EnableVoid, "void", true, locale.Loc("enable_void", nil))
|
||||
f.BoolVar(&c.SaveImage, "image", false, locale.Loc("save_image", nil))
|
||||
f.BoolVar(&c.ExperimentInventory, "inv", false, locale.Loc("test_block_inv", nil))
|
||||
}
|
||||
|
||||
func (c *WorldCMD) Usage() string {
|
||||
return c.Name() + ": " + c.Synopsis() + "\n" + utils.SERVER_ADDRESS_HELP
|
||||
}
|
||||
|
||||
func (c *WorldCMD) Execute(ctx context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
|
||||
/*
|
||||
go func() {
|
||||
http.ListenAndServe(":8000", nil)
|
||||
}()
|
||||
*/
|
||||
|
||||
server_address, hostname, err := utils.ServerInput(ctx, c.Address)
|
||||
func (c *WorldCMD) Execute(ctx context.Context, ui utils.UI) error {
|
||||
serverAddress, hostname, err := ui.ServerInput(ctx, c.ServerAddress)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
return 1
|
||||
return err
|
||||
}
|
||||
|
||||
w := NewWorldState()
|
||||
w.voidgen = c.enableVoid
|
||||
w.ServerName = hostname
|
||||
w.withPacks = c.packs
|
||||
w.saveImage = c.saveImage
|
||||
w.experimentInventory = c.experimentInventory
|
||||
w.ctx = ctx
|
||||
proxy, err := utils.NewProxy()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
proxy := utils.NewProxy(logrus.StandardLogger())
|
||||
w := NewWorldState(ctx, proxy, hostname, ui)
|
||||
w.voidGen = c.EnableVoid
|
||||
w.withPacks = c.Packs
|
||||
w.saveImage = c.SaveImage
|
||||
w.experimentInventory = c.ExperimentInventory
|
||||
|
||||
proxy.AlwaysGetPacks = true
|
||||
proxy.ConnectCB = w.OnConnect
|
||||
proxy.PacketCB = func(pk packet.Packet, proxy *utils.ProxyContext, toServer bool) (packet.Packet, error) {
|
||||
var forward bool
|
||||
proxy.OnClientConnect = func(proxy *utils.ProxyContext, hasClient bool) {
|
||||
w.gui.Message(messages.SetUIState, messages.UIStateConnecting)
|
||||
}
|
||||
proxy.PacketCB = func(pk packet.Packet, proxy *utils.ProxyContext, toServer bool, _ time.Time) (packet.Packet, error) {
|
||||
forward := true
|
||||
|
||||
if toServer {
|
||||
pk, forward = w.ProcessPacketClient(pk)
|
||||
// from client
|
||||
pk = w.processItemPacketsClient(pk, &forward)
|
||||
pk = w.processMapPacketsClient(pk, &forward)
|
||||
} else {
|
||||
pk, forward = w.ProcessPacketServer(pk)
|
||||
// from server
|
||||
pk = w.processItemPacketsServer(pk)
|
||||
pk = w.ProcessChunkPackets(pk)
|
||||
pk = w.ProcessEntityPackets(pk)
|
||||
}
|
||||
|
||||
if !forward {
|
||||
return nil, nil
|
||||
}
|
||||
return pk, nil
|
||||
}
|
||||
|
||||
err = proxy.Run(ctx, server_address)
|
||||
w.gui.Message(messages.SetUIState, messages.UIStateConnect)
|
||||
err = w.proxy.Run(ctx, serverAddress)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
return err
|
||||
}
|
||||
w.SaveAndReset()
|
||||
return 0
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *WorldState) setnameCommand(cmdline []string) bool {
|
||||
w.WorldName = strings.Join(cmdline, " ")
|
||||
w.proxy.SendMessage(fmt.Sprintf("worldName is now: %s", w.WorldName))
|
||||
return true
|
||||
}
|
||||
|
||||
func (w *WorldState) toggleVoid(cmdline []string) bool {
|
||||
w.voidgen = !w.voidgen
|
||||
w.proxy.SendMessage(fmt.Sprintf("using void generator: %t", w.voidgen))
|
||||
return true
|
||||
}
|
||||
|
||||
func (w *WorldState) ProcessLevelChunk(pk *packet.LevelChunk) {
|
||||
_, exists := w.chunks[pk.Position]
|
||||
if exists {
|
||||
return
|
||||
func (w *WorldState) uiMessage(name string, data interface{}) messages.MessageResponse {
|
||||
r := messages.MessageResponse{
|
||||
Ok: false,
|
||||
Data: nil,
|
||||
}
|
||||
|
||||
ch, blockNBTs, err := chunk.NetworkDecode(w.airRid, pk.RawPayload, int(pk.SubChunkCount), w.Dim.Range(), w.ispre118)
|
||||
if err != nil {
|
||||
logrus.Error(err)
|
||||
return
|
||||
switch name {
|
||||
case messages.SetVoidGen:
|
||||
set_void_gen := data.(messages.SetVoidGenPayload)
|
||||
r.Ok = w.setVoidGen(set_void_gen.Value, true)
|
||||
case messages.SetWorldName:
|
||||
set_world_name := data.(messages.SetWorldNamePayload)
|
||||
r.Ok = w.setWorldName(set_world_name.WorldName, true)
|
||||
}
|
||||
if blockNBTs != nil {
|
||||
w.blockNBT[protocol.SubChunkPos{
|
||||
pk.Position.X(), 0, pk.Position.Z(),
|
||||
}] = blockNBTs
|
||||
}
|
||||
|
||||
w.chunks[pk.Position] = ch
|
||||
|
||||
if pk.SubChunkRequestMode == protocol.SubChunkRequestModeLegacy {
|
||||
w.ui.SetChunk(pk.Position, ch)
|
||||
} else {
|
||||
w.ui.SetChunk(pk.Position, nil)
|
||||
// request all the subchunks
|
||||
|
||||
max := w.Dim.Range().Height() / 16
|
||||
if pk.SubChunkRequestMode == protocol.SubChunkRequestModeLimited {
|
||||
max = int(pk.HighestSubChunk)
|
||||
}
|
||||
|
||||
w.proxy.Server.WritePacket(&packet.SubChunkRequest{
|
||||
Dimension: int32(w.Dim.EncodeDimension()),
|
||||
Position: protocol.SubChunkPos{
|
||||
pk.Position.X(), 0, pk.Position.Z(),
|
||||
},
|
||||
Offsets: Offset_table[:max],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (w *WorldState) ProcessSubChunk(pk *packet.SubChunk) {
|
||||
pos_to_redraw := make(map[protocol.ChunkPos]bool)
|
||||
|
||||
for _, sub := range pk.SubChunkEntries {
|
||||
var (
|
||||
abs_x = pk.Position[0] + int32(sub.Offset[0])
|
||||
abs_y = pk.Position[1] + int32(sub.Offset[1])
|
||||
abs_z = pk.Position[2] + int32(sub.Offset[2])
|
||||
subpos = protocol.SubChunkPos{abs_x, abs_y, abs_z}
|
||||
pos = protocol.ChunkPos{abs_x, abs_z}
|
||||
)
|
||||
ch := w.chunks[pos]
|
||||
if ch == nil {
|
||||
logrus.Errorf("the server didnt send the chunk before the subchunk!")
|
||||
continue
|
||||
}
|
||||
blockNBT, err := ch.ApplySubChunkEntry(uint8(abs_y), &sub)
|
||||
if err != nil {
|
||||
logrus.Error(err)
|
||||
}
|
||||
if blockNBT != nil {
|
||||
w.blockNBT[subpos] = blockNBT
|
||||
}
|
||||
|
||||
pos_to_redraw[pos] = true
|
||||
}
|
||||
|
||||
// redraw the chunks
|
||||
for pos := range pos_to_redraw {
|
||||
w.ui.SetChunk(pos, w.chunks[pos])
|
||||
}
|
||||
w.ui.SchedRedraw()
|
||||
}
|
||||
|
||||
func (w *WorldState) ProcessAnimate(pk *packet.Animate) {
|
||||
if pk.ActionType == packet.AnimateActionSwingArm {
|
||||
w.ui.ChangeZoom()
|
||||
w.proxy.SendPopup(fmt.Sprintf("Zoom: %d", w.ui.zoomLevel))
|
||||
}
|
||||
}
|
||||
|
||||
func (w *WorldState) ProcessChangeDimension(pk *packet.ChangeDimension) {
|
||||
if len(w.chunks) > 0 {
|
||||
w.SaveAndReset()
|
||||
} else {
|
||||
logrus.Info("Skipping save because the world didnt contain any chunks.")
|
||||
w.Reset()
|
||||
}
|
||||
dim_id := pk.Dimension
|
||||
if w.ispre118 {
|
||||
dim_id += 10
|
||||
}
|
||||
w.Dim = dimension_ids[uint8(dim_id)]
|
||||
return r
|
||||
}
|
||||
|
||||
func (w *WorldState) SetPlayerPos(Position mgl32.Vec3, Pitch, Yaw, HeadYaw float32) {
|
||||
|
@ -296,19 +213,74 @@ func (w *WorldState) SetPlayerPos(Position mgl32.Vec3, Pitch, Yaw, HeadYaw float
|
|||
}
|
||||
|
||||
if int(last.Position.X()) != int(w.PlayerPos.Position.X()) || int(last.Position.Z()) != int(w.PlayerPos.Position.Z()) {
|
||||
w.ui.SchedRedraw()
|
||||
w.mapUI.SchedRedraw()
|
||||
}
|
||||
}
|
||||
|
||||
func (w *WorldState) setVoidGen(val bool, fromUI bool) bool {
|
||||
w.voidGen = val
|
||||
var s string
|
||||
if w.voidGen {
|
||||
s = locale.Loc("void_generator_true", nil)
|
||||
} else {
|
||||
s = locale.Loc("void_generator_false", nil)
|
||||
}
|
||||
w.proxy.SendMessage(s)
|
||||
|
||||
if !fromUI {
|
||||
w.gui.Message(messages.SetVoidGen, messages.SetVoidGenPayload{
|
||||
Value: w.voidGen,
|
||||
})
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (w *WorldState) setWorldName(val string, fromUI bool) bool {
|
||||
w.WorldName = val
|
||||
w.proxy.SendMessage(locale.Loc("worldname_set", locale.Strmap{"Name": w.WorldName}))
|
||||
|
||||
if !fromUI {
|
||||
w.gui.Message(messages.SetWorldName, messages.SetWorldNamePayload{
|
||||
WorldName: w.WorldName,
|
||||
})
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (w *WorldState) Reset() {
|
||||
w.chunks = make(map[protocol.ChunkPos]*chunk.Chunk)
|
||||
w.WorldName = fmt.Sprintf("world-%d", w.worldCounter)
|
||||
w.ui.Reset()
|
||||
w.mapUI.Reset()
|
||||
}
|
||||
|
||||
// writes the world to a folder, resets all the chunks
|
||||
// SaveAndReset writes the world to a folder, resets all the chunks
|
||||
func (w *WorldState) SaveAndReset() {
|
||||
logrus.Infof("Saving world %s %d chunks", w.WorldName, len(w.chunks))
|
||||
keys := make([]protocol.ChunkPos, 0, len(w.chunks))
|
||||
for cp := range w.chunks {
|
||||
keys = append(keys, cp)
|
||||
}
|
||||
|
||||
for _, cp := range keys {
|
||||
has_any := false
|
||||
for _, sc := range w.chunks[cp].Sub() {
|
||||
has_any = !sc.Empty()
|
||||
if has_any {
|
||||
break
|
||||
}
|
||||
}
|
||||
if !has_any {
|
||||
delete(w.chunks, cp)
|
||||
}
|
||||
}
|
||||
|
||||
if len(w.chunks) == 0 {
|
||||
w.Reset()
|
||||
return
|
||||
}
|
||||
|
||||
logrus.Infof(locale.Loc("saving_world", locale.Strmap{"Name": w.WorldName, "Count": len(w.chunks)}))
|
||||
|
||||
// open world
|
||||
folder := path.Join("worlds", fmt.Sprintf("%s/%s", w.ServerName, w.WorldName))
|
||||
|
@ -326,13 +298,26 @@ func (w *WorldState) SaveAndReset() {
|
|||
}
|
||||
|
||||
// save block nbt data
|
||||
blockNBT := make(map[protocol.ChunkPos][]map[string]any)
|
||||
blockNBT := make(map[world.ChunkPos][]map[string]any)
|
||||
for scp, v := range w.blockNBT { // 3d to 2d
|
||||
cp := protocol.ChunkPos{scp.X(), scp.Z()}
|
||||
cp := world.ChunkPos{scp.X(), scp.Z()}
|
||||
blockNBT[cp] = append(blockNBT[cp], v...)
|
||||
}
|
||||
for cp, v := range blockNBT {
|
||||
err = provider.SaveBlockNBT((world.ChunkPos)(cp), v, w.Dim)
|
||||
err = provider.SaveBlockNBT(cp, v, w.Dim)
|
||||
if err != nil {
|
||||
logrus.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
chunkEntities := make(map[world.ChunkPos][]world.Entity)
|
||||
for _, es := range w.entities {
|
||||
cp := world.ChunkPos{int32(es.Position.X()) >> 4, int32(es.Position.Z()) >> 4}
|
||||
chunkEntities[cp] = append(chunkEntities[cp], es.ToServerEntity())
|
||||
}
|
||||
|
||||
for cp, v := range chunkEntities {
|
||||
err = provider.SaveEntities(cp, v, w.Dim)
|
||||
if err != nil {
|
||||
logrus.Error(err)
|
||||
}
|
||||
|
@ -415,66 +400,143 @@ func (w *WorldState) SaveAndReset() {
|
|||
ld.ShowBorderEffect = gr.Value.(bool)
|
||||
// todo
|
||||
default:
|
||||
logrus.Warnf("unknown gamerule: %s\n", gr.Name)
|
||||
logrus.Warnf(locale.Loc("unknown_gamerule", locale.Strmap{"Name": gr.Name}))
|
||||
}
|
||||
}
|
||||
|
||||
ld.RandomSeed = int64(gd.WorldSeed)
|
||||
|
||||
// void world
|
||||
if w.voidgen {
|
||||
if w.voidGen {
|
||||
ld.FlatWorldLayers = `{"biome_id":1,"block_layers":[{"block_data":0,"block_id":0,"count":1},{"block_data":0,"block_id":0,"count":2},{"block_data":0,"block_id":0,"count":1}],"encoding_version":3,"structure_options":null}`
|
||||
ld.Generator = 2
|
||||
}
|
||||
|
||||
if w.bp.HasContent() {
|
||||
if ld.Experiments == nil {
|
||||
ld.Experiments = map[string]any{}
|
||||
}
|
||||
ld.Experiments["data_driven_items"] = true
|
||||
ld.Experiments["experiments_ever_used"] = true
|
||||
ld.Experiments["saved_with_toggled_experiments"] = true
|
||||
}
|
||||
|
||||
provider.SaveSettings(s)
|
||||
provider.Close()
|
||||
if err = provider.Close(); err != nil {
|
||||
logrus.Error(err)
|
||||
}
|
||||
|
||||
w.worldCounter += 1
|
||||
|
||||
for k, p := range w.packs {
|
||||
logrus.Infof("Adding resource pack: %s\n", k)
|
||||
pack_folder := path.Join(folder, "resource_packs", k)
|
||||
os.MkdirAll(pack_folder, 0o755)
|
||||
data := make([]byte, p.Len())
|
||||
p.ReadAt(data, 0)
|
||||
utils.UnpackZip(bytes.NewReader(data), int64(len(data)), pack_folder)
|
||||
type dep struct {
|
||||
PackID string `json:"pack_id"`
|
||||
Version [3]int `json:"version"`
|
||||
}
|
||||
addPacksJSON := func(name string, deps []dep) {
|
||||
f, err := os.Create(path.Join(folder, name))
|
||||
if err != nil {
|
||||
logrus.Error(err)
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
if err := json.NewEncoder(f).Encode(deps); err != nil {
|
||||
logrus.Error(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// save behaviourpack
|
||||
if w.bp.HasContent() {
|
||||
name := strings.ReplaceAll(w.ServerName, "./", "")
|
||||
name = strings.ReplaceAll(name, "/", "-")
|
||||
packFolder := path.Join(folder, "behavior_packs", name)
|
||||
os.MkdirAll(packFolder, 0o755)
|
||||
|
||||
for _, p := range w.proxy.Server.ResourcePacks() {
|
||||
p := utils.PackFromBase(p)
|
||||
w.bp.CheckAddLink(p)
|
||||
}
|
||||
|
||||
w.bp.Save(packFolder)
|
||||
addPacksJSON("world_behavior_packs.json", []dep{{
|
||||
PackID: w.bp.Manifest.Header.UUID,
|
||||
Version: w.bp.Manifest.Header.Version,
|
||||
}})
|
||||
|
||||
// force resource packs for worlds with custom blocks
|
||||
w.withPacks = true
|
||||
}
|
||||
|
||||
// add resource packs
|
||||
if w.withPacks {
|
||||
packs, err := utils.GetPacks(w.proxy.Server)
|
||||
if err != nil {
|
||||
logrus.Error(err)
|
||||
} else {
|
||||
var rdeps []dep
|
||||
for k, p := range packs {
|
||||
logrus.Infof(locale.Loc("adding_pack", locale.Strmap{"Name": k}))
|
||||
packFolder := path.Join(folder, "resource_packs", p.Name())
|
||||
os.MkdirAll(packFolder, 0o755)
|
||||
data := make([]byte, p.Len())
|
||||
p.ReadAt(data, 0)
|
||||
utils.UnpackZip(bytes.NewReader(data), int64(len(data)), packFolder)
|
||||
|
||||
rdeps = append(rdeps, dep{
|
||||
PackID: p.Manifest().Header.UUID,
|
||||
Version: p.Manifest().Header.Version,
|
||||
})
|
||||
}
|
||||
if len(rdeps) > 0 {
|
||||
addPacksJSON("world_resource_packs.json", rdeps)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if w.saveImage {
|
||||
f, _ := os.Create(folder + ".png")
|
||||
png.Encode(f, w.ui.ToImage())
|
||||
png.Encode(f, w.mapUI.ToImage())
|
||||
f.Close()
|
||||
}
|
||||
|
||||
// zip it
|
||||
filename := folder + ".mcworld"
|
||||
|
||||
if err := utils.ZipFolder(filename, folder); err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
logrus.Infof("Saved: %s\n", filename)
|
||||
os.RemoveAll(folder)
|
||||
logrus.Info(locale.Loc("saved", locale.Strmap{"Name": filename}))
|
||||
//os.RemoveAll(folder)
|
||||
w.Reset()
|
||||
}
|
||||
|
||||
func (w *WorldState) OnConnect(proxy *utils.ProxyContext) {
|
||||
func (w *WorldState) OnConnect(proxy *utils.ProxyContext, err error) bool {
|
||||
w.gui.Message(messages.SetUIState, messages.UIStateMain)
|
||||
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
w.proxy = proxy
|
||||
gd := w.proxy.Server.GameData()
|
||||
|
||||
if len(gd.CustomBlocks) > 0 {
|
||||
logrus.Info("Using Custom Blocks")
|
||||
/*
|
||||
for _, be := range gd.CustomBlocks {
|
||||
b := block.ServerCustomBlock(be)
|
||||
world.RegisterBlock(b)
|
||||
}
|
||||
*/
|
||||
world.InsertCustomItems(gd.Items)
|
||||
|
||||
for _, ie := range gd.Items {
|
||||
w.bp.AddItem(ie)
|
||||
}
|
||||
|
||||
if w.withPacks {
|
||||
go func() {
|
||||
w.packs, _ = utils.GetPacks(w.proxy.Server)
|
||||
}()
|
||||
mapItemID, _ := world.ItemRidByName("minecraft:filled_map")
|
||||
MapItemPacket.Content[0].Stack.ItemType.NetworkID = mapItemID
|
||||
if gd.ServerAuthoritativeInventory {
|
||||
MapItemPacket.Content[0].StackNetworkID = 0xffff + rand.Int31n(0xfff)
|
||||
}
|
||||
|
||||
if len(gd.CustomBlocks) > 0 {
|
||||
logrus.Info(locale.Loc("using_customblocks", nil))
|
||||
for _, be := range gd.CustomBlocks {
|
||||
w.bp.AddBlock(be)
|
||||
}
|
||||
// telling the chunk code what custom blocks there are so it can generate offsets
|
||||
world.InsertCustomBlocks(gd.CustomBlocks)
|
||||
}
|
||||
|
||||
{ // check game version
|
||||
|
@ -486,42 +548,28 @@ func (w *WorldState) OnConnect(proxy *utils.ProxyContext) {
|
|||
w.ispre118 = ver < 18
|
||||
}
|
||||
if err != nil || len(gv) <= 1 {
|
||||
logrus.Info("couldnt determine game version, assuming > 1.18")
|
||||
logrus.Info(locale.Loc("guessing_version", nil))
|
||||
}
|
||||
|
||||
dim_id := gd.Dimension
|
||||
dimensionID := gd.Dimension
|
||||
if w.ispre118 {
|
||||
logrus.Info("using legacy (< 1.18)")
|
||||
dim_id += 10
|
||||
logrus.Info(locale.Loc("using_under_118", nil))
|
||||
dimensionID += 10
|
||||
}
|
||||
w.Dim = dimension_ids[uint8(dim_id)]
|
||||
w.Dim = dimensionIDMap[uint8(dimensionID)]
|
||||
}
|
||||
|
||||
w.proxy.SendMessage("use /setname <worldname>\nto set the world name")
|
||||
w.proxy.SendMessage(locale.Loc("use_setname", nil))
|
||||
|
||||
w.ui.Start()
|
||||
go func() { // send map item
|
||||
select {
|
||||
case <-w.ctx.Done():
|
||||
return
|
||||
default:
|
||||
for {
|
||||
time.Sleep(1 * time.Second)
|
||||
if w.proxy.Client != nil {
|
||||
err := w.proxy.Client.WritePacket(&MAP_ITEM_PACKET)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
w.mapUI.Start()
|
||||
|
||||
proxy.AddCommand(utils.IngameCommand{
|
||||
Exec: w.setnameCommand,
|
||||
Exec: func(cmdline []string) bool {
|
||||
return w.setWorldName(strings.Join(cmdline, " "), false)
|
||||
},
|
||||
Cmd: protocol.Command{
|
||||
Name: "setname",
|
||||
Description: "set user defined name for this world",
|
||||
Description: locale.Loc("setname_desc", nil),
|
||||
Overloads: []protocol.CommandOverload{
|
||||
{
|
||||
Parameters: []protocol.CommandParameter{
|
||||
|
@ -537,139 +585,14 @@ func (w *WorldState) OnConnect(proxy *utils.ProxyContext) {
|
|||
})
|
||||
|
||||
proxy.AddCommand(utils.IngameCommand{
|
||||
Exec: w.toggleVoid,
|
||||
Exec: func(cmdline []string) bool {
|
||||
return w.setVoidGen(!w.voidGen, false)
|
||||
},
|
||||
Cmd: protocol.Command{
|
||||
Name: "void",
|
||||
Description: "toggle if void generator should be used",
|
||||
Description: locale.Loc("void_desc", nil),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (w *WorldState) ProcessPacketClient(pk packet.Packet) (packet.Packet, bool) {
|
||||
forward := true
|
||||
switch pk := pk.(type) {
|
||||
case *packet.MovePlayer:
|
||||
w.SetPlayerPos(pk.Position, pk.Pitch, pk.Yaw, pk.HeadYaw)
|
||||
case *packet.PlayerAuthInput:
|
||||
w.SetPlayerPos(pk.Position, pk.Pitch, pk.Yaw, pk.HeadYaw)
|
||||
case *packet.MapInfoRequest:
|
||||
if pk.MapID == VIEW_MAP_ID {
|
||||
w.ui.SchedRedraw()
|
||||
forward = false
|
||||
}
|
||||
case *packet.MobEquipment:
|
||||
if pk.NewItem.Stack.NBTData["map_uuid"] == int64(VIEW_MAP_ID) {
|
||||
forward = false
|
||||
}
|
||||
case *packet.Animate:
|
||||
w.ProcessAnimate(pk)
|
||||
}
|
||||
return pk, forward
|
||||
}
|
||||
|
||||
// stackToItem converts a network ItemStack representation back to an item.Stack.
|
||||
func stackToItem(it protocol.ItemStack) item.Stack {
|
||||
t, ok := world.ItemByRuntimeID(it.NetworkID, int16(it.MetadataValue))
|
||||
if !ok {
|
||||
t = block.Air{}
|
||||
}
|
||||
if it.BlockRuntimeID > 0 {
|
||||
// It shouldn't matter if it (for whatever reason) wasn't able to get the block runtime ID,
|
||||
// since on the next line, we assert that the block is an item. If it didn't succeed, it'll
|
||||
// return air anyway.
|
||||
b, _ := world.BlockByRuntimeID(uint32(it.BlockRuntimeID))
|
||||
if t, ok = b.(world.Item); !ok {
|
||||
t = block.Air{}
|
||||
}
|
||||
}
|
||||
//noinspection SpellCheckingInspection
|
||||
if nbter, ok := t.(world.NBTer); ok && len(it.NBTData) != 0 {
|
||||
t = nbter.DecodeNBT(it.NBTData).(world.Item)
|
||||
}
|
||||
s := item.NewStack(t, int(it.Count))
|
||||
return nbtconv.ReadItem(it.NBTData, &s)
|
||||
}
|
||||
|
||||
func (w *WorldState) ProcessPacketServer(pk packet.Packet) (packet.Packet, bool) {
|
||||
switch pk := pk.(type) {
|
||||
case *packet.ChangeDimension:
|
||||
w.ProcessChangeDimension(pk)
|
||||
case *packet.LevelChunk:
|
||||
w.ProcessLevelChunk(pk)
|
||||
w.proxy.SendPopup(fmt.Sprintf("%d chunks loaded\nname: %s", len(w.chunks), w.WorldName))
|
||||
case *packet.SubChunk:
|
||||
w.ProcessSubChunk(pk)
|
||||
case *packet.ContainerOpen:
|
||||
if w.experimentInventory {
|
||||
// add to open containers
|
||||
existing, ok := w.openItemContainers[pk.WindowID]
|
||||
if !ok {
|
||||
existing = &itemContainer{}
|
||||
}
|
||||
w.openItemContainers[pk.WindowID] = &itemContainer{
|
||||
OpenPacket: pk,
|
||||
Content: existing.Content,
|
||||
}
|
||||
}
|
||||
case *packet.InventoryContent:
|
||||
if w.experimentInventory {
|
||||
// save content
|
||||
fmt.Printf("WindowID: %d\n", pk.WindowID)
|
||||
existing, ok := w.openItemContainers[byte(pk.WindowID)]
|
||||
if !ok {
|
||||
if pk.WindowID == 0x0 { // inventory
|
||||
w.openItemContainers[byte(pk.WindowID)] = &itemContainer{
|
||||
Content: pk,
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
existing.Content = pk
|
||||
}
|
||||
case *packet.ContainerClose:
|
||||
if w.experimentInventory {
|
||||
switch pk.WindowID {
|
||||
case protocol.WindowIDArmour: // todo handle
|
||||
case protocol.WindowIDOffHand: // todo handle
|
||||
case protocol.WindowIDUI:
|
||||
case protocol.WindowIDInventory: // todo handle
|
||||
default:
|
||||
// find container info
|
||||
existing, ok := w.openItemContainers[byte(pk.WindowID)]
|
||||
if !ok {
|
||||
logrus.Warn("Closed window that wasnt open")
|
||||
break
|
||||
}
|
||||
|
||||
if existing.Content == nil {
|
||||
break
|
||||
}
|
||||
|
||||
pos := existing.OpenPacket.ContainerPosition
|
||||
cp := protocol.SubChunkPos{pos.X() << 4, pos.Z() << 4}
|
||||
|
||||
// create inventory
|
||||
inv := inventory.New(len(existing.Content.Content), nil)
|
||||
for i, c := range existing.Content.Content {
|
||||
item := stackToItem(c.Stack)
|
||||
inv.SetItem(i, item)
|
||||
}
|
||||
|
||||
// put into subchunk
|
||||
nbts := w.blockNBT[cp]
|
||||
for i, v := range nbts {
|
||||
nbt_pos := protocol.BlockPos{v["x"].(int32), v["y"].(int32), v["z"].(int32)}
|
||||
if nbt_pos == pos {
|
||||
w.blockNBT[cp][i]["Items"] = nbtconv.InvToNBT(inv)
|
||||
}
|
||||
}
|
||||
|
||||
w.proxy.SendMessage("Saved Block Inventory")
|
||||
|
||||
// remove it again
|
||||
delete(w.openItemContainers, byte(pk.WindowID))
|
||||
}
|
||||
}
|
||||
}
|
||||
return pk, true
|
||||
|
||||
return true
|
||||
}
|
||||
|
|
|
@ -0,0 +1,140 @@
|
|||
//go:build gui || android
|
||||
|
||||
package ui
|
||||
|
||||
import (
|
||||
"context"
|
||||
"image/color"
|
||||
|
||||
"gioui.org/app"
|
||||
"gioui.org/font/gofont"
|
||||
"gioui.org/io/system"
|
||||
"gioui.org/layout"
|
||||
"gioui.org/op"
|
||||
"gioui.org/widget/material"
|
||||
"gioui.org/x/pref/theme"
|
||||
"github.com/bedrock-tool/bedrocktool/ui/gui/pages"
|
||||
"github.com/bedrock-tool/bedrocktool/ui/gui/pages/settings"
|
||||
"github.com/bedrock-tool/bedrocktool/ui/gui/pages/skins"
|
||||
"github.com/bedrock-tool/bedrocktool/ui/gui/pages/worlds"
|
||||
"github.com/bedrock-tool/bedrocktool/ui/messages"
|
||||
"github.com/bedrock-tool/bedrocktool/utils"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type GUI struct {
|
||||
utils.BaseUI
|
||||
|
||||
router pages.Router
|
||||
cancel context.CancelFunc
|
||||
}
|
||||
|
||||
func (g *GUI) Init() bool {
|
||||
utils.SetCurrentUI(g)
|
||||
return true
|
||||
}
|
||||
|
||||
var paletteLight = material.Palette{
|
||||
Bg: color.NRGBA{0xff, 0xff, 0xff, 0xff},
|
||||
Fg: color.NRGBA{0x12, 0x12, 0x12, 0xff},
|
||||
ContrastBg: color.NRGBA{0x7c, 0x00, 0xf8, 0xff},
|
||||
ContrastFg: color.NRGBA{0x00, 0x00, 0x00, 0xff},
|
||||
}
|
||||
|
||||
var paletteDark = material.Palette{
|
||||
Bg: color.NRGBA{0x12, 0x12, 0x12, 0xff},
|
||||
Fg: color.NRGBA{0xff, 0xff, 0xff, 0xff},
|
||||
ContrastBg: color.NRGBA{0x7c, 0x00, 0xf8, 0xff},
|
||||
ContrastFg: color.NRGBA{0xff, 0xff, 0xff, 0xff},
|
||||
}
|
||||
|
||||
func (g *GUI) Start(ctx context.Context, cancel context.CancelFunc) (err error) {
|
||||
g.cancel = cancel
|
||||
|
||||
w := app.NewWindow(
|
||||
app.Title("Bedrocktool"),
|
||||
)
|
||||
|
||||
th := material.NewTheme(gofont.Collection())
|
||||
dark, err := theme.IsDarkMode()
|
||||
if err != nil {
|
||||
logrus.Warn(err)
|
||||
}
|
||||
if dark {
|
||||
_th := th.WithPalette(paletteDark)
|
||||
th = &_th
|
||||
} else {
|
||||
_th := th.WithPalette(paletteLight)
|
||||
th = &_th
|
||||
}
|
||||
|
||||
g.router = pages.NewRouter(ctx, w.Invalidate, th)
|
||||
|
||||
g.router.Register("Settings", settings.New(&g.router))
|
||||
g.router.Register("worlds", worlds.New(&g.router))
|
||||
g.router.Register("skins", skins.New(&g.router))
|
||||
|
||||
g.router.SwitchTo("Settings")
|
||||
|
||||
go func() {
|
||||
err = g.run(w)
|
||||
}()
|
||||
|
||||
go func() {
|
||||
app.Main()
|
||||
}()
|
||||
|
||||
<-ctx.Done()
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (g *GUI) run(w *app.Window) error {
|
||||
var ops op.Ops
|
||||
for {
|
||||
select {
|
||||
case e := <-w.Events():
|
||||
switch e := e.(type) {
|
||||
case system.DestroyEvent:
|
||||
logrus.Info("Closing")
|
||||
g.cancel()
|
||||
g.router.Wg.Wait()
|
||||
return e.Err
|
||||
case system.FrameEvent:
|
||||
gtx := layout.NewContext(&ops, e)
|
||||
g.router.Layout(gtx, g.router.Theme)
|
||||
e.Frame(gtx.Ops)
|
||||
}
|
||||
case <-g.router.Ctx.Done():
|
||||
logrus.Info("Closing")
|
||||
g.cancel()
|
||||
g.router.Wg.Wait()
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (g *GUI) Message(name string, data interface{}) messages.MessageResponse {
|
||||
r := g.router.Handler(name, data)
|
||||
if r.Ok || r.Data != nil {
|
||||
return r
|
||||
}
|
||||
|
||||
r = messages.MessageResponse{
|
||||
Ok: false,
|
||||
Data: nil,
|
||||
}
|
||||
|
||||
switch name {
|
||||
case "can_show_images":
|
||||
r.Ok = true
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func init() {
|
||||
utils.MakeGui = func() utils.UI {
|
||||
return &GUI{}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,148 @@
|
|||
package pages
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"gioui.org/layout"
|
||||
"gioui.org/op/paint"
|
||||
"gioui.org/widget/material"
|
||||
"gioui.org/x/component"
|
||||
"github.com/bedrock-tool/bedrocktool/ui/messages"
|
||||
)
|
||||
|
||||
type HandlerFunc = func(name string, data interface{}) messages.MessageResponse
|
||||
|
||||
type Page interface {
|
||||
Actions() []component.AppBarAction
|
||||
Overflow() []component.OverflowAction
|
||||
Layout(gtx layout.Context, th *material.Theme) layout.Dimensions
|
||||
NavItem() component.NavItem
|
||||
|
||||
// handle events from program
|
||||
Handler() HandlerFunc
|
||||
}
|
||||
|
||||
type Router struct {
|
||||
Ctx context.Context
|
||||
Wg sync.WaitGroup
|
||||
Invalidate func()
|
||||
|
||||
Theme *material.Theme
|
||||
|
||||
pages map[string]Page
|
||||
current string
|
||||
*component.ModalNavDrawer
|
||||
NavAnim component.VisibilityAnimation
|
||||
*component.AppBar
|
||||
*component.ModalLayer
|
||||
NonModalDrawer, BottomBar bool
|
||||
}
|
||||
|
||||
func NewRouter(ctx context.Context, invalidate func(), th *material.Theme) Router {
|
||||
modal := component.NewModal()
|
||||
|
||||
nav := component.NewNav("Navigation Drawer", "This is an example.")
|
||||
modalNav := component.ModalNavFrom(&nav, modal)
|
||||
|
||||
bar := component.NewAppBar(modal)
|
||||
//bar.NavigationIcon = icon.MenuIcon
|
||||
|
||||
na := component.VisibilityAnimation{
|
||||
State: component.Invisible,
|
||||
Duration: time.Millisecond * 250,
|
||||
}
|
||||
return Router{
|
||||
Ctx: ctx,
|
||||
Invalidate: invalidate,
|
||||
Theme: th,
|
||||
pages: make(map[string]Page),
|
||||
ModalLayer: modal,
|
||||
ModalNavDrawer: modalNav,
|
||||
AppBar: bar,
|
||||
NavAnim: na,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Router) Register(tag string, p Page) {
|
||||
r.pages[tag] = p
|
||||
navItem := p.NavItem()
|
||||
navItem.Tag = tag
|
||||
if r.current == "" {
|
||||
r.current = tag
|
||||
r.AppBar.Title = navItem.Name
|
||||
r.AppBar.SetActions(p.Actions(), p.Overflow())
|
||||
}
|
||||
r.ModalNavDrawer.AddNavItem(navItem)
|
||||
}
|
||||
|
||||
func (r *Router) SwitchTo(tag string) {
|
||||
p, ok := r.pages[tag]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
navItem := p.NavItem()
|
||||
r.current = tag
|
||||
r.AppBar.Title = navItem.Name
|
||||
r.AppBar.SetActions(p.Actions(), p.Overflow())
|
||||
}
|
||||
|
||||
func (r *Router) Layout(gtx layout.Context, th *material.Theme) layout.Dimensions {
|
||||
for _, event := range r.AppBar.Events(gtx) {
|
||||
switch event := event.(type) {
|
||||
case component.AppBarNavigationClicked:
|
||||
if r.NonModalDrawer {
|
||||
r.NavAnim.ToggleVisibility(gtx.Now)
|
||||
} else {
|
||||
r.ModalNavDrawer.Appear(gtx.Now)
|
||||
r.NavAnim.Disappear(gtx.Now)
|
||||
}
|
||||
case component.AppBarContextMenuDismissed:
|
||||
log.Printf("Context menu dismissed: %v", event)
|
||||
case component.AppBarOverflowActionClicked:
|
||||
log.Printf("Overflow action selected: %v", event)
|
||||
}
|
||||
}
|
||||
if r.ModalNavDrawer.NavDestinationChanged() {
|
||||
r.SwitchTo(r.ModalNavDrawer.CurrentNavDestination().(string))
|
||||
}
|
||||
paint.Fill(gtx.Ops, th.Palette.Bg)
|
||||
content := layout.Flexed(1, func(gtx layout.Context) layout.Dimensions {
|
||||
return layout.Flex{}.Layout(gtx,
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
gtx.Constraints.Max.X /= 3
|
||||
return r.NavDrawer.Layout(gtx, th, &r.NavAnim)
|
||||
}),
|
||||
layout.Flexed(1, func(gtx layout.Context) layout.Dimensions {
|
||||
return r.pages[r.current].Layout(gtx, th)
|
||||
}),
|
||||
)
|
||||
})
|
||||
bar := layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
return r.AppBar.Layout(gtx, th, "Menu", "Actions")
|
||||
})
|
||||
flex := layout.Flex{Axis: layout.Vertical}
|
||||
if r.BottomBar {
|
||||
flex.Layout(gtx, content, bar)
|
||||
} else {
|
||||
flex.Layout(gtx, bar, content)
|
||||
}
|
||||
r.ModalLayer.Layout(gtx, th)
|
||||
return layout.Dimensions{Size: gtx.Constraints.Max}
|
||||
}
|
||||
|
||||
func (r *Router) Handler(name string, data interface{}) messages.MessageResponse {
|
||||
page, ok := r.pages[r.current]
|
||||
if ok {
|
||||
return page.Handler()(name, data)
|
||||
}
|
||||
return messages.MessageResponse{}
|
||||
}
|
||||
|
||||
var Pages = map[string]func(*Router) Page{}
|
||||
|
||||
func Register(name string, fun func(*Router) Page) {
|
||||
Pages[name] = fun
|
||||
}
|
|
@ -0,0 +1,179 @@
|
|||
package settings
|
||||
|
||||
import (
|
||||
"image"
|
||||
|
||||
"gioui.org/layout"
|
||||
"gioui.org/unit"
|
||||
"gioui.org/widget"
|
||||
"gioui.org/widget/material"
|
||||
"gioui.org/x/component"
|
||||
"github.com/bedrock-tool/bedrocktool/ui/gui/pages"
|
||||
"github.com/bedrock-tool/bedrocktool/ui/gui/settings"
|
||||
"github.com/bedrock-tool/bedrocktool/ui/messages"
|
||||
"github.com/bedrock-tool/bedrocktool/utils"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type (
|
||||
C = layout.Context
|
||||
D = layout.Dimensions
|
||||
)
|
||||
|
||||
type Page struct {
|
||||
*pages.Router
|
||||
|
||||
cmdMenu struct {
|
||||
show bool
|
||||
open widget.Clickable
|
||||
state *component.MenuState
|
||||
items map[string]*widget.Clickable
|
||||
selected string
|
||||
}
|
||||
|
||||
startButton widget.Clickable
|
||||
}
|
||||
|
||||
func New(router *pages.Router) *Page {
|
||||
p := &Page{
|
||||
Router: router,
|
||||
startButton: widget.Clickable{},
|
||||
}
|
||||
|
||||
options := make([]func(layout.Context) layout.Dimensions, 0, len(utils.ValidCMDs))
|
||||
p.cmdMenu.items = make(map[string]*widget.Clickable, len(utils.ValidCMDs))
|
||||
for k := range utils.ValidCMDs {
|
||||
item := &widget.Clickable{}
|
||||
p.cmdMenu.items[k] = item
|
||||
options = append(options, component.MenuItem(router.Theme, item, k).Layout)
|
||||
}
|
||||
|
||||
p.cmdMenu.state = &component.MenuState{
|
||||
OptionList: layout.List{},
|
||||
Options: options,
|
||||
}
|
||||
//p.cmdMenu.selected = "worlds"
|
||||
|
||||
for _, su := range settings.Settings {
|
||||
su.Init()
|
||||
}
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
var _ pages.Page = &Page{}
|
||||
|
||||
func (p *Page) Actions() []component.AppBarAction {
|
||||
return []component.AppBarAction{}
|
||||
}
|
||||
|
||||
func (p *Page) Overflow() []component.OverflowAction {
|
||||
return []component.OverflowAction{}
|
||||
}
|
||||
|
||||
func (p *Page) NavItem() component.NavItem {
|
||||
return component.NavItem{
|
||||
Name: "Settings",
|
||||
//Icon: icon.OtherIcon,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Page) Layout(gtx C, th *material.Theme) D {
|
||||
if p.startButton.Clicked() {
|
||||
if p.cmdMenu.selected != "" {
|
||||
cmd, ok := utils.ValidCMDs[p.cmdMenu.selected]
|
||||
if !ok {
|
||||
logrus.Errorf("Cmd %s not found", p.cmdMenu.selected)
|
||||
}
|
||||
|
||||
if s, ok := settings.Settings[p.cmdMenu.selected]; ok {
|
||||
s.Apply()
|
||||
}
|
||||
|
||||
p.Router.SwitchTo(p.cmdMenu.selected)
|
||||
|
||||
p.Router.Wg.Add(1)
|
||||
go func() {
|
||||
defer p.Router.Wg.Done()
|
||||
utils.InitDNS()
|
||||
utils.InitExtraDebug(p.Router.Ctx)
|
||||
|
||||
err := cmd.Execute(p.Router.Ctx, utils.CurrentUI)
|
||||
if err != nil {
|
||||
logrus.Error(err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
if p.cmdMenu.open.Clicked() {
|
||||
p.cmdMenu.show = !p.cmdMenu.show
|
||||
}
|
||||
|
||||
for k, c := range p.cmdMenu.items {
|
||||
if c.Clicked() {
|
||||
p.cmdMenu.selected = k
|
||||
p.cmdMenu.show = false
|
||||
}
|
||||
}
|
||||
|
||||
return layout.UniformInset(7).Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||
d := layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
||||
// Select Command Button
|
||||
layout.Rigid(func(gtx C) D {
|
||||
str := p.cmdMenu.selected
|
||||
if str == "" {
|
||||
str = "Select Command"
|
||||
}
|
||||
btn := material.Button(th, &p.cmdMenu.open, str)
|
||||
return btn.Layout(gtx)
|
||||
}),
|
||||
|
||||
layout.Flexed(1, func(gtx layout.Context) layout.Dimensions {
|
||||
if p.cmdMenu.selected == "" {
|
||||
return layout.Dimensions{}
|
||||
}
|
||||
s, ok := settings.Settings[p.cmdMenu.selected]
|
||||
if !ok {
|
||||
return layout.Center.Layout(gtx, material.H4(th, "No Settings Yet (Use CLI)").Layout)
|
||||
} else {
|
||||
return layout.UniformInset(15).Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||
return s.Layout(gtx, th)
|
||||
})
|
||||
}
|
||||
}),
|
||||
|
||||
// Start Button
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
return layout.Center.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||
return layout.Inset{
|
||||
Top: unit.Dp(15),
|
||||
Bottom: unit.Dp(15),
|
||||
}.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||
gtx.Constraints = layout.Constraints{
|
||||
Min: image.Pt(300, 50),
|
||||
Max: image.Pt(400, 50),
|
||||
}
|
||||
btn := material.Button(th, &p.startButton, "Start")
|
||||
return btn.Layout(gtx)
|
||||
})
|
||||
})
|
||||
}),
|
||||
)
|
||||
|
||||
if p.cmdMenu.show {
|
||||
component.Menu(th, p.cmdMenu.state).Layout(gtx)
|
||||
}
|
||||
|
||||
return d
|
||||
})
|
||||
}
|
||||
|
||||
func (p *Page) Handler() pages.HandlerFunc {
|
||||
return func(name string, data interface{}) messages.MessageResponse {
|
||||
return messages.MessageResponse{
|
||||
Ok: false,
|
||||
Data: nil,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
package skins
|
||||
|
||||
import (
|
||||
"gioui.org/layout"
|
||||
"gioui.org/unit"
|
||||
"gioui.org/widget/material"
|
||||
"gioui.org/x/component"
|
||||
"github.com/bedrock-tool/bedrocktool/ui/gui"
|
||||
"github.com/bedrock-tool/bedrocktool/ui/gui/pages"
|
||||
"github.com/bedrock-tool/bedrocktool/ui/messages"
|
||||
)
|
||||
|
||||
type (
|
||||
C = layout.Context
|
||||
D = layout.Dimensions
|
||||
)
|
||||
|
||||
type Page struct {
|
||||
*pages.Router
|
||||
|
||||
State messages.UIState
|
||||
}
|
||||
|
||||
func New(router *pages.Router) *Page {
|
||||
return &Page{
|
||||
Router: router,
|
||||
}
|
||||
}
|
||||
|
||||
var _ pages.Page = &Page{}
|
||||
|
||||
func (p *Page) Actions() []component.AppBarAction {
|
||||
return []component.AppBarAction{}
|
||||
}
|
||||
|
||||
func (p *Page) Overflow() []component.OverflowAction {
|
||||
return []component.OverflowAction{}
|
||||
}
|
||||
|
||||
func (p *Page) NavItem() component.NavItem {
|
||||
return component.NavItem{
|
||||
Name: "Skin Grabber",
|
||||
//Icon: icon.OtherIcon,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Page) Layout(gtx C, th *material.Theme) D {
|
||||
margin := layout.Inset{
|
||||
Top: unit.Dp(25),
|
||||
Bottom: unit.Dp(25),
|
||||
Right: unit.Dp(35),
|
||||
Left: unit.Dp(35),
|
||||
}
|
||||
|
||||
switch p.State {
|
||||
case messages.UIStateConnect:
|
||||
// display login page
|
||||
return margin.Layout(gtx, material.Label(th, 100, "connect Client").Layout)
|
||||
case messages.UIStateConnecting:
|
||||
// display connecting to server
|
||||
return margin.Layout(gtx, material.Label(th, 100, "Connecting").Layout)
|
||||
case messages.UIStateMain:
|
||||
// show the main ui
|
||||
return margin.Layout(gtx, func(gtx C) D {
|
||||
return layout.Flex{
|
||||
Axis: layout.Vertical,
|
||||
}.Layout(gtx,
|
||||
layout.Rigid(material.Label(th, 20, "Skin Basic UI").Layout),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
return layout.Flex{}.Layout(gtx)
|
||||
}
|
||||
|
||||
func (u *Page) handler(name string, data interface{}) messages.MessageResponse {
|
||||
r := messages.MessageResponse{
|
||||
Ok: false,
|
||||
Data: nil,
|
||||
}
|
||||
|
||||
switch name {
|
||||
case messages.SetUIState:
|
||||
state := data.(messages.UIState)
|
||||
u.State = state
|
||||
u.Router.Invalidate()
|
||||
r.Ok = true
|
||||
|
||||
case messages.Init:
|
||||
init := data.(messages.InitPayload)
|
||||
_ = init
|
||||
r.Ok = true
|
||||
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func (p *Page) Handler() gui.HandlerFunc {
|
||||
return p.handler
|
||||
}
|
|
@ -0,0 +1,119 @@
|
|||
package worlds
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/draw"
|
||||
"time"
|
||||
|
||||
"gioui.org/f32"
|
||||
"gioui.org/gesture"
|
||||
"gioui.org/io/pointer"
|
||||
"gioui.org/layout"
|
||||
"gioui.org/op"
|
||||
"gioui.org/op/paint"
|
||||
"github.com/bedrock-tool/bedrocktool/ui/messages"
|
||||
"github.com/sandertv/gophertunnel/minecraft/protocol"
|
||||
)
|
||||
|
||||
type Map struct {
|
||||
click f32.Point
|
||||
mapPos f32.Point
|
||||
pos f32.Point
|
||||
imageOp paint.ImageOp
|
||||
zoom float32
|
||||
|
||||
drag gesture.Drag
|
||||
scroll gesture.Scroll
|
||||
|
||||
MapImage *image.RGBA
|
||||
BoundsMin protocol.ChunkPos
|
||||
BoundsMax protocol.ChunkPos
|
||||
Rotation float32
|
||||
}
|
||||
|
||||
func (m *Map) Layout(gtx layout.Context) layout.Dimensions {
|
||||
// here we loop through all the events associated with this button.
|
||||
for _, e := range m.drag.Events(gtx.Metric, gtx.Queue, gesture.Both) {
|
||||
switch e.Type {
|
||||
case pointer.Press:
|
||||
m.click = e.Position
|
||||
case pointer.Drag:
|
||||
m.pos = m.mapPos.Sub(m.click).Add(e.Position)
|
||||
case pointer.Release:
|
||||
m.mapPos = m.pos
|
||||
}
|
||||
}
|
||||
|
||||
scrollDist := m.scroll.Scroll(gtx.Metric, gtx.Queue, time.Now(), gesture.Vertical)
|
||||
|
||||
m.zoom -= float32(scrollDist) / 20
|
||||
if m.zoom < 0.2 {
|
||||
m.zoom = 0.2
|
||||
}
|
||||
|
||||
size := gtx.Constraints.Max
|
||||
|
||||
if m.MapImage != nil {
|
||||
m.imageOp.Add(gtx.Ops)
|
||||
b := m.MapImage.Bounds()
|
||||
sx := float32(b.Dx() / 2)
|
||||
sy := float32(b.Dy() / 2)
|
||||
|
||||
op.Affine(
|
||||
f32.Affine2D{}.
|
||||
Scale(f32.Pt(sx, sy), f32.Pt(m.zoom, m.zoom)).
|
||||
Offset(m.pos),
|
||||
).Add(gtx.Ops)
|
||||
paint.PaintOp{}.Add(gtx.Ops)
|
||||
}
|
||||
|
||||
m.drag.Add(gtx.Ops)
|
||||
m.scroll.Add(gtx.Ops, image.Rect(-size.X, -size.Y, size.X, size.Y))
|
||||
|
||||
return layout.Dimensions{
|
||||
Size: size,
|
||||
}
|
||||
}
|
||||
|
||||
func drawTile(img *image.RGBA, min, pos protocol.ChunkPos, tile *image.RGBA) {
|
||||
px := image.Pt(
|
||||
int((pos.X()-min[0])*16),
|
||||
int((pos.Z()-min[0])*16),
|
||||
)
|
||||
draw.Draw(img, image.Rect(
|
||||
px.X, px.Y,
|
||||
px.X+16, px.Y+16,
|
||||
), tile, image.Point{}, draw.Src)
|
||||
}
|
||||
|
||||
func (m *Map) Update(u *messages.UpdateMapPayload) {
|
||||
if m.MapImage == nil {
|
||||
m.zoom = 1
|
||||
}
|
||||
|
||||
needNewImage := false
|
||||
if m.BoundsMin != u.BoundsMin {
|
||||
needNewImage = true
|
||||
m.BoundsMin = u.BoundsMin
|
||||
}
|
||||
if m.BoundsMax != u.BoundsMax {
|
||||
needNewImage = true
|
||||
m.BoundsMax = u.BoundsMax
|
||||
}
|
||||
|
||||
if needNewImage {
|
||||
chunksX := int(m.BoundsMax[0] - m.BoundsMin[0] + 1) // how many chunk lengths is x coordinate
|
||||
chunksY := int(m.BoundsMax[1] - m.BoundsMin[1] + 1)
|
||||
m.MapImage = image.NewRGBA(image.Rect(0, 0, chunksX*16, chunksY*16))
|
||||
for pos, tile := range u.Tiles {
|
||||
drawTile(m.MapImage, m.BoundsMin, pos, tile)
|
||||
}
|
||||
} else {
|
||||
for _, pos := range u.UpdatedTiles {
|
||||
tile := u.Tiles[pos]
|
||||
drawTile(m.MapImage, m.BoundsMin, pos, tile)
|
||||
}
|
||||
}
|
||||
|
||||
m.imageOp = paint.NewImageOp(m.MapImage)
|
||||
}
|
|
@ -0,0 +1,127 @@
|
|||
package worlds
|
||||
|
||||
import (
|
||||
"gioui.org/layout"
|
||||
"gioui.org/unit"
|
||||
"gioui.org/widget/material"
|
||||
"gioui.org/x/component"
|
||||
"github.com/bedrock-tool/bedrocktool/ui/gui"
|
||||
"github.com/bedrock-tool/bedrocktool/ui/gui/pages"
|
||||
"github.com/bedrock-tool/bedrocktool/ui/messages"
|
||||
)
|
||||
|
||||
type (
|
||||
C = layout.Context
|
||||
D = layout.Dimensions
|
||||
)
|
||||
|
||||
type Page struct {
|
||||
*pages.Router
|
||||
|
||||
worldMap *Map
|
||||
State messages.UIState
|
||||
chunkCount int
|
||||
voidGen bool
|
||||
worldName string
|
||||
}
|
||||
|
||||
func New(router *pages.Router) *Page {
|
||||
return &Page{
|
||||
Router: router,
|
||||
worldMap: &Map{},
|
||||
}
|
||||
}
|
||||
|
||||
var _ pages.Page = &Page{}
|
||||
|
||||
func (p *Page) Actions() []component.AppBarAction {
|
||||
return []component.AppBarAction{}
|
||||
}
|
||||
|
||||
func (p *Page) Overflow() []component.OverflowAction {
|
||||
return []component.OverflowAction{}
|
||||
}
|
||||
|
||||
func (p *Page) NavItem() component.NavItem {
|
||||
return component.NavItem{
|
||||
Name: "World Downloader",
|
||||
//Icon: icon.OtherIcon,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Page) Layout(gtx C, th *material.Theme) D {
|
||||
margin := layout.Inset{
|
||||
Top: unit.Dp(25),
|
||||
Bottom: unit.Dp(25),
|
||||
Right: unit.Dp(35),
|
||||
Left: unit.Dp(35),
|
||||
}
|
||||
|
||||
switch p.State {
|
||||
case messages.UIStateConnect:
|
||||
// display login page
|
||||
return margin.Layout(gtx, material.Label(th, 100, "connect Client").Layout)
|
||||
case messages.UIStateConnecting:
|
||||
// display connecting to server
|
||||
return margin.Layout(gtx, material.Label(th, 100, "Connecting").Layout)
|
||||
case messages.UIStateMain:
|
||||
// show the main ui
|
||||
return margin.Layout(gtx, func(gtx C) D {
|
||||
return layout.Flex{
|
||||
Axis: layout.Vertical,
|
||||
}.Layout(gtx,
|
||||
layout.Rigid(material.Label(th, 20, "World Downloader Basic UI").Layout),
|
||||
layout.Flexed(1, func(gtx C) D {
|
||||
return layout.Center.Layout(gtx, p.worldMap.Layout)
|
||||
}),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
return layout.Flex{}.Layout(gtx)
|
||||
}
|
||||
|
||||
func (u *Page) handler(name string, data interface{}) messages.MessageResponse {
|
||||
r := messages.MessageResponse{
|
||||
Ok: false,
|
||||
Data: nil,
|
||||
}
|
||||
|
||||
switch name {
|
||||
case messages.SetUIState:
|
||||
state := data.(messages.UIState)
|
||||
u.State = state
|
||||
u.Router.Invalidate()
|
||||
r.Ok = true
|
||||
|
||||
case messages.Init:
|
||||
init := data.(messages.InitPayload)
|
||||
_ = init
|
||||
r.Ok = true
|
||||
|
||||
case messages.UpdateMap:
|
||||
update_map := data.(messages.UpdateMapPayload)
|
||||
u.chunkCount = update_map.ChunkCount
|
||||
u.worldMap.Update(&update_map)
|
||||
u.Router.Invalidate()
|
||||
r.Ok = true
|
||||
|
||||
case messages.SetVoidGen:
|
||||
set_void_gen := data.(messages.SetVoidGenPayload)
|
||||
u.voidGen = set_void_gen.Value
|
||||
u.Router.Invalidate()
|
||||
r.Ok = true
|
||||
|
||||
case messages.SetWorldName:
|
||||
set_world_name := data.(messages.SetWorldNamePayload)
|
||||
u.worldName = set_world_name.WorldName
|
||||
u.Router.Invalidate()
|
||||
r.Ok = true
|
||||
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func (p *Page) Handler() gui.HandlerFunc {
|
||||
return p.handler
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
package settings
|
||||
|
||||
import (
|
||||
"gioui.org/layout"
|
||||
"gioui.org/widget/material"
|
||||
)
|
||||
|
||||
type SettingsUI interface {
|
||||
Init()
|
||||
Apply()
|
||||
Layout(layout.Context, *material.Theme) layout.Dimensions
|
||||
}
|
||||
|
||||
var Settings = map[string]SettingsUI{}
|
|
@ -0,0 +1,44 @@
|
|||
package settings
|
||||
|
||||
import (
|
||||
"gioui.org/layout"
|
||||
"gioui.org/unit"
|
||||
"gioui.org/widget"
|
||||
"gioui.org/widget/material"
|
||||
"github.com/bedrock-tool/bedrocktool/subcommands/skins"
|
||||
"github.com/bedrock-tool/bedrocktool/utils"
|
||||
)
|
||||
|
||||
type skinsSettings struct {
|
||||
skins *skins.SkinCMD
|
||||
|
||||
Filter widget.Editor
|
||||
Proxy widget.Bool
|
||||
serverAddress widget.Editor
|
||||
}
|
||||
|
||||
func (s *skinsSettings) Init() {
|
||||
s.skins = utils.ValidCMDs["skins"].(*skins.SkinCMD)
|
||||
s.serverAddress.SingleLine = true
|
||||
s.Filter.SingleLine = true
|
||||
s.Proxy.Value = true
|
||||
}
|
||||
|
||||
func (s *skinsSettings) Apply() {
|
||||
s.skins.Filter = s.Filter.Text()
|
||||
s.skins.NoProxy = !s.Proxy.Value
|
||||
s.skins.ServerAddress = s.serverAddress.Text()
|
||||
}
|
||||
|
||||
func (s *skinsSettings) Layout(gtx layout.Context, th *material.Theme) layout.Dimensions {
|
||||
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
||||
layout.Rigid(material.CheckBox(th, &s.Proxy, "Enable Proxy").Layout),
|
||||
layout.Rigid(material.Editor(th, &s.Filter, "Player name filter").Layout),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(15)}.Layout),
|
||||
layout.Rigid(material.Editor(th, &s.serverAddress, "server Address").Layout),
|
||||
)
|
||||
}
|
||||
|
||||
func init() {
|
||||
Settings["skins"] = &skinsSettings{}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
package settings
|
||||
|
||||
import (
|
||||
"gioui.org/layout"
|
||||
"gioui.org/widget"
|
||||
"gioui.org/widget/material"
|
||||
"github.com/bedrock-tool/bedrocktool/subcommands/world"
|
||||
"github.com/bedrock-tool/bedrocktool/utils"
|
||||
)
|
||||
|
||||
type worldSettings struct {
|
||||
worlds *world.WorldCMD
|
||||
|
||||
withPacks widget.Bool
|
||||
voidGen widget.Bool
|
||||
saveImage widget.Bool
|
||||
serverAddress widget.Editor
|
||||
}
|
||||
|
||||
func (s *worldSettings) Init() {
|
||||
s.worlds = utils.ValidCMDs["worlds"].(*world.WorldCMD)
|
||||
s.serverAddress.SingleLine = true
|
||||
s.voidGen.Value = true
|
||||
}
|
||||
|
||||
func (s *worldSettings) Apply() {
|
||||
s.worlds.Packs = s.withPacks.Value
|
||||
s.worlds.EnableVoid = s.voidGen.Value
|
||||
s.worlds.SaveImage = s.saveImage.Value
|
||||
s.worlds.ServerAddress = s.serverAddress.Text()
|
||||
}
|
||||
|
||||
func (s *worldSettings) Layout(gtx layout.Context, th *material.Theme) layout.Dimensions {
|
||||
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
||||
layout.Rigid(material.CheckBox(th, &s.withPacks, "with Packs").Layout),
|
||||
layout.Rigid(material.CheckBox(th, &s.voidGen, "void Generator").Layout),
|
||||
layout.Rigid(material.CheckBox(th, &s.saveImage, "save image").Layout),
|
||||
layout.Rigid(material.Editor(th, &s.serverAddress, "server Address").Layout),
|
||||
)
|
||||
}
|
||||
|
||||
func init() {
|
||||
Settings["worlds"] = &worldSettings{}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package gui
|
||||
|
||||
import (
|
||||
"gioui.org/layout"
|
||||
"github.com/bedrock-tool/bedrocktool/ui/messages"
|
||||
)
|
||||
|
||||
type C = layout.Context
|
||||
type D = layout.Dimensions
|
||||
|
||||
type HandlerFunc = func(name string, data interface{}) messages.MessageResponse
|
|
@ -0,0 +1,74 @@
|
|||
package messages
|
||||
|
||||
import (
|
||||
"image"
|
||||
|
||||
"github.com/sandertv/gophertunnel/minecraft/protocol"
|
||||
)
|
||||
|
||||
type MessageResponse struct {
|
||||
Ok bool
|
||||
Data interface{}
|
||||
}
|
||||
|
||||
type UIState = int
|
||||
|
||||
const (
|
||||
UIStateConnect = iota
|
||||
UIStateConnecting
|
||||
UIStateMain
|
||||
)
|
||||
|
||||
type HandlerFunc = func(name string, data interface{}) MessageResponse
|
||||
|
||||
//
|
||||
|
||||
const SetUIState = "set_ui_state"
|
||||
|
||||
type SetUIStatePayload = UIState
|
||||
|
||||
//
|
||||
|
||||
const SetVoidGen = "set_void_gen"
|
||||
|
||||
type SetVoidGenPayload struct {
|
||||
Value bool
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
const SetWorldName = "set_world_name"
|
||||
|
||||
type SetWorldNamePayload struct {
|
||||
WorldName string
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
var Init = "init"
|
||||
|
||||
type InitPayload struct {
|
||||
Handler HandlerFunc
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
var UpdateMap = "update_map"
|
||||
|
||||
type UpdateMapPayload struct {
|
||||
ChunkCount int
|
||||
Rotation float32
|
||||
UpdatedTiles []protocol.ChunkPos
|
||||
Tiles map[protocol.ChunkPos]*image.RGBA
|
||||
BoundsMin protocol.ChunkPos
|
||||
BoundsMax protocol.ChunkPos
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
var NewSkin = "new_skin"
|
||||
|
||||
type NewSkinPayload struct {
|
||||
PlayerName string
|
||||
Skin *protocol.Skin
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
package ui
|
|
@ -2,57 +2,64 @@ package utils
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/bedrock-tool/bedrocktool/locale"
|
||||
"github.com/sandertv/gophertunnel/minecraft/auth"
|
||||
"github.com/sandertv/gophertunnel/minecraft/realms"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
const TOKEN_FILE = "token.json"
|
||||
const TokenFile = "token.json"
|
||||
|
||||
var G_token_src oauth2.TokenSource
|
||||
var gTokenSrc oauth2.TokenSource
|
||||
|
||||
func GetTokenSource() oauth2.TokenSource {
|
||||
if G_token_src != nil {
|
||||
return G_token_src
|
||||
if gTokenSrc != nil {
|
||||
return gTokenSrc
|
||||
}
|
||||
token := get_token()
|
||||
G_token_src = auth.RefreshTokenSource(&token)
|
||||
new_token, err := G_token_src.Token()
|
||||
token := getToken()
|
||||
gTokenSrc = auth.RefreshTokenSource(&token)
|
||||
newToken, err := gTokenSrc.Token()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if !token.Valid() {
|
||||
logrus.Info("Refreshed token")
|
||||
write_token(new_token)
|
||||
logrus.Info(locale.Loc("refreshed_token", nil))
|
||||
writeToken(newToken)
|
||||
}
|
||||
|
||||
return G_token_src
|
||||
return gTokenSrc
|
||||
}
|
||||
|
||||
var G_realms_api *realms.Client
|
||||
var RealmsEnv string
|
||||
|
||||
func GetRealmsApi() *realms.Client {
|
||||
if G_realms_api == nil {
|
||||
G_realms_api = realms.NewClient(GetTokenSource())
|
||||
var gRealmsAPI *realms.Client
|
||||
|
||||
func GetRealmsAPI() *realms.Client {
|
||||
if gRealmsAPI == nil {
|
||||
if RealmsEnv != "" {
|
||||
realms.RealmsAPIBase = fmt.Sprintf("https://pocket-%s.realms.minecraft.net/", RealmsEnv)
|
||||
}
|
||||
gRealmsAPI = realms.NewClient(GetTokenSource())
|
||||
}
|
||||
return G_realms_api
|
||||
return gRealmsAPI
|
||||
}
|
||||
|
||||
func write_token(token *oauth2.Token) {
|
||||
func writeToken(token *oauth2.Token) {
|
||||
buf, err := json.Marshal(token)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
os.WriteFile(TOKEN_FILE, buf, 0o755)
|
||||
os.WriteFile(TokenFile, buf, 0o755)
|
||||
}
|
||||
|
||||
func get_token() oauth2.Token {
|
||||
func getToken() oauth2.Token {
|
||||
var token oauth2.Token
|
||||
if _, err := os.Stat(TOKEN_FILE); err == nil {
|
||||
f, err := os.Open(TOKEN_FILE)
|
||||
if _, err := os.Stat(TokenFile); err == nil {
|
||||
f, err := os.Open(TokenFile)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
@ -65,7 +72,7 @@ func get_token() oauth2.Token {
|
|||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
write_token(_token)
|
||||
writeToken(_token)
|
||||
token = *_token
|
||||
}
|
||||
return token
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
package behaviourpack
|
||||
|
||||
import (
|
||||
"github.com/df-mc/dragonfly/server/world"
|
||||
"github.com/sandertv/gophertunnel/minecraft/protocol"
|
||||
)
|
||||
|
||||
type blockBehaviour struct {
|
||||
FormatVersion string `json:"format_version"`
|
||||
MinecraftBlock world.MinecraftBlock `json:"minecraft:block"`
|
||||
}
|
||||
|
||||
func (bp *BehaviourPack) AddBlock(block protocol.BlockEntry) {
|
||||
ns, _ := ns_name_split(block.Name)
|
||||
if ns == "minecraft" {
|
||||
return
|
||||
}
|
||||
entry := blockBehaviour{
|
||||
FormatVersion: bp.formatVersion,
|
||||
MinecraftBlock: world.ParseBlock(block),
|
||||
}
|
||||
|
||||
bp.blocks = append(bp.blocks, entry)
|
||||
}
|
|
@ -0,0 +1,142 @@
|
|||
package behaviourpack
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/bedrock-tool/bedrocktool/utils"
|
||||
"github.com/sandertv/gophertunnel/minecraft/resource"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type BehaviourPack struct {
|
||||
formatVersion string
|
||||
Manifest *resource.Manifest
|
||||
blocks []blockBehaviour
|
||||
items map[string]itemBehaviour
|
||||
entities map[string]entityBehaviour
|
||||
}
|
||||
|
||||
func New(name string) *BehaviourPack {
|
||||
return &BehaviourPack{
|
||||
formatVersion: "1.16.0",
|
||||
Manifest: &resource.Manifest{
|
||||
FormatVersion: 2,
|
||||
Header: resource.Header{
|
||||
Name: name,
|
||||
Description: "Adds Blocks, Items and Entities from the server to this world",
|
||||
UUID: utils.RandSeededUUID(name + "_datapack"),
|
||||
Version: [3]int{1, 0, 0},
|
||||
MinimumGameVersion: [3]int{1, 19, 50},
|
||||
},
|
||||
Modules: []resource.Module{
|
||||
{
|
||||
Type: "data",
|
||||
UUID: utils.RandSeededUUID(name + "_data_module"),
|
||||
Description: "Datapack",
|
||||
Version: [3]int{1, 0, 0},
|
||||
},
|
||||
},
|
||||
Dependencies: []resource.Dependency{},
|
||||
Capabilities: []resource.Capability{},
|
||||
},
|
||||
blocks: []blockBehaviour{},
|
||||
items: make(map[string]itemBehaviour),
|
||||
entities: make(map[string]entityBehaviour),
|
||||
}
|
||||
}
|
||||
|
||||
func (bp *BehaviourPack) AddDependency(id string, ver [3]int) {
|
||||
bp.Manifest.Dependencies = append(bp.Manifest.Dependencies, resource.Dependency{
|
||||
UUID: id,
|
||||
Version: ver,
|
||||
})
|
||||
}
|
||||
|
||||
func (bp *BehaviourPack) CheckAddLink(pack utils.Pack) {
|
||||
z, err := zip.NewReader(pack, int64(pack.Len()))
|
||||
if err != nil {
|
||||
logrus.Error(err)
|
||||
return
|
||||
}
|
||||
if len(bp.blocks) > 0 {
|
||||
_, err = z.Open("blocks.json")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
h := pack.Manifest().Header
|
||||
bp.AddDependency(h.UUID, h.Version)
|
||||
}
|
||||
|
||||
func (bp *BehaviourPack) HasBlocks() bool {
|
||||
return len(bp.blocks) > 0
|
||||
}
|
||||
|
||||
func (bp *BehaviourPack) HasItems() bool {
|
||||
return len(bp.items) > 0
|
||||
}
|
||||
|
||||
func (bp *BehaviourPack) HasContent() bool {
|
||||
return bp.HasBlocks() || bp.HasItems()
|
||||
}
|
||||
|
||||
func ns_name_split(identifier string) (ns, name string) {
|
||||
ns_name := strings.Split(identifier, ":")
|
||||
return ns_name[0], ns_name[len(ns_name)-1]
|
||||
}
|
||||
|
||||
func (bp *BehaviourPack) Save(fpath string) error {
|
||||
if err := utils.WriteManifest(bp.Manifest, fpath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_add_thing := func(base, identifier string, thing any) error {
|
||||
ns, name := ns_name_split(identifier)
|
||||
thing_dir := path.Join(base, ns)
|
||||
os.Mkdir(thing_dir, 0o755)
|
||||
w, err := os.Create(path.Join(thing_dir, name+".json"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
e := json.NewEncoder(w)
|
||||
e.SetIndent("", "\t")
|
||||
return e.Encode(thing)
|
||||
}
|
||||
|
||||
if len(bp.blocks) > 0 { // blocks
|
||||
blocks_dir := path.Join(fpath, "blocks")
|
||||
os.Mkdir(blocks_dir, 0o755)
|
||||
for _, be := range bp.blocks {
|
||||
err := _add_thing(blocks_dir, be.MinecraftBlock.Description.Identifier, be)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(bp.items) > 0 { // items
|
||||
items_dir := path.Join(fpath, "items")
|
||||
os.Mkdir(items_dir, 0o755)
|
||||
for _, ib := range bp.items {
|
||||
err := _add_thing(items_dir, ib.MinecraftItem.Description.Identifier, ib)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(bp.entities) > 0 { // items
|
||||
items_dir := path.Join(fpath, "entities")
|
||||
os.Mkdir(items_dir, 0o755)
|
||||
for _, eb := range bp.entities {
|
||||
err := _add_thing(items_dir, eb.MinecraftEntity.Description.Identifier, eb)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
package behaviourpack
|
||||
|
||||
import (
|
||||
"github.com/sandertv/gophertunnel/minecraft/protocol"
|
||||
)
|
||||
|
||||
type EntityDescription struct {
|
||||
Identifier string `json:"identifier"`
|
||||
Spawnable bool `json:"is_spawnable"`
|
||||
Summonable bool `json:"is_summonable"`
|
||||
Experimental bool `json:"is_experimental"`
|
||||
}
|
||||
|
||||
type MinecraftEntity struct {
|
||||
Description EntityDescription `json:"description"`
|
||||
ComponentGroups map[string]any `json:"component_groups"`
|
||||
Components map[string]any `json:"components"`
|
||||
Events map[string]any `json:"events,omitempty"`
|
||||
}
|
||||
|
||||
type entityBehaviour struct {
|
||||
FormatVersion string `json:"format_version"`
|
||||
MinecraftEntity MinecraftEntity `json:"minecraft:entity"`
|
||||
}
|
||||
|
||||
type EntityIn struct {
|
||||
Identifier string
|
||||
Attr []protocol.AttributeValue
|
||||
Meta protocol.EntityMetadata
|
||||
}
|
||||
|
||||
func (bp *BehaviourPack) AddEntity(entity EntityIn) {
|
||||
ns, _ := ns_name_split(entity.Identifier)
|
||||
if ns == "minecraft" {
|
||||
return
|
||||
}
|
||||
|
||||
if _, ok := bp.entities[entity.Identifier]; ok {
|
||||
return
|
||||
}
|
||||
|
||||
entry := entityBehaviour{
|
||||
FormatVersion: bp.formatVersion,
|
||||
MinecraftEntity: MinecraftEntity{
|
||||
Description: EntityDescription{
|
||||
Identifier: entity.Identifier,
|
||||
Spawnable: true,
|
||||
Summonable: true,
|
||||
Experimental: true,
|
||||
},
|
||||
ComponentGroups: make(map[string]any),
|
||||
Components: make(map[string]any),
|
||||
Events: nil,
|
||||
},
|
||||
}
|
||||
for _, av := range entity.Attr {
|
||||
switch av.Name {
|
||||
case "minecraft:health":
|
||||
entry.MinecraftEntity.Components["minecraft:health"] = map[string]int{
|
||||
"value": int(av.Value),
|
||||
"max": int(av.Max),
|
||||
}
|
||||
case "minecraft:movement":
|
||||
entry.MinecraftEntity.Components["minecraft:movement"] = map[string]any{
|
||||
"value": av.Value,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if scale, ok := entity.Meta[protocol.EntityDataKeyScale].(float32); ok {
|
||||
entry.MinecraftEntity.Components["minecraft:scale"] = map[string]any{
|
||||
"value": scale,
|
||||
}
|
||||
}
|
||||
|
||||
hasCollision := entity.Meta.Flag(protocol.EntityDataKeyFlags, protocol.EntityDataFlagHasCollision)
|
||||
hasGravity := entity.Meta.Flag(protocol.EntityDataKeyFlags, protocol.EntityDataFlagHasGravity)
|
||||
entry.MinecraftEntity.Components["minecraft:physics"] = map[string]any{
|
||||
"has_collision": hasCollision,
|
||||
"has_gravity": hasGravity,
|
||||
}
|
||||
|
||||
bp.entities[entity.Identifier] = entry
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
package behaviourpack
|
||||
|
||||
import "github.com/sandertv/gophertunnel/minecraft/protocol"
|
||||
|
||||
type itemDescription struct {
|
||||
Category string `json:"category"`
|
||||
Identifier string `json:"identifier"`
|
||||
IsExperimental bool `json:"is_experimental"`
|
||||
}
|
||||
|
||||
type minecraftItem struct {
|
||||
Description itemDescription `json:"description"`
|
||||
Components map[string]any `json:"components,omitempty"`
|
||||
}
|
||||
|
||||
type itemBehaviour struct {
|
||||
FormatVersion string `json:"format_version"`
|
||||
MinecraftItem minecraftItem `json:"minecraft:item"`
|
||||
}
|
||||
|
||||
func (bp *BehaviourPack) AddItem(item protocol.ItemEntry) {
|
||||
ns, _ := ns_name_split(item.Name)
|
||||
if ns == "minecraft" {
|
||||
return
|
||||
}
|
||||
|
||||
entry := itemBehaviour{
|
||||
FormatVersion: bp.formatVersion,
|
||||
MinecraftItem: minecraftItem{
|
||||
Description: itemDescription{
|
||||
Identifier: item.Name,
|
||||
IsExperimental: true,
|
||||
},
|
||||
Components: make(map[string]any),
|
||||
},
|
||||
}
|
||||
bp.items[item.Name] = entry
|
||||
}
|
||||
|
||||
func (bp *BehaviourPack) ApplyComponentEntries(entries []protocol.ItemComponentEntry) {
|
||||
for _, ice := range entries {
|
||||
item, ok := bp.items[ice.Name]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
item.MinecraftItem.Components = ice.Data
|
||||
}
|
||||
}
|
|
@ -1,10 +1,42 @@
|
|||
package utils
|
||||
|
||||
import "github.com/google/subcommands"
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
|
||||
var ValidCMDs = make(map[string]string, 0)
|
||||
"github.com/google/subcommands"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func RegisterCommand(sub subcommands.Command) {
|
||||
subcommands.Register(sub, "")
|
||||
ValidCMDs[sub.Name()] = sub.Synopsis()
|
||||
var ValidCMDs = make(map[string]Command, 0)
|
||||
|
||||
type Command interface {
|
||||
Name() string
|
||||
Synopsis() string
|
||||
SetFlags(f *flag.FlagSet)
|
||||
Execute(ctx context.Context, ui UI) error
|
||||
}
|
||||
|
||||
type cmdWrap struct {
|
||||
subcommands.Command
|
||||
|
||||
cmd Command
|
||||
}
|
||||
|
||||
func (c *cmdWrap) Name() string { return c.cmd.Name() }
|
||||
func (c *cmdWrap) Synopsis() string { return c.cmd.Synopsis() }
|
||||
func (c *cmdWrap) SetFlags(f *flag.FlagSet) { c.cmd.SetFlags(f) }
|
||||
func (c *cmdWrap) Usage() string { return c.Name() + ": " + c.Synopsis() }
|
||||
func (c *cmdWrap) Execute(ctx context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
|
||||
err := c.cmd.Execute(ctx, CurrentUI)
|
||||
if err != nil {
|
||||
logrus.Error(err)
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func RegisterCommand(sub Command) {
|
||||
subcommands.Register(&cmdWrap{cmd: sub}, "")
|
||||
ValidCMDs[sub.Name()] = sub
|
||||
}
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
package crypt
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
_ "embed"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/openpgp"
|
||||
"golang.org/x/crypto/openpgp/armor"
|
||||
"golang.org/x/crypto/openpgp/packet"
|
||||
)
|
||||
|
||||
//go:embed key.gpg
|
||||
var key_gpg []byte
|
||||
var recipients []*openpgp.Entity
|
||||
|
||||
func init() {
|
||||
block, err := armor.Decode(bytes.NewBuffer(key_gpg))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
recip, err := openpgp.ReadEntity(packet.NewReader(block.Body))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
recipients = append(recipients, recip)
|
||||
}
|
||||
|
||||
func Enc(name string, data []byte) ([]byte, error) {
|
||||
w := bytes.NewBuffer(nil)
|
||||
wc, err := openpgp.Encrypt(w, recipients, nil, &openpgp.FileHints{
|
||||
IsBinary: true, FileName: name, ModTime: time.Now(),
|
||||
}, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err = wc.Write(data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wc.Close()
|
||||
return w.Bytes(), nil
|
||||
}
|
||||
|
||||
func Encer(name string, w io.Writer) (io.WriteCloser, error) {
|
||||
wc, err := openpgp.Encrypt(w, recipients, nil, &openpgp.FileHints{
|
||||
IsBinary: true, FileName: name, ModTime: time.Now(),
|
||||
}, nil)
|
||||
return wc, err
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
mQGNBGHqFb8BDADdhaCCihGEuMNtgHo931t2/H6D6I/j+kYZkgt54XeQqTQVLkaW
|
||||
FzL/MkLB5Hu3CQl1BlCdg8wRyOLdLOyhwtiAiFGcmUel28eIM+Y/Hcr+cUErGrEd
|
||||
M3r2VjCLytgQeX+jip/Wu/xVrizsCwuy4oxJN1DtAuUMNjP78TBdIa9MJfc68NTW
|
||||
YAbvG6XT3CJaTHQwWGMUC500Jtg647aRMCdWS1JXQqZiIdqFy/dFCgpfRycg9vpz
|
||||
RbxQ+EDZ23GDljIcDeaCJ6WWBMIw2pSdTA6psGl4FMeGfHGWaMPudpm5AATG/X1W
|
||||
bKzhs4JEDcXwu6Si65j7I6BiaxuARfBgRnLBOPhAt+TE0K6jzUcIQHMeLBWk1rfw
|
||||
wP7hK76OGQiLY4BvpMWyuNXGHy3Le1uePknNBVZppusSgfvm7TUytBg7RfWvw2Dr
|
||||
j+N07p6KbAn6NelewsybCyeh0k94FvaAtjodjDo1sIogiqLYWleAH/IAF2jFREq6
|
||||
oGbix9d5UBX+SNEAEQEAAbQdb2xlYmVjayA8b2xlYmVja0BvbGViZWNrLmNvbT6J
|
||||
Ac4EEwEIADgWIQSGV2qyDgjKdVHooj7U/i4We6MHnQUCYeoVvwIbAwULCQgHAgYV
|
||||
CgkICwIEFgIDAQIeAQIXgAAKCRDU/i4We6MHnbEzDACrA05J9HvBiQSNXRuVcgT3
|
||||
5I5brahyhtGdGbbmanvYBNdABkmXsXcVErF4hYASIkQfSZ0uui0kOnmsQRjoKuIX
|
||||
+agp5d9/S67gwdkafPjkj/vBtCmpFdoNoe24njvlNY0Wd6dYhE0jqCk95ZX0E+AR
|
||||
J3L1t+f9uFB5GfyVU9oBpSXqZsJD4AoDa7nPz5vNVm3cxPKivlXv1Q5HV96Ngk8R
|
||||
6Y+hIk8vF5YJ5Q7HLf4xAzqHgbNo8IkbsPAg1uiLozo6bQD0vh8ash9bBycmWujl
|
||||
jKpDitqPRjNsOhXQ322v90QR2s9mHRyJdi4duSXWPCKlsoTNL4o5Kd/AX/qR1hB8
|
||||
4Ml7rTI0LDUI5vf2K9p2lxD45ZwJyI8VrORvzjdQcddvtJge6MVQyRktrzEkSeuc
|
||||
sAjW2xcswgHChmP8f56gLUTZZmAk5TK3A61UkJW8oU0qNBRl4j+Yd84vonW2Dlm2
|
||||
V20cNmBg7qg2Uldn0TAQrKtzLVQsRp46pFOP6RWcMbO5AY0EYeoVvwEMALo7jQBr
|
||||
Jtjr2C/abVAmI/ToEif3MPcFv/LkBLuEttTTkDJw9fdj3y/iuqy5EEyG07J8t/+z
|
||||
PjDLgLUuyNCpxobi80b7GgukDTeiw94ezTyIIXxtb7aYBlHZ/uDecJzNukZ4yNIx
|
||||
mX/YrUx4isV1APqa66y+eH0aGBZQIZ1sOPeGgsPOVZmIjFDPWEPqBBCQLD96M/Kd
|
||||
FTRYM8SslnLxBfeX7iye4ZVmLgGyWrJq4cG88Rj++cnp4XI25pJoNgE4GHoQYuep
|
||||
XazwU8PaJgudQyynIDCrsgDEiCukAY7ZoiVmLSmxWVNNNAdXYKWW88XUMZ0raSSB
|
||||
H9AsPK4c2YSd916E93nbL3TSiYkVt3Ahty9VAwThoYHZ4Z8/ddD5t6HRTR+/tZn4
|
||||
JLMskr2FuCb9lR+jOGCp3jW7UezX6G3SLJFB/afBqjhPhurm8Dz63psxIsUZPMzb
|
||||
yzDXXMjo5lfe1yNXs/joHq4ni74ASpSheO6Kigj+N29hQEZ5AvgkBke/vwARAQAB
|
||||
iQG2BBgBCAAgFiEEhldqsg4IynVR6KI+1P4uFnujB50FAmHqFb8CGwwACgkQ1P4u
|
||||
FnujB52j3gwAyO7tBmpNy2NF+LumtUhsB8QYKAs2Xwo7WNQMdYkKrFMD3umXI6n2
|
||||
BpnfpoJWKeA9HOwJZwdaEggvzcZw2/KPLOW0L2XEOLMoDsJMLQkfaw9ewG9A//em
|
||||
E0RzTXP1vdbIVdjbNsNmfGa5MiniNDt0khiOkC6u/IXu767vTrVxQwwBvbj/Jhjz
|
||||
amCuwdFDl4SsadsCm8amYKRFi9k9j2jkYRJSy3KomG7b+2ZfUbmdJoL+NnIivVFZ
|
||||
AN6WpWhC0Usxgm5xjLLi5f0DlnkOIdPiq8oajA7iCuCGoJvYMZddGigdRdJCZmaR
|
||||
h19ELdyh/t21ySGCOckkDNQ4cGcm4mm8hilHkJDNsTJ/ACQFjzNyg9mwZ+80fjFa
|
||||
Wnl6U3bkPsUJsI5vfgVb2td51mxEe6DYd5MTDiKkchlbO+J3vQ0FOb6xkuVSJp0W
|
||||
ZNl7HmnETLuKjemNoW8Gj0IB0AipLwrisORbYpee1mN2YDasr+0cK5ADIuBXvx6f
|
||||
cNMCuTrO1l7m
|
||||
=AZpZ
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
18
utils/dns.go
18
utils/dns.go
|
@ -4,11 +4,12 @@ import (
|
|||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/bedrock-tool/bedrocktool/locale"
|
||||
"github.com/miekg/dns"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var override_dns = map[string]bool{
|
||||
var overrideDNS = map[string]bool{
|
||||
"geo.hivebedrock.network.": true,
|
||||
}
|
||||
|
||||
|
@ -23,15 +24,15 @@ func (d *DNSServer) answerQuery(remote net.Addr, req *dns.Msg) (reply *dns.Msg)
|
|||
case dns.TypeA:
|
||||
logrus.Infof("Query for %s", q.Name)
|
||||
|
||||
if override_dns[q.Name] {
|
||||
if overrideDNS[q.Name] {
|
||||
host, _, _ := net.SplitHostPort(remote.String())
|
||||
remote_ip := net.ParseIP(host)
|
||||
remoteIP := net.ParseIP(host)
|
||||
|
||||
addrs, _ := net.InterfaceAddrs()
|
||||
var ip string
|
||||
for _, addr := range addrs {
|
||||
if ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
|
||||
if ipnet.Contains(remote_ip) {
|
||||
if ipnet.Contains(remoteIP) {
|
||||
ip = ipnet.IP.String()
|
||||
}
|
||||
}
|
||||
|
@ -83,17 +84,20 @@ func (d *DNSServer) handler(w dns.ResponseWriter, req *dns.Msg) {
|
|||
}
|
||||
|
||||
func InitDNS() {
|
||||
if !Options.EnableDNS {
|
||||
return
|
||||
}
|
||||
d := DNSServer{}
|
||||
dns.HandleFunc(".", d.handler)
|
||||
|
||||
server := &dns.Server{Addr: ":53", Net: "udp"}
|
||||
go func() {
|
||||
logrus.Infof("Starting dns at %s:53\n", GetLocalIP())
|
||||
logrus.Infof(locale.Loc("starting_dns", locale.Strmap{"Ip": GetLocalIP()}))
|
||||
err := server.ListenAndServe()
|
||||
defer server.Shutdown()
|
||||
if err != nil {
|
||||
logrus.Warnf("Failed to start dns server: %s\n ", err.Error())
|
||||
logrus.Info("you may have to use bedrockconnect")
|
||||
logrus.Warnf(locale.Loc("failed_to_start_dns", locale.Strmap{"Err": err.Error()}))
|
||||
logrus.Info(locale.Loc("suggest_bedrockconnect", nil))
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
|
|
@ -3,6 +3,8 @@ package utils
|
|||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
"image/png"
|
||||
"os"
|
||||
"reflect"
|
||||
"unsafe"
|
||||
)
|
||||
|
@ -14,6 +16,18 @@ func Img2rgba(img *image.RGBA) []color.RGBA {
|
|||
return *(*[]color.RGBA)(unsafe.Pointer(&header))
|
||||
}
|
||||
|
||||
func loadPng(path string) (*image.RGBA, error) {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
img, err := png.Decode(f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return (*image.RGBA)(img.(*image.NRGBA)), err
|
||||
}
|
||||
|
||||
// LERP is a linear interpolation function
|
||||
func LERP(p1, p2, alpha float64) float64 {
|
||||
return (1-alpha)*p1 + alpha*p2
|
||||
|
@ -34,3 +48,17 @@ func BlendColors(c1, c2 color.RGBA) (ret color.RGBA) {
|
|||
ret.A = blendAlphaValue(c1.A, c2.A)
|
||||
return ret
|
||||
}
|
||||
|
||||
// DrawImgScaledPos draws src onto dst at bottomLeft, scaled to size
|
||||
func DrawImgScaledPos(dst *image.RGBA, src *image.RGBA, bottomLeft image.Point, sizeScaled int) {
|
||||
sbx := src.Bounds().Dx()
|
||||
ratio := int(float64(sbx) / float64(sizeScaled))
|
||||
|
||||
for xOut := bottomLeft.X; xOut < bottomLeft.X+sizeScaled; xOut++ {
|
||||
for yOut := bottomLeft.Y; yOut < bottomLeft.Y+sizeScaled; yOut++ {
|
||||
xIn := (xOut - bottomLeft.X) * ratio
|
||||
yIn := (yOut - bottomLeft.Y) * ratio
|
||||
dst.Set(xOut, yOut, src.At(xIn, yIn))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,10 +9,11 @@ import (
|
|||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/bedrock-tool/bedrocktool/locale"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func User_input(ctx context.Context, q string) (string, bool) {
|
||||
func UserInput(ctx context.Context, q string) (string, bool) {
|
||||
c := make(chan string)
|
||||
go func() {
|
||||
fmt.Print(q)
|
||||
|
@ -31,47 +32,65 @@ func User_input(ctx context.Context, q string) (string, bool) {
|
|||
}
|
||||
}
|
||||
|
||||
func server_url_to_name(server string) string {
|
||||
func serverGetHostname(server string) string {
|
||||
host, _, err := net.SplitHostPort(server)
|
||||
if err != nil {
|
||||
logrus.Fatalf("Invalid server: %s", err)
|
||||
logrus.Fatalf(locale.Loc("invalid_server", locale.Strmap{"Err": err.Error()}))
|
||||
}
|
||||
return host
|
||||
}
|
||||
|
||||
func ServerInput(ctx context.Context, server string) (address, name string, err error) {
|
||||
if server == "" { // no arg provided, interactive input
|
||||
var (
|
||||
realmRegex = regexp.MustCompile("realm:(?P<Name>.*)(?::(?P<ID>.*))?")
|
||||
pcapRegex = regexp.MustCompile(`(?P<Filename>(?P<Name>.*)\.pcap2)(?:\?(?P<Args>.*))?`)
|
||||
)
|
||||
|
||||
func regexGetParams(r *regexp.Regexp, s string) (params map[string]string) {
|
||||
match := r.FindStringSubmatch(s)
|
||||
params = make(map[string]string)
|
||||
for i, name := range r.SubexpNames() {
|
||||
if i > 0 && i <= len(match) {
|
||||
params[name] = match[i]
|
||||
}
|
||||
}
|
||||
return params
|
||||
}
|
||||
|
||||
func ServerInput(ctx context.Context, server string) (string, string, error) {
|
||||
// no arg provided, interactive input
|
||||
if server == "" {
|
||||
var cancelled bool
|
||||
server, cancelled = User_input(ctx, "Enter Server: ")
|
||||
server, cancelled = UserInput(ctx, locale.Loc("enter_server", nil))
|
||||
if cancelled {
|
||||
return "", "", context.Canceled
|
||||
}
|
||||
}
|
||||
|
||||
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(context.Background(), GetRealmsApi(), realm_info[1], id)
|
||||
// realm
|
||||
if realmRegex.MatchString(server) {
|
||||
p := regexGetParams(realmRegex, server)
|
||||
name, address, err := getRealm(ctx, p["Name"], p["ID"])
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
name = CleanupName(name)
|
||||
} else if strings.HasSuffix(server, ".pcap") || strings.HasSuffix(server, ".pcap2") {
|
||||
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, CleanupName(name), nil
|
||||
}
|
||||
|
||||
return address, name, nil
|
||||
// old pcap format
|
||||
if match, _ := regexp.MatchString(`.*\.pcap$`, server); match {
|
||||
return "", "", fmt.Errorf(locale.Loc("not_supported_anymore", nil))
|
||||
}
|
||||
|
||||
// new pcap format
|
||||
|
||||
if pcapRegex.MatchString(server) {
|
||||
p := regexGetParams(pcapRegex, server)
|
||||
return "PCAP!" + p["Filename"], p["Name"], nil
|
||||
}
|
||||
|
||||
// normal server dns or ip
|
||||
if len(strings.Split(server, ":")) == 1 {
|
||||
server += ":19132"
|
||||
}
|
||||
return server, serverGetHostname(server), nil
|
||||
}
|
||||
|
|
|
@ -0,0 +1,91 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/bedrock-tool/bedrocktool/locale"
|
||||
"github.com/bedrock-tool/bedrocktool/ui/messages"
|
||||
"github.com/google/subcommands"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type UI interface {
|
||||
Init() bool
|
||||
Start(context.Context, context.CancelFunc) error
|
||||
Message(name string, data interface{}) messages.MessageResponse
|
||||
ServerInput(context.Context, string) (string, string, error)
|
||||
}
|
||||
|
||||
type BaseUI struct {
|
||||
UI
|
||||
}
|
||||
|
||||
func (u *BaseUI) Message(name string, data interface{}) messages.MessageResponse {
|
||||
return messages.MessageResponse{
|
||||
Ok: false,
|
||||
Data: nil,
|
||||
}
|
||||
}
|
||||
|
||||
func (u *BaseUI) ServerInput(ctx context.Context, server string) (string, string, error) {
|
||||
address, name, err := ServerInput(ctx, server)
|
||||
return address, name, err
|
||||
}
|
||||
|
||||
var CurrentUI UI
|
||||
|
||||
func SetCurrentUI(ui UI) {
|
||||
CurrentUI = ui
|
||||
}
|
||||
|
||||
type InteractiveCLI struct {
|
||||
BaseUI
|
||||
}
|
||||
|
||||
func (c *InteractiveCLI) Init() bool {
|
||||
CurrentUI = c
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *InteractiveCLI) Start(ctx context.Context, cancel context.CancelFunc) error {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
default:
|
||||
fmt.Println(locale.Loc("available_commands", nil))
|
||||
for name, cmd := range ValidCMDs {
|
||||
fmt.Printf("\t%s\t%s\n", name, cmd.Synopsis())
|
||||
}
|
||||
fmt.Println(locale.Loc("use_to_run_command", nil))
|
||||
|
||||
cmd, cancelled := UserInput(ctx, locale.Loc("input_command", nil))
|
||||
if cancelled {
|
||||
cancel()
|
||||
return nil
|
||||
}
|
||||
_cmd := strings.Split(cmd, " ")
|
||||
os.Args = append(os.Args, _cmd...)
|
||||
}
|
||||
flag.Parse()
|
||||
|
||||
InitDNS()
|
||||
InitExtraDebug(ctx)
|
||||
|
||||
subcommands.Execute(ctx)
|
||||
|
||||
if Options.IsInteractive {
|
||||
logrus.Info(locale.Loc("enter_to_exit", nil))
|
||||
input := bufio.NewScanner(os.Stdin)
|
||||
input.Scan()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var MakeGui = func() UI {
|
||||
return &InteractiveCLI{}
|
||||
}
|
|
@ -17,7 +17,7 @@ var PrivateIPNetworks = []net.IPNet{
|
|||
},
|
||||
}
|
||||
|
||||
// check if ip is private
|
||||
// IPPrivate checks if ip is private
|
||||
func IPPrivate(ip net.IP) bool {
|
||||
for _, ipNet := range PrivateIPNetworks {
|
||||
if ipNet.Contains(ip) {
|
||||
|
|
|
@ -2,9 +2,15 @@ package utils
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/bedrock-tool/bedrocktool/locale"
|
||||
"github.com/fatih/color"
|
||||
"github.com/sandertv/gophertunnel/minecraft/protocol"
|
||||
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
|
||||
|
@ -12,7 +18,7 @@ import (
|
|||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
var Pool = packet.NewPool()
|
||||
var pool = packet.NewPool()
|
||||
|
||||
var MutedPackets = []string{
|
||||
"packet.UpdateBlock",
|
||||
|
@ -42,52 +48,175 @@ var MutedPackets = []string{
|
|||
"packet.PlaySound",
|
||||
}
|
||||
|
||||
var ExtraVerbose []string
|
||||
var (
|
||||
ExtraVerbose []string
|
||||
FLog io.Writer
|
||||
dmpLock sync.Mutex
|
||||
)
|
||||
|
||||
func dmpStruct(level int, inputStruct any, withType bool, isInList bool) (s string) {
|
||||
tBase := strings.Repeat("\t", level)
|
||||
|
||||
if inputStruct == nil {
|
||||
return "nil"
|
||||
}
|
||||
|
||||
ii := reflect.Indirect(reflect.ValueOf(inputStruct))
|
||||
typeName := reflect.TypeOf(inputStruct).String()
|
||||
typeString := ""
|
||||
if withType {
|
||||
if slices.Contains([]string{"bool", "string"}, typeName) {
|
||||
} else {
|
||||
typeString = typeName + " "
|
||||
}
|
||||
}
|
||||
|
||||
if strings.HasPrefix(typeName, "protocol.Optional") {
|
||||
v := ii.MethodByName("Value").Call(nil)
|
||||
val, set := v[0], v[1]
|
||||
if !set.Bool() {
|
||||
s += typeName + " Not Set"
|
||||
} else {
|
||||
s += typeName + "{\n" + tBase + "\t"
|
||||
s += dmpStruct(level+1, val.Interface(), false, false)
|
||||
s += "\n" + tBase + "}"
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if ii.Kind() == reflect.Struct {
|
||||
if ii.NumField() == 0 {
|
||||
s += typeName + "{}"
|
||||
} else {
|
||||
s += typeName + "{\n"
|
||||
for i := 0; i < ii.NumField(); i++ {
|
||||
fieldType := ii.Type().Field(i)
|
||||
|
||||
if fieldType.IsExported() {
|
||||
field := ii.Field(i).Interface()
|
||||
d := dmpStruct(level+1, field, true, false)
|
||||
s += tBase + fmt.Sprintf("\t%s = %s\n", fieldType.Name, d)
|
||||
} else {
|
||||
s += tBase + " " + fieldType.Name + " (unexported)"
|
||||
}
|
||||
}
|
||||
s += tBase + "}"
|
||||
}
|
||||
} else if ii.Kind() == reflect.Slice {
|
||||
var t reflect.Type
|
||||
is_elem_struct := false
|
||||
if ii.Len() > 0 {
|
||||
e := ii.Index(0)
|
||||
t = reflect.TypeOf(e.Interface())
|
||||
is_elem_struct = t.Kind() == reflect.Struct
|
||||
}
|
||||
|
||||
if ii.Len() > 1000 {
|
||||
s += "[<slice too long>]"
|
||||
} else if ii.Len() == 0 {
|
||||
s += typeString + "[]"
|
||||
} else {
|
||||
s += typeString + "["
|
||||
if is_elem_struct {
|
||||
s += "\n"
|
||||
}
|
||||
for i := 0; i < ii.Len(); i++ {
|
||||
if is_elem_struct {
|
||||
s += tBase + "\t"
|
||||
}
|
||||
s += dmpStruct(level+1, ii.Index(i).Interface(), false, true)
|
||||
if is_elem_struct {
|
||||
s += "\n"
|
||||
} else {
|
||||
s += " "
|
||||
}
|
||||
}
|
||||
if is_elem_struct {
|
||||
s += tBase
|
||||
}
|
||||
s += "]"
|
||||
}
|
||||
} else if ii.Kind() == reflect.Map {
|
||||
j, err := json.MarshalIndent(ii.Interface(), tBase, "\t")
|
||||
if err != nil {
|
||||
s += err.Error()
|
||||
}
|
||||
s += string(j)
|
||||
} else {
|
||||
is_array := ii.Kind() == reflect.Array
|
||||
if !isInList && !is_array {
|
||||
s += typeString
|
||||
}
|
||||
s += fmt.Sprintf("%#v", ii.Interface())
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func DumpStruct(data interface{}) {
|
||||
if FLog == nil {
|
||||
return
|
||||
}
|
||||
|
||||
FLog.Write([]byte(dmpStruct(0, data, true, false)))
|
||||
FLog.Write([]byte("\n\n\n"))
|
||||
}
|
||||
|
||||
func PacketLogger(header packet.Header, payload []byte, src, dst net.Addr) {
|
||||
var pk packet.Packet
|
||||
if pkFunc, ok := Pool[header.PacketID]; ok {
|
||||
if pkFunc, ok := pool[header.PacketID]; ok {
|
||||
pk = pkFunc()
|
||||
} else {
|
||||
pk = &packet.Unknown{PacketID: header.PacketID}
|
||||
}
|
||||
|
||||
if pk.ID() == packet.IDRequestNetworkSettings {
|
||||
ClientAddr = src
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if recoveredErr := recover(); recoveredErr != nil {
|
||||
logrus.Errorf("%T: %w", pk, recoveredErr.(error))
|
||||
logrus.Errorf("%T: %w", pk, recoveredErr)
|
||||
}
|
||||
}()
|
||||
|
||||
pk.Unmarshal(protocol.NewReader(bytes.NewBuffer(payload), 0))
|
||||
|
||||
pk_name := reflect.TypeOf(pk).String()[1:]
|
||||
if slices.Contains(MutedPackets, pk_name) {
|
||||
if FLog != nil {
|
||||
dmpLock.Lock()
|
||||
defer dmpLock.Unlock()
|
||||
FLog.Write([]byte(dmpStruct(0, pk, true, false)))
|
||||
FLog.Write([]byte("\n\n\n"))
|
||||
}
|
||||
|
||||
pkName := reflect.TypeOf(pk).String()[1:]
|
||||
if slices.Contains(MutedPackets, pkName) {
|
||||
return
|
||||
}
|
||||
|
||||
switch pk := pk.(type) {
|
||||
case *packet.Disconnect:
|
||||
logrus.Infof("Disconnect: %s", pk.Message)
|
||||
logrus.Infof(locale.Loc("disconnect", locale.Strmap{"Pk": pk}))
|
||||
}
|
||||
|
||||
dir_S2C := color.GreenString("S") + "->" + color.CyanString("C")
|
||||
dir_C2S := color.CyanString("C") + "->" + color.GreenString("S")
|
||||
var dir string = dir_S2C
|
||||
dirS2C := color.GreenString("S") + "->" + color.CyanString("C")
|
||||
dirC2S := color.CyanString("C") + "->" + color.GreenString("S")
|
||||
var dir string = dirS2C
|
||||
|
||||
if Client_addr != nil {
|
||||
if src == Client_addr {
|
||||
dir = dir_C2S
|
||||
if ClientAddr != nil {
|
||||
if src == ClientAddr {
|
||||
dir = dirC2S
|
||||
}
|
||||
} else {
|
||||
src_addr, _, _ := net.SplitHostPort(src.String())
|
||||
if IPPrivate(net.ParseIP(src_addr)) {
|
||||
dir = dir_C2S
|
||||
srcAddr, _, _ := net.SplitHostPort(src.String())
|
||||
if IPPrivate(net.ParseIP(srcAddr)) {
|
||||
dir = dirS2C
|
||||
}
|
||||
}
|
||||
|
||||
logrus.Debugf("%s 0x%02x, %s", dir, pk.ID(), pk_name)
|
||||
logrus.Debugf("%s 0x%02x, %s", dir, pk.ID(), pkName)
|
||||
|
||||
if slices.Contains(ExtraVerbose, pk_name) {
|
||||
if slices.Contains(ExtraVerbose, pkName) {
|
||||
logrus.Debugf("%+v", pk)
|
||||
}
|
||||
}
|
||||
|
|
323
utils/proxy.go
323
utils/proxy.go
|
@ -2,20 +2,26 @@ package utils
|
|||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/bedrock-tool/bedrocktool/locale"
|
||||
"github.com/sandertv/gophertunnel/minecraft"
|
||||
"github.com/sandertv/gophertunnel/minecraft/protocol"
|
||||
"github.com/sandertv/gophertunnel/minecraft/protocol/login"
|
||||
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
|
||||
"github.com/sandertv/gophertunnel/minecraft/resource"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var G_disconnect_reason = "Connection lost"
|
||||
var DisconnectReason = "Connection lost"
|
||||
|
||||
type dummyProto struct {
|
||||
id int32
|
||||
|
@ -33,27 +39,127 @@ func (p dummyProto) ConvertFromLatest(pk packet.Packet, _ *minecraft.Conn) []pac
|
|||
return []packet.Packet{pk}
|
||||
}
|
||||
|
||||
type ProxyContext struct {
|
||||
Server *minecraft.Conn
|
||||
Client *minecraft.Conn
|
||||
Listener *minecraft.Listener
|
||||
commands map[string]IngameCommand
|
||||
type (
|
||||
PacketFunc func(header packet.Header, payload []byte, src, dst net.Addr)
|
||||
PacketCallback func(pk packet.Packet, proxy *ProxyContext, toServer bool, timeReceived time.Time) (packet.Packet, error)
|
||||
ClientConnectCallback func(proxy *ProxyContext, hasClient bool)
|
||||
ConnectCallback func(proxy *ProxyContext, err error) bool
|
||||
IngameCommand struct {
|
||||
Exec func(cmdline []string) bool
|
||||
Cmd protocol.Command
|
||||
}
|
||||
)
|
||||
|
||||
log *logrus.Logger
|
||||
type ProxyContext struct {
|
||||
Server *minecraft.Conn
|
||||
Client *minecraft.Conn
|
||||
Listener *minecraft.Listener
|
||||
commands map[string]IngameCommand
|
||||
AlwaysGetPacks bool
|
||||
WithClient bool
|
||||
IgnoreDisconnect bool
|
||||
CustomClientData *login.ClientData
|
||||
|
||||
// called for every packet
|
||||
PacketFunc PacketFunc
|
||||
// called after client connected
|
||||
OnClientConnect ClientConnectCallback
|
||||
// called after game started
|
||||
ConnectCB ConnectCallback
|
||||
// called on every packet after login
|
||||
PacketCB PacketCallback
|
||||
}
|
||||
|
||||
type (
|
||||
PacketFunc func(header packet.Header, payload []byte, src, dst net.Addr)
|
||||
PacketCallback func(pk packet.Packet, proxy *ProxyContext, toServer bool) (packet.Packet, error)
|
||||
ConnectCallback func(proxy *ProxyContext)
|
||||
)
|
||||
func NewProxy() (*ProxyContext, error) {
|
||||
p := &ProxyContext{
|
||||
commands: make(map[string]IngameCommand),
|
||||
WithClient: true,
|
||||
IgnoreDisconnect: false,
|
||||
}
|
||||
if Options.PathCustomUserData != "" {
|
||||
if err := p.LoadCustomUserData(Options.PathCustomUserData); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func (p *ProxyContext) AddCommand(cmd IngameCommand) {
|
||||
p.commands[cmd.Cmd.Name] = cmd
|
||||
}
|
||||
|
||||
type CustomClientData struct {
|
||||
// skin things
|
||||
CapeFilename string
|
||||
SkinFilename string
|
||||
SkinGeometryFilename string
|
||||
SkinID string
|
||||
PlayFabID string
|
||||
PersonaSkin bool
|
||||
PremiumSkin bool
|
||||
TrustedSkin bool
|
||||
ArmSize string
|
||||
SkinColour string
|
||||
|
||||
// misc
|
||||
IsEditorMode bool
|
||||
LanguageCode string
|
||||
DeviceID string
|
||||
}
|
||||
|
||||
func (p *ProxyContext) LoadCustomUserData(path string) error {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var customData CustomClientData
|
||||
err = json.NewDecoder(f).Decode(&customData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p.CustomClientData = &login.ClientData{
|
||||
SkinID: customData.SkinID,
|
||||
PlayFabID: customData.PlayFabID,
|
||||
PersonaSkin: customData.PersonaSkin,
|
||||
PremiumSkin: customData.PremiumSkin,
|
||||
TrustedSkin: customData.TrustedSkin,
|
||||
ArmSize: customData.ArmSize,
|
||||
SkinColour: customData.SkinColour,
|
||||
}
|
||||
|
||||
if customData.SkinFilename != "" {
|
||||
img, err := loadPng(customData.SkinFilename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.CustomClientData.SkinData = base64.RawStdEncoding.EncodeToString(img.Pix)
|
||||
p.CustomClientData.SkinImageWidth = img.Rect.Dx()
|
||||
p.CustomClientData.SkinImageHeight = img.Rect.Dy()
|
||||
}
|
||||
|
||||
if customData.CapeFilename != "" {
|
||||
img, err := loadPng(customData.CapeFilename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.CustomClientData.CapeData = base64.RawStdEncoding.EncodeToString(img.Pix)
|
||||
p.CustomClientData.CapeImageWidth = img.Rect.Dx()
|
||||
p.CustomClientData.CapeImageHeight = img.Rect.Dy()
|
||||
}
|
||||
|
||||
if customData.SkinGeometryFilename != "" {
|
||||
data, err := os.ReadFile(customData.SkinGeometryFilename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.CustomClientData.SkinGeometry = base64.RawStdEncoding.EncodeToString(data)
|
||||
}
|
||||
|
||||
p.CustomClientData.DeviceID = customData.DeviceID
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *ProxyContext) SendMessage(text string) {
|
||||
if p.Client != nil {
|
||||
|
@ -73,16 +179,7 @@ func (p *ProxyContext) SendPopup(text string) {
|
|||
}
|
||||
}
|
||||
|
||||
type IngameCommand struct {
|
||||
Exec func(cmdline []string) bool
|
||||
Cmd protocol.Command
|
||||
}
|
||||
|
||||
func (p *ProxyContext) AddCommand(cmd IngameCommand) {
|
||||
p.commands[cmd.Cmd.Name] = cmd
|
||||
}
|
||||
|
||||
func (p *ProxyContext) CommandHandlerPacketCB(pk packet.Packet, proxy *ProxyContext, toServer bool) (packet.Packet, error) {
|
||||
func (p *ProxyContext) CommandHandlerPacketCB(pk packet.Packet, proxy *ProxyContext, toServer bool, _ time.Time) (packet.Packet, error) {
|
||||
switch _pk := pk.(type) {
|
||||
case *packet.CommandRequest:
|
||||
cmd := strings.Split(_pk.CommandLine, " ")
|
||||
|
@ -105,14 +202,14 @@ func (p *ProxyContext) CommandHandlerPacketCB(pk packet.Packet, proxy *ProxyCont
|
|||
return pk, nil
|
||||
}
|
||||
|
||||
func proxyLoop(ctx context.Context, proxy *ProxyContext, toServer bool, packetCBs []PacketCallback) error {
|
||||
func (p *ProxyContext) proxyLoop(ctx context.Context, toServer bool, packetCBs []PacketCallback) error {
|
||||
var c1, c2 *minecraft.Conn
|
||||
if toServer {
|
||||
c1 = proxy.Client
|
||||
c2 = proxy.Server
|
||||
c1 = p.Client
|
||||
c2 = p.Server
|
||||
} else {
|
||||
c1 = proxy.Server
|
||||
c2 = proxy.Client
|
||||
c1 = p.Server
|
||||
c2 = p.Client
|
||||
}
|
||||
|
||||
for {
|
||||
|
@ -126,16 +223,16 @@ func proxyLoop(ctx context.Context, proxy *ProxyContext, toServer bool, packetCB
|
|||
}
|
||||
|
||||
for _, packetCB := range packetCBs {
|
||||
pk, err = packetCB(pk, proxy, toServer)
|
||||
pk, err = packetCB(pk, p, toServer, time.Now())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if pk != nil {
|
||||
if pk != nil && c2 != nil {
|
||||
if err := c2.WritePacket(pk); err != nil {
|
||||
if disconnect, ok := errors.Unwrap(err).(minecraft.DisconnectError); ok {
|
||||
G_disconnect_reason = disconnect.Error()
|
||||
DisconnectReason = disconnect.Error()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
@ -143,87 +240,99 @@ func proxyLoop(ctx context.Context, proxy *ProxyContext, toServer bool, packetCB
|
|||
}
|
||||
}
|
||||
|
||||
func NewProxy(log *logrus.Logger) *ProxyContext {
|
||||
if log == nil {
|
||||
log = logrus.StandardLogger()
|
||||
}
|
||||
return &ProxyContext{
|
||||
log: log,
|
||||
commands: make(map[string]IngameCommand),
|
||||
}
|
||||
}
|
||||
var ClientAddr net.Addr
|
||||
|
||||
var Client_addr net.Addr
|
||||
|
||||
func (p *ProxyContext) Run(ctx context.Context, server_address string) (err error) {
|
||||
if strings.HasSuffix(server_address, ".pcap") {
|
||||
return fmt.Errorf("not supported anymore")
|
||||
}
|
||||
if strings.HasSuffix(server_address, ".pcap2") {
|
||||
return create_replay_connection(ctx, p.log, server_address, p.ConnectCB, p.PacketCB)
|
||||
func (p *ProxyContext) Run(ctx context.Context, serverAddress string) (err error) {
|
||||
if strings.HasPrefix(serverAddress, "PCAP!") {
|
||||
return createReplayConnection(ctx, serverAddress[5:], p.ConnectCB, p.PacketCB)
|
||||
}
|
||||
|
||||
GetTokenSource() // ask for login before listening
|
||||
var packs []*resource.Pack
|
||||
if G_preload_packs {
|
||||
p.log.Info("Preloading resourcepacks")
|
||||
var serverConn *minecraft.Conn
|
||||
serverConn, err = ConnectServer(ctx, server_address, nil, true, nil)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed to connect to %s: %s", server_address, err)
|
||||
return
|
||||
|
||||
var cdp *login.ClientData = nil
|
||||
if p.WithClient {
|
||||
var packs []*resource.Pack
|
||||
if Options.Preload {
|
||||
logrus.Info(locale.Loc("preloading_packs", nil))
|
||||
var serverConn *minecraft.Conn
|
||||
serverConn, err = connectServer(ctx, serverAddress, nil, true, nil)
|
||||
if err != nil {
|
||||
err = fmt.Errorf(locale.Loc("failed_to_connect", locale.Strmap{"Address": serverAddress, "Err": err}))
|
||||
return
|
||||
}
|
||||
serverConn.Close()
|
||||
packs = serverConn.ResourcePacks()
|
||||
logrus.Infof(locale.Locm("pack_count_loaded", locale.Strmap{"Count": len(packs)}, len(packs)))
|
||||
}
|
||||
serverConn.Close()
|
||||
packs = serverConn.ResourcePacks()
|
||||
p.log.Infof("%d packs loaded", len(packs))
|
||||
|
||||
_status := minecraft.NewStatusProvider("Server")
|
||||
p.Listener, err = minecraft.ListenConfig{
|
||||
StatusProvider: _status,
|
||||
ResourcePacks: packs,
|
||||
AcceptedProtocols: []minecraft.Protocol{
|
||||
//dummyProto{id: 567, ver: "1.19.60"},
|
||||
},
|
||||
}.Listen("raknet", ":19132")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
if p.Client != nil {
|
||||
p.Listener.Disconnect(p.Client, DisconnectReason)
|
||||
}
|
||||
p.Listener.Close()
|
||||
}()
|
||||
|
||||
logrus.Infof(locale.Loc("listening_on", locale.Strmap{"Address": p.Listener.Addr()}))
|
||||
logrus.Infof(locale.Loc("help_connect", nil))
|
||||
|
||||
c, err := p.Listener.Accept()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.Client = c.(*minecraft.Conn)
|
||||
cd := p.Client.ClientData()
|
||||
cdp = &cd
|
||||
}
|
||||
|
||||
_status := minecraft.NewStatusProvider("Server")
|
||||
p.Listener, err = minecraft.ListenConfig{
|
||||
StatusProvider: _status,
|
||||
ResourcePacks: packs,
|
||||
AcceptedProtocols: []minecraft.Protocol{
|
||||
dummyProto{id: 544, ver: "1.19.20"},
|
||||
},
|
||||
}.Listen("raknet", ":19132")
|
||||
if p.OnClientConnect != nil {
|
||||
p.OnClientConnect(p, p.WithClient)
|
||||
}
|
||||
|
||||
if p.CustomClientData != nil {
|
||||
cdp = p.CustomClientData
|
||||
}
|
||||
|
||||
p.Server, err = connectServer(ctx, serverAddress, cdp, p.AlwaysGetPacks, p.PacketFunc)
|
||||
if err != nil {
|
||||
return
|
||||
if p.ConnectCB != nil {
|
||||
if p.ConnectCB(p, err) {
|
||||
err = nil
|
||||
}
|
||||
}
|
||||
err = fmt.Errorf(locale.Loc("failed_to_connect", locale.Strmap{"Address": serverAddress, "Err": err}))
|
||||
return err
|
||||
}
|
||||
defer p.Listener.Close()
|
||||
|
||||
p.log.Infof("Listening on %s", p.Listener.Addr())
|
||||
p.log.Infof("Open Minecraft and connect to this computers local ip address to continue")
|
||||
|
||||
var c net.Conn
|
||||
c, err = p.Listener.Accept()
|
||||
if err != nil {
|
||||
p.log.Fatal(err)
|
||||
}
|
||||
p.Client = c.(*minecraft.Conn)
|
||||
|
||||
cd := p.Client.ClientData()
|
||||
p.Server, err = ConnectServer(ctx, server_address, &cd, false, p.PacketFunc)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed to connect to %s: %s", server_address, err)
|
||||
return
|
||||
}
|
||||
// spawn and start the game
|
||||
if err = spawn_conn(ctx, p.Client, p.Server); err != nil {
|
||||
err = fmt.Errorf("failed to spawn: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
defer p.Server.Close()
|
||||
defer p.Listener.Disconnect(p.Client, G_disconnect_reason)
|
||||
|
||||
// spawn and start the game
|
||||
if err = spawnConn(ctx, p.Client, p.Server); err != nil {
|
||||
err = fmt.Errorf(locale.Loc("failed_to_spawn", locale.Strmap{"Err": err}))
|
||||
return err
|
||||
}
|
||||
|
||||
if p.ConnectCB != nil {
|
||||
p.ConnectCB(p)
|
||||
if !p.ConnectCB(p, nil) {
|
||||
return errors.New("Cancelled")
|
||||
}
|
||||
}
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
cbs := []PacketCallback{
|
||||
p.CommandHandlerPacketCB,
|
||||
}
|
||||
|
||||
var cbs []PacketCallback
|
||||
cbs = append(cbs, p.CommandHandlerPacketCB)
|
||||
if p.PacketCB != nil {
|
||||
cbs = append(cbs, p.PacketCB)
|
||||
}
|
||||
|
@ -232,21 +341,23 @@ func (p *ProxyContext) Run(ctx context.Context, server_address string) (err erro
|
|||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
if err := proxyLoop(ctx, p, false, cbs); err != nil {
|
||||
p.log.Error(err)
|
||||
if err := p.proxyLoop(ctx, false, cbs); err != nil {
|
||||
logrus.Error(err)
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
// client to server
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
if err := proxyLoop(ctx, p, true, cbs); err != nil {
|
||||
p.log.Error(err)
|
||||
return
|
||||
}
|
||||
}()
|
||||
if p.Client != nil {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
if err := p.proxyLoop(ctx, true, cbs); err != nil {
|
||||
logrus.Error(err)
|
||||
return
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
return err
|
||||
|
|
|
@ -6,18 +6,16 @@ import (
|
|||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/google/subcommands"
|
||||
"github.com/sandertv/gophertunnel/minecraft/realms"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/bedrock-tool/bedrocktool/locale"
|
||||
)
|
||||
|
||||
func get_realm(ctx context.Context, api *realms.Client, realm_name, id string) (name string, address string, err error) {
|
||||
realms, err := api.Realms(ctx)
|
||||
func getRealm(ctx context.Context, realmName, id string) (name string, address string, err error) {
|
||||
realms, err := GetRealmsAPI().Realms(ctx)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
for _, realm := range realms {
|
||||
if strings.HasPrefix(realm.Name, realm_name) {
|
||||
if strings.HasPrefix(realm.Name, realmName) {
|
||||
if id != "" && id != fmt.Sprint(id) {
|
||||
continue
|
||||
}
|
||||
|
@ -34,25 +32,18 @@ func get_realm(ctx context.Context, api *realms.Client, realm_name, id string) (
|
|||
|
||||
type RealmListCMD struct{}
|
||||
|
||||
func (*RealmListCMD) Name() string { return "list-realms" }
|
||||
func (*RealmListCMD) Synopsis() string { return "prints all realms you have access to" }
|
||||
|
||||
func (*RealmListCMD) Name() string { return "list-realms" }
|
||||
func (*RealmListCMD) Synopsis() string { return locale.Loc("list_realms_synopsis", nil) }
|
||||
func (c *RealmListCMD) SetFlags(f *flag.FlagSet) {}
|
||||
func (c *RealmListCMD) Usage() string {
|
||||
return c.Name() + ": " + c.Synopsis() + "\n"
|
||||
}
|
||||
|
||||
func (c *RealmListCMD) Execute(ctx context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
|
||||
api := realms.NewClient(GetTokenSource())
|
||||
realms, err := api.Realms(ctx)
|
||||
func (c *RealmListCMD) Execute(ctx context.Context, ui UI) error {
|
||||
realms, err := GetRealmsAPI().Realms(ctx)
|
||||
if err != nil {
|
||||
logrus.Error(err)
|
||||
return 1
|
||||
return err
|
||||
}
|
||||
for _, realm := range realms {
|
||||
fmt.Printf("Name: %s\tid: %d\n", realm.Name, realm.ID)
|
||||
fmt.Println(locale.Loc("realm_list_line", locale.Strmap{"Name": realm.Name, "Id": realm.ID}))
|
||||
}
|
||||
return 0
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
|
105
utils/replay.go
105
utils/replay.go
|
@ -1,89 +1,136 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"github.com/sandertv/gophertunnel/minecraft"
|
||||
"github.com/sandertv/gophertunnel/minecraft/protocol"
|
||||
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func create_replay_connection(ctx context.Context, log *logrus.Logger, filename string, onConnect ConnectCallback, packetCB PacketCallback) error {
|
||||
log.Infof("Reading replay %s", filename)
|
||||
type replayHeader struct {
|
||||
Version int32
|
||||
}
|
||||
|
||||
var replayMagic = []byte("BTCP")
|
||||
|
||||
const (
|
||||
currentReplayVersion = 2
|
||||
)
|
||||
|
||||
func WriteReplayHeader(f io.Writer) {
|
||||
f.Write(replayMagic)
|
||||
header := replayHeader{
|
||||
Version: currentReplayVersion,
|
||||
}
|
||||
binary.Write(f, binary.LittleEndian, &header)
|
||||
}
|
||||
|
||||
func createReplayConnection(ctx context.Context, filename string, onConnect ConnectCallback, packetCB PacketCallback) error {
|
||||
logrus.Infof("Reading replay %s", filename)
|
||||
|
||||
f, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var size int64
|
||||
{
|
||||
stat, err := f.Stat()
|
||||
if err != nil {
|
||||
stat, err := f.Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
totalSize := stat.Size()
|
||||
|
||||
// default version is version 1, since that didnt have a header
|
||||
ver := 1
|
||||
|
||||
magic := make([]byte, 4)
|
||||
io.ReadAtLeast(f, magic, 4)
|
||||
if bytes.Equal(magic, replayMagic) {
|
||||
var header replayHeader
|
||||
if err := binary.Read(f, binary.LittleEndian, &header); err != nil {
|
||||
return err
|
||||
}
|
||||
size = stat.Size()
|
||||
ver = int(header.Version)
|
||||
} else {
|
||||
logrus.Info("Version 1 capture assumed.")
|
||||
f.Seek(-4, io.SeekCurrent)
|
||||
}
|
||||
|
||||
proxy := NewProxy(logrus.StandardLogger())
|
||||
proxy, _ := NewProxy()
|
||||
proxy.Server = minecraft.NewConn()
|
||||
|
||||
game_started := false
|
||||
gameStarted := false
|
||||
i := 0
|
||||
for {
|
||||
i += 1
|
||||
var magic uint32 = 0
|
||||
var packet_length uint32 = 0
|
||||
var packetLength uint32 = 0
|
||||
var toServer bool = false
|
||||
timeReceived := time.Now()
|
||||
|
||||
offset, _ := f.Seek(0, io.SeekCurrent)
|
||||
if offset == size {
|
||||
log.Info("Reached End")
|
||||
if offset == totalSize {
|
||||
logrus.Info("Reached End")
|
||||
return nil
|
||||
}
|
||||
|
||||
binary.Read(f, binary.LittleEndian, &magic)
|
||||
if magic != 0xAAAAAAAA {
|
||||
logrus.Fatal("Wrong Magic")
|
||||
return fmt.Errorf("wrong Magic")
|
||||
}
|
||||
binary.Read(f, binary.LittleEndian, &packet_length)
|
||||
binary.Read(f, binary.LittleEndian, &packetLength)
|
||||
binary.Read(f, binary.LittleEndian, &toServer)
|
||||
payload := make([]byte, packet_length)
|
||||
if ver >= 2 {
|
||||
var timeMs int64
|
||||
binary.Read(f, binary.LittleEndian, &timeMs)
|
||||
timeReceived = time.UnixMilli(timeMs)
|
||||
}
|
||||
|
||||
payload := make([]byte, packetLength)
|
||||
n, err := f.Read(payload)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
logrus.Error(err)
|
||||
return nil
|
||||
}
|
||||
if n != int(packet_length) {
|
||||
log.Errorf("Truncated %d", i)
|
||||
return nil
|
||||
if n != int(packetLength) {
|
||||
return fmt.Errorf("truncated %d", i)
|
||||
}
|
||||
|
||||
var magic2 uint32
|
||||
binary.Read(f, binary.LittleEndian, &magic2)
|
||||
if magic2 != 0xBBBBBBBB {
|
||||
logrus.Fatal("Wrong Magic2")
|
||||
return fmt.Errorf("wrong Magic2")
|
||||
}
|
||||
|
||||
pk_data, err := minecraft.ParseData(payload, proxy.Server)
|
||||
pkData, err := minecraft.ParseData(payload, proxy.Server)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pks, err := pk_data.Decode(proxy.Server)
|
||||
pks, err := pkData.Decode(proxy.Server)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
logrus.Error(err)
|
||||
continue
|
||||
}
|
||||
|
||||
for _, pk := range pks {
|
||||
logrus.Printf("%s", reflect.TypeOf(pk).String()[1:])
|
||||
f := bytes.NewBuffer(nil)
|
||||
b := protocol.NewWriter(f, 0)
|
||||
pk.Marshal(b)
|
||||
|
||||
if game_started {
|
||||
if Options.Debug {
|
||||
PacketLogger(packet.Header{PacketID: pk.ID()}, f.Bytes(), &net.UDPAddr{}, &net.UDPAddr{})
|
||||
}
|
||||
|
||||
if gameStarted {
|
||||
if packetCB != nil {
|
||||
packetCB(pk, proxy, toServer)
|
||||
packetCB(pk, proxy, toServer, timeReceived)
|
||||
}
|
||||
} else {
|
||||
switch pk := pk.(type) {
|
||||
|
@ -117,9 +164,9 @@ func create_replay_connection(ctx context.Context, log *logrus.Logger, filename
|
|||
ChatRestrictionLevel: pk.ChatRestrictionLevel,
|
||||
DisablePlayerInteractions: pk.DisablePlayerInteractions,
|
||||
})
|
||||
game_started = true
|
||||
gameStarted = true
|
||||
if onConnect != nil {
|
||||
onConnect(proxy)
|
||||
onConnect(proxy, nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Binary file not shown.
|
@ -1,14 +1,82 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
|
||||
"github.com/sandertv/gophertunnel/minecraft"
|
||||
"github.com/sandertv/gophertunnel/minecraft/resource"
|
||||
)
|
||||
|
||||
type Pack interface {
|
||||
io.ReaderAt
|
||||
ReadAll() ([]byte, error)
|
||||
Decrypt() ([]byte, error)
|
||||
Encrypted() bool
|
||||
CanDecrypt() bool
|
||||
UUID() string
|
||||
Name() string
|
||||
Version() string
|
||||
ContentKey() string
|
||||
Len() int
|
||||
Manifest() resource.Manifest
|
||||
Base() *resource.Pack
|
||||
}
|
||||
|
||||
type Packb struct {
|
||||
*resource.Pack
|
||||
}
|
||||
|
||||
func (p *Packb) 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 *Packb) CanDecrypt() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *Packb) Decrypt() ([]byte, error) {
|
||||
return nil, errors.New("no_decrypt")
|
||||
}
|
||||
|
||||
func (p *Packb) Base() *resource.Pack {
|
||||
return p.Pack
|
||||
}
|
||||
|
||||
var PackFromBase = func(pack *resource.Pack) Pack {
|
||||
b := &Packb{pack}
|
||||
return b
|
||||
}
|
||||
|
||||
func GetPacks(server *minecraft.Conn) (packs map[string]*resource.Pack, err error) {
|
||||
packs = make(map[string]*resource.Pack)
|
||||
for _, pack := range server.ResourcePacks() {
|
||||
packs[pack.Name()] = pack
|
||||
pack := PackFromBase(pack)
|
||||
if pack.Encrypted() && pack.CanDecrypt() {
|
||||
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.Base()
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
|
@ -1,15 +1,50 @@
|
|||
package utils
|
||||
|
||||
import "github.com/sanbornm/go-selfupdate/selfupdate"
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
"github.com/sanbornm/go-selfupdate/selfupdate"
|
||||
)
|
||||
|
||||
var Version string
|
||||
var CmdName = "bedrocktool"
|
||||
|
||||
const updateServer = "https://updates.yuv.pink/"
|
||||
|
||||
type trequester struct {
|
||||
selfupdate.Requester
|
||||
}
|
||||
|
||||
func (httpRequester *trequester) Fetch(url string) (io.ReadCloser, error) {
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// set user agent to know what versions are run
|
||||
h, _ := os.Hostname()
|
||||
req.Header.Add("User-Agent", fmt.Sprintf("%s %s '%s' %d", CmdName, Version, h, runtime.NumCPU()))
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
return nil, fmt.Errorf("bad http status from %s: %v", url, resp.Status)
|
||||
}
|
||||
|
||||
return resp.Body, nil
|
||||
}
|
||||
|
||||
var Updater = &selfupdate.Updater{
|
||||
CurrentVersion: Version,
|
||||
ApiURL: updateServer,
|
||||
BinURL: updateServer,
|
||||
Dir: "update/",
|
||||
CmdName: "bedrocktool", // app name
|
||||
CmdName: CmdName,
|
||||
Requester: &trequester{},
|
||||
}
|
||||
|
|
185
utils/utils.go
185
utils/utils.go
|
@ -1,18 +1,23 @@
|
|||
// Package utils ...
|
||||
package utils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"crypto/aes"
|
||||
"crypto/sha256"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"path"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/bedrock-tool/bedrocktool/locale"
|
||||
"github.com/bedrock-tool/bedrocktool/utils/crypt"
|
||||
"github.com/google/uuid"
|
||||
"github.com/sandertv/gophertunnel/minecraft"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
@ -21,34 +26,21 @@ import (
|
|||
|
||||
"github.com/sandertv/gophertunnel/minecraft/protocol/login"
|
||||
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
|
||||
"github.com/sandertv/gophertunnel/minecraft/resource"
|
||||
)
|
||||
|
||||
const SERVER_ADDRESS_HELP = `accepted server address formats:
|
||||
123.234.123.234
|
||||
123.234.123.234:19132
|
||||
realm:<Realmname>
|
||||
realm:<Realmname>:<Id>
|
||||
|
||||
`
|
||||
|
||||
var (
|
||||
G_debug bool
|
||||
G_preload_packs bool
|
||||
G_interactive bool
|
||||
)
|
||||
|
||||
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)
|
||||
var Options struct {
|
||||
Debug bool
|
||||
Preload bool
|
||||
IsInteractive bool
|
||||
ExtraDebug bool
|
||||
EnableDNS bool
|
||||
PathCustomUserData string
|
||||
}
|
||||
|
||||
var name_regexp = regexp.MustCompile(`\||(?:§.?)`)
|
||||
var nameRegexp = regexp.MustCompile(`\||(?:§.?)`)
|
||||
|
||||
// cleans name so it can be used as a filename
|
||||
// CleanupName cleans name so it can be used as a filename
|
||||
func CleanupName(name string) string {
|
||||
name = strings.Split(name, "\n")[0]
|
||||
var _tmp struct {
|
||||
|
@ -58,71 +50,77 @@ func CleanupName(name string) string {
|
|||
if err == nil {
|
||||
name = _tmp.K
|
||||
}
|
||||
name = string(name_regexp.ReplaceAll([]byte(name), []byte("")))
|
||||
name = string(nameRegexp.ReplaceAll([]byte(name), []byte("")))
|
||||
name = strings.TrimSpace(name)
|
||||
return name
|
||||
}
|
||||
|
||||
// connections
|
||||
|
||||
func ConnectServer(ctx context.Context, address string, ClientData *login.ClientData, want_packs bool, packetFunc PacketFunc) (serverConn *minecraft.Conn, err error) {
|
||||
packet_func := func(header packet.Header, payload []byte, src, dst net.Addr) {
|
||||
if G_debug {
|
||||
PacketLogger(header, payload, src, dst)
|
||||
}
|
||||
if packetFunc != nil {
|
||||
packetFunc(header, payload, src, dst)
|
||||
}
|
||||
}
|
||||
|
||||
func connectServer(ctx context.Context, address string, ClientData *login.ClientData, wantPacks bool, packetFunc PacketFunc) (serverConn *minecraft.Conn, err error) {
|
||||
cd := login.ClientData{}
|
||||
if ClientData != nil {
|
||||
cd = *ClientData
|
||||
}
|
||||
|
||||
logrus.Infof("Connecting to %s\n", address)
|
||||
logrus.Info(locale.Loc("connecting", locale.Strmap{"Address": address}))
|
||||
serverConn, err = minecraft.Dialer{
|
||||
TokenSource: GetTokenSource(),
|
||||
ClientData: cd,
|
||||
PacketFunc: packet_func,
|
||||
DownloadResourcePack: func(id uuid.UUID, version string) bool {
|
||||
return want_packs
|
||||
PacketFunc: func(header packet.Header, payload []byte, src, dst net.Addr) {
|
||||
if Options.Debug {
|
||||
PacketLogger(header, payload, src, dst)
|
||||
}
|
||||
if packetFunc != nil {
|
||||
packetFunc(header, payload, src, dst)
|
||||
}
|
||||
},
|
||||
DownloadResourcePack: func(id uuid.UUID, version string, current int, total int) bool {
|
||||
return wantPacks
|
||||
},
|
||||
}.DialContext(ctx, "raknet", address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return serverConn, err
|
||||
}
|
||||
|
||||
logrus.Debug("Connected.")
|
||||
Client_addr = serverConn.LocalAddr()
|
||||
logrus.Debug(locale.Loc("connected", nil))
|
||||
ClientAddr = serverConn.LocalAddr()
|
||||
return serverConn, nil
|
||||
}
|
||||
|
||||
func spawn_conn(ctx context.Context, clientConn *minecraft.Conn, serverConn *minecraft.Conn) error {
|
||||
func spawnConn(ctx context.Context, clientConn *minecraft.Conn, serverConn *minecraft.Conn) error {
|
||||
wg := sync.WaitGroup{}
|
||||
errs := make(chan error, 2)
|
||||
if clientConn != nil {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
errs <- clientConn.StartGame(serverConn.GameData())
|
||||
}()
|
||||
}
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
errs <- clientConn.StartGame(serverConn.GameData())
|
||||
}()
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
errs <- serverConn.DoSpawn()
|
||||
}()
|
||||
|
||||
// wait for both to finish
|
||||
wg.Wait()
|
||||
for i := 0; i < 2; i++ {
|
||||
select {
|
||||
case err := <-errs:
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to start game: %s", err)
|
||||
return errors.New(locale.Loc("failed_start_game", locale.Strmap{"Err": err}))
|
||||
}
|
||||
case <-ctx.Done():
|
||||
return fmt.Errorf("connection cancelled")
|
||||
return errors.New(locale.Loc("connection_cancelled", nil))
|
||||
default:
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// get longest line length
|
||||
func max_len(lines []string) int {
|
||||
func maxLen(lines []string) int {
|
||||
o := 0
|
||||
for _, line := range lines {
|
||||
if o < len(line) {
|
||||
|
@ -132,10 +130,10 @@ func max_len(lines []string) int {
|
|||
return o
|
||||
}
|
||||
|
||||
// make text centered
|
||||
// MarginLines makes text centered
|
||||
func MarginLines(lines []string) string {
|
||||
ret := ""
|
||||
max := max_len(lines)
|
||||
max := maxLen(lines)
|
||||
for _, line := range lines {
|
||||
if len(line) != max {
|
||||
ret += strings.Repeat(" ", max/2-len(line)/4)
|
||||
|
@ -163,3 +161,82 @@ func Clamp(a, b int) int {
|
|||
}
|
||||
return a
|
||||
}
|
||||
|
||||
func RandSeededUUID(str string) string {
|
||||
h := sha256.Sum256([]byte(str))
|
||||
id, _ := uuid.NewRandomFromReader(bytes.NewBuffer(h[:]))
|
||||
return id.String()
|
||||
}
|
||||
|
||||
func WriteManifest(manifest *resource.Manifest, fpath string) error {
|
||||
w, err := os.Create(path.Join(fpath, "manifest.json"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
e := json.NewEncoder(w)
|
||||
e.SetIndent("", "\t")
|
||||
if err = e.Encode(manifest); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func CfbDecrypt(data []byte, key []byte) []byte {
|
||||
cipher, _ := aes.NewCipher([]byte(key))
|
||||
|
||||
shiftRegister := append(key[:16], data...)
|
||||
iv := make([]byte, 16)
|
||||
off := 0
|
||||
for ; off < len(data); off += 1 {
|
||||
cipher.Encrypt(iv, shiftRegister)
|
||||
data[off] ^= iv[0]
|
||||
shiftRegister = shiftRegister[1:]
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
func InitExtraDebug(ctx context.Context) {
|
||||
if !Options.ExtraDebug {
|
||||
return
|
||||
}
|
||||
Options.Debug = true
|
||||
|
||||
var logPlain, logCryptEnc io.WriteCloser = nil, nil
|
||||
|
||||
// open plain text log
|
||||
logPlain, err := os.Create("packets.log")
|
||||
if err != nil {
|
||||
logrus.Error(err)
|
||||
} else {
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
logPlain.Close()
|
||||
}()
|
||||
}
|
||||
|
||||
// open gpg log
|
||||
logCrypt, err := os.Create("packets.log.gpg")
|
||||
if err != nil {
|
||||
logrus.Error(err)
|
||||
} else {
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
logCrypt.Close()
|
||||
}()
|
||||
// encrypter for the log
|
||||
logCryptEnc, err = crypt.Encer("packets.log", logCrypt)
|
||||
if err != nil {
|
||||
logrus.Error(err)
|
||||
} else {
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
logCryptEnc.Close()
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
FLog = io.MultiWriter(logPlain, logCryptEnc)
|
||||
if err != nil {
|
||||
logrus.Error(err)
|
||||
}
|
||||
}
|
||||
|
|
26
utils/zip.go
26
utils/zip.go
|
@ -2,25 +2,27 @@ package utils
|
|||
|
||||
import (
|
||||
"archive/zip"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func UnpackZip(r io.ReaderAt, size int64, unpack_folder string) {
|
||||
func UnpackZip(r io.ReaderAt, size int64, unpackFolder 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, 0o755)
|
||||
for _, srcFile := range zr.File {
|
||||
srcName := strings.ReplaceAll(srcFile.Name, "\\", "/")
|
||||
outPath := path.Join(unpackFolder, srcName)
|
||||
if srcFile.Mode().IsDir() {
|
||||
os.Mkdir(outPath, 0o755)
|
||||
} else {
|
||||
os.MkdirAll(path.Dir(out_path), 0o755)
|
||||
fr, _ := src_file.Open()
|
||||
f, _ := os.Create(path.Join(unpack_folder, src_file.Name))
|
||||
os.MkdirAll(path.Dir(outPath), 0o755)
|
||||
fr, _ := srcFile.Open()
|
||||
f, _ := os.Create(path.Join(unpackFolder, srcName))
|
||||
io.Copy(f, fr)
|
||||
}
|
||||
}
|
||||
|
@ -29,7 +31,7 @@ func UnpackZip(r io.ReaderAt, size int64, unpack_folder string) {
|
|||
func ZipFolder(filename, folder string) error {
|
||||
f, err := os.Create(filename)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
zw := zip.NewWriter(f)
|
||||
err = filepath.WalkDir(folder, func(path string, d fs.DirEntry, err error) error {
|
||||
|
@ -38,7 +40,7 @@ func ZipFolder(filename, folder string) error {
|
|||
zwf, _ := zw.Create(rel)
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
logrus.Error(err)
|
||||
}
|
||||
zwf.Write(data)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue