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
|
name: ci-build
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
branches:
|
||||||
|
- '**'
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
@ -13,51 +15,56 @@ jobs:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- run: |
|
- run: |
|
||||||
git fetch --force --tags
|
git fetch --force --tags
|
||||||
|
|
||||||
- uses: actions/setup-go@v3
|
- name: Setup Golang with cache
|
||||||
|
uses: magnetikonline/action-golang-cache@v3
|
||||||
with:
|
with:
|
||||||
go-version: '1.19'
|
go-version-file: go.mod
|
||||||
check-latest: true
|
|
||||||
cache: true
|
- 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
|
- uses: awalsh128/cache-apt-pkgs-action@latest
|
||||||
|
if: ${{ env.REPO_KEY != '' }}
|
||||||
with:
|
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
|
version: 1.0
|
||||||
|
env:
|
||||||
|
REPO_KEY: ${{ secrets.REPO_KEY }}
|
||||||
|
|
||||||
- name: decrypt
|
- name: decrypt
|
||||||
if: ${{ !env.ACT }}
|
if: ${{ env.REPO_KEY != '' }}
|
||||||
run: |
|
run: |
|
||||||
echo ${REPO_KEY} | xxd -r -p > ../bedrock-repo-key.key
|
echo ${REPO_KEY} | xxd -r -p > ../bedrock-repo-key.key
|
||||||
git-crypt unlock ../bedrock-repo-key.key
|
git-crypt unlock ../bedrock-repo-key.key
|
||||||
rm ../bedrock-repo-key.key
|
rm ../bedrock-repo-key.key
|
||||||
env:
|
env:
|
||||||
REPO_KEY: ${{ secrets.REPO_KEY }}
|
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
|
- name: dependencies
|
||||||
uses: shimataro/ssh-key-action@v2
|
run: |
|
||||||
with:
|
go get ./cmd/bedrocktool
|
||||||
key: ${{ secrets.SSH_PRIVATE_KEY }}
|
go install gioui.org/cmd/gogio@latest
|
||||||
known_hosts: ${{ secrets.KNOWN_HOSTS }}
|
|
||||||
|
|
||||||
- name: build
|
- name: build
|
||||||
id: build
|
id: build
|
||||||
run: |
|
run: python build.py
|
||||||
make -j dists updates
|
|
||||||
|
|
||||||
- name: Deploy with rsync
|
- name: Deploy with rsync
|
||||||
run: rsync -avz ./public/ olebeck@${{ secrets.SSH_HOST }}:/var/www/updates/bedrocktool/
|
if: ${{ env.SSH_HOST != '' }}
|
||||||
|
run: rsync -avzO ./updates/ olebeck@${SSH_HOST}:/var/www/updates/
|
||||||
- name: 'Get Previous tag'
|
env:
|
||||||
id: previoustag
|
SSH_HOST: ${{ secrets.SSH_HOST }}
|
||||||
uses: "WyriHaximus/github-action-get-previous-tag@v1"
|
|
||||||
|
|
||||||
- uses: "marvinpinto/action-automatic-releases@latest"
|
- uses: "marvinpinto/action-automatic-releases@latest"
|
||||||
with:
|
with:
|
||||||
repo_token: "${{ secrets.GITHUB_TOKEN }}"
|
repo_token: "${{ secrets.GITHUB_TOKEN }}"
|
||||||
automatic_release_tag: ${{ steps.previoustag.outputs.tag }}
|
automatic_release_tag: ${{ steps.build.outputs.release_tag }}
|
||||||
files: dist/*
|
files: ./builds/*
|
||||||
prerelease: false
|
prerelease: false
|
||||||
|
|
|
@ -8,6 +8,7 @@ token.json
|
||||||
*.bmp
|
*.bmp
|
||||||
*.bin
|
*.bin
|
||||||
*.log
|
*.log
|
||||||
|
*.syso
|
||||||
|
|
||||||
/bedrocktool
|
/bedrocktool
|
||||||
/bedrocktool-*
|
/bedrocktool-*
|
||||||
|
@ -17,6 +18,12 @@ __debug_bin
|
||||||
keys.db
|
keys.db
|
||||||
/skins/
|
/skins/
|
||||||
/worlds/
|
/worlds/
|
||||||
|
/packs/
|
||||||
/dist/
|
/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"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
"strings"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/bedrock-tool/bedrocktool/locale"
|
||||||
"github.com/bedrock-tool/bedrocktool/utils"
|
"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"
|
||||||
_ "github.com/bedrock-tool/bedrocktool/subcommands/skins"
|
_ "github.com/bedrock-tool/bedrocktool/subcommands/skins"
|
||||||
_ "github.com/bedrock-tool/bedrocktool/subcommands/world"
|
_ "github.com/bedrock-tool/bedrocktool/subcommands/world"
|
||||||
|
_ "github.com/bedrock-tool/bedrocktool/ui"
|
||||||
|
|
||||||
"github.com/google/subcommands"
|
"github.com/google/subcommands"
|
||||||
"github.com/sirupsen/logrus"
|
"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() {
|
func main() {
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := recover(); err != nil {
|
if err := recover(); err != nil {
|
||||||
logrus.Errorf("Fatal Error occurred.")
|
logrus.Errorf(locale.Loc("fatal_error", nil))
|
||||||
println("")
|
println("")
|
||||||
println("--COPY FROM HERE--")
|
println("--COPY FROM HERE--")
|
||||||
logrus.Infof("Version: %s", utils.Version)
|
logrus.Infof("Version: %s", utils.Version)
|
||||||
logrus.Infof("Cmdline: %s", os.Args)
|
logrus.Infof("Cmdline: %s", os.Args)
|
||||||
logrus.Errorf("Error: %s", err)
|
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("--END COPY HERE--")
|
||||||
println("")
|
println("")
|
||||||
println("if you want to report this error, please open an issue at")
|
println(locale.Loc("report_issue", nil))
|
||||||
println("https://github.com/bedrock-tool/bedrocktool/issues")
|
if utils.Options.ExtraDebug {
|
||||||
println("And attach the error info, describe what you did to get this error.")
|
println(locale.Loc("used_extra_debug_report", nil))
|
||||||
println("Thanks!\n")
|
}
|
||||||
if utils.G_interactive {
|
if utils.Options.IsInteractive {
|
||||||
input := bufio.NewScanner(os.Stdin)
|
input := bufio.NewScanner(os.Stdin)
|
||||||
input.Scan()
|
input.Scan()
|
||||||
}
|
}
|
||||||
|
@ -47,7 +66,7 @@ func main() {
|
||||||
|
|
||||||
logrus.SetLevel(logrus.DebugLevel)
|
logrus.SetLevel(logrus.DebugLevel)
|
||||||
if utils.Version != "" {
|
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()
|
newVersion, err := utils.Updater.UpdateAvailable()
|
||||||
|
@ -56,14 +75,18 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if newVersion != "" && utils.Version != "" {
|
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())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
|
||||||
flag.BoolVar(&utils.G_debug, "debug", false, "debug mode")
|
flag.StringVar(&utils.RealmsEnv, "realms-env", "", "realms env")
|
||||||
flag.BoolVar(&utils.G_preload_packs, "preload", false, "preload resourcepacks for proxy")
|
flag.BoolVar(&utils.Options.Debug, "debug", false, locale.Loc("debug_mode", nil))
|
||||||
enable_dns := flag.Bool("dns", false, "enable dns server for consoles")
|
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.Register(subcommands.HelpCommand(), "")
|
||||||
subcommands.ImportantFlag("debug")
|
subcommands.ImportantFlag("debug")
|
||||||
|
@ -71,33 +94,13 @@ func main() {
|
||||||
subcommands.ImportantFlag("preload")
|
subcommands.ImportantFlag("preload")
|
||||||
subcommands.HelpCommand()
|
subcommands.HelpCommand()
|
||||||
|
|
||||||
{ // interactive input
|
var ui utils.UI
|
||||||
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])
|
|
||||||
|
|
||||||
cmd, cancelled := utils.User_input(ctx, "Input Command: ")
|
if len(os.Args) < 2 {
|
||||||
if cancelled {
|
ui = utils.MakeGui()
|
||||||
return
|
utils.Options.IsInteractive = true
|
||||||
}
|
} else {
|
||||||
_cmd := strings.Split(cmd, " ")
|
ui = &CLI{}
|
||||||
os.Args = append(os.Args, _cmd...)
|
|
||||||
utils.G_interactive = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
flag.Parse()
|
|
||||||
|
|
||||||
if *enable_dns {
|
|
||||||
utils.InitDNS()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// exit cleanup
|
// exit cleanup
|
||||||
|
@ -109,12 +112,14 @@ func main() {
|
||||||
cancel()
|
cancel()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
subcommands.Execute(ctx)
|
if !ui.Init() {
|
||||||
|
logrus.Error("Failed to init UI!")
|
||||||
if utils.G_interactive {
|
return
|
||||||
logrus.Info("Press Enter to exit.")
|
}
|
||||||
input := bufio.NewScanner(os.Stdin)
|
err = ui.Start(ctx, cancel)
|
||||||
input.Scan()
|
cancel()
|
||||||
|
if err != nil {
|
||||||
|
logrus.Error(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,31 +129,52 @@ type TransCMD struct {
|
||||||
|
|
||||||
func (*TransCMD) Name() string { return "trans" }
|
func (*TransCMD) Name() string { return "trans" }
|
||||||
func (*TransCMD) Synopsis() string { return "" }
|
func (*TransCMD) Synopsis() string { return "" }
|
||||||
|
|
||||||
func (c *TransCMD) SetFlags(f *flag.FlagSet) {
|
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) Execute(_ context.Context, ui utils.UI) error {
|
||||||
func (c *TransCMD) Usage() string {
|
|
||||||
return c.Name() + ": " + c.Synopsis() + "\n"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *TransCMD) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
|
|
||||||
const (
|
const (
|
||||||
BLACK_FG = "\033[30m"
|
BlackFg = "\033[30m"
|
||||||
BOLD = "\033[1m"
|
Bold = "\033[1m"
|
||||||
BLUE = "\033[46m"
|
Blue = "\033[46m"
|
||||||
PINK = "\033[45m"
|
Pink = "\033[45m"
|
||||||
WHITE = "\033[47m"
|
White = "\033[47m"
|
||||||
RESET = "\033[0m"
|
Reset = "\033[0m"
|
||||||
)
|
)
|
||||||
if c.auth {
|
if c.auth {
|
||||||
utils.GetTokenSource()
|
utils.GetTokenSource()
|
||||||
}
|
}
|
||||||
fmt.Println(BLACK_FG + BOLD + BLUE + " Trans " + PINK + " Rights " + WHITE + " Are " + PINK + " Human " + BLUE + " Rights " + RESET)
|
fmt.Println(BlackFg + Bold + Blue + " Trans " + Pink + " Rights " + White + " Are " + Pink + " Human " + Blue + " Rights " + Reset)
|
||||||
return 0
|
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() {
|
func init() {
|
||||||
utils.RegisterCommand(&TransCMD{})
|
utils.RegisterCommand(&TransCMD{})
|
||||||
|
utils.RegisterCommand(&CreateCustomDataCMD{})
|
||||||
}
|
}
|
||||||
|
|
60
go.mod
60
go.mod
|
@ -1,54 +1,66 @@
|
||||||
module github.com/bedrock-tool/bedrocktool
|
module github.com/bedrock-tool/bedrocktool
|
||||||
|
|
||||||
go 1.19
|
go 1.20
|
||||||
|
|
||||||
//replace github.com/sandertv/gophertunnel => ./gophertunnel
|
//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 => ./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 (
|
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/df-mc/goleveldb v1.1.9
|
||||||
github.com/fatih/color v1.13.0
|
github.com/fatih/color v1.14.1
|
||||||
github.com/flytam/filenamify v1.1.1
|
github.com/flytam/filenamify v1.1.2
|
||||||
github.com/go-gl/mathgl v1.0.0
|
github.com/go-gl/mathgl v1.0.0
|
||||||
github.com/google/subcommands v1.2.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/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.design/x/lockfree v0.0.1
|
||||||
golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9
|
golang.org/x/crypto v0.7.0
|
||||||
golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1
|
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 (
|
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/brentp/intintmap v0.0.0-20190211203843-30dc0ade9af9 // indirect
|
||||||
github.com/changkun/lockfree v0.0.1 // 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/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/protobuf v1.5.2 // indirect
|
||||||
github.com/golang/snappy v0.0.4 // 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/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/kr/binarydist v0.1.0 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.13 // 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/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
|
go.uber.org/atomic v1.10.0 // indirect
|
||||||
golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be // indirect
|
golang.org/x/exp/shiny v0.0.0-20220827204233-334a2380cb91 // indirect
|
||||||
golang.org/x/image v0.0.0-20220902085622-e7cb96979f69
|
golang.org/x/mod v0.8.0 // indirect
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
|
golang.org/x/net v0.8.0 // indirect
|
||||||
golang.org/x/net v0.0.0-20220930213112-107f3e3c3b0b // indirect
|
golang.org/x/sys v0.6.0 // indirect
|
||||||
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec // indirect
|
golang.org/x/tools v0.6.0 // indirect
|
||||||
golang.org/x/text v0.3.7 // indirect
|
|
||||||
golang.org/x/tools v0.1.12 // indirect
|
|
||||||
google.golang.org/appengine v1.6.7 // indirect
|
google.golang.org/appengine v1.6.7 // indirect
|
||||||
google.golang.org/protobuf v1.28.1 // indirect
|
google.golang.org/protobuf v1.28.1 // indirect
|
||||||
gopkg.in/inconshreveable/go-update.v0 v0.0.0-20150814200126-d8b0b1d421aa // 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 h1:/G0ghZwrhou0Wq21qc1vXXMm/t/aKWkALWwITptKbE0=
|
||||||
github.com/brentp/intintmap v0.0.0-20190211203843-30dc0ade9af9/go.mod h1:TOk10ahXejq9wkEaym3KPRNeuR/h5Jx+s8QRWIa2oTM=
|
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 h1:5WefVJLglY4IHRqOQmh6Ao6wkJYaJkarshKU8VUtId4=
|
||||||
github.com/changkun/lockfree v0.0.1/go.mod h1:3bKiaXn/iNzIPlSvSOMSVbRQUQtAp8qUAyBUtzU11s4=
|
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.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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
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/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 h1:ihdosZyy5jkQKrxucTQmN90jq/2lUwQnJZjIYIC/9YU=
|
||||||
github.com/df-mc/goleveldb v1.1.9/go.mod h1:+NHCup03Sci5q84APIA21z3iPZCuk6m6ABtg4nANCSk=
|
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.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w=
|
||||||
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg=
|
||||||
github.com/flytam/filenamify v1.1.1 h1:508gP8QR6vtbv46S3oz2ob9l7JGFdDFfqqMeh/TwzTk=
|
github.com/flytam/filenamify v1.1.2 h1:dGlfWU4zrhDlsmvob4IFcfgjG5vIjfo4UwLyec6Wx94=
|
||||||
github.com/flytam/filenamify v1.1.1/go.mod h1:Dzf9kVycwcsBlr2ATg6uxjqiFgKGH+5SKFuhdeP5zu8=
|
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/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 h1:t9DznWJlXxxjeeKLIdovCOVJQk/GzDEL7h/h+Ro2B68=
|
||||||
github.com/go-gl/mathgl v1.0.0/go.mod h1:yhpkQzEiH9yPyxDUGzkmgScbaBVlhC06qodikEM0ZwQ=
|
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.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.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
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 h1:iQTw/8FWTuc7uiaSepXwyf3o52HaUYcV+Tu66S3F5GA=
|
||||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
|
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.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||||
github.com/klauspost/compress v1.15.11 h1:Lcadnb3RKGin4FYM/orgq0qde+nc15E5Cbqg4B9Sx9c=
|
github.com/klauspost/compress v1.15.15 h1:EF27CXIuDsYJ6mmvtBRlEuB2UVOqHG1tAXgZ7yIO+lw=
|
||||||
github.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM=
|
github.com/klauspost/compress v1.15.15/go.mod h1:ZcK2JAFqKOpnBlxcLsJzYfrS9X1akm9fHZNnD9+Vo/4=
|
||||||
github.com/klauspost/compress v1.15.12 h1:YClS/PImqYbn+UILDnqxQCZ3RehC9N318SU3kElDUEM=
|
|
||||||
github.com/klauspost/compress v1.15.12/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM=
|
|
||||||
github.com/kr/binarydist v0.1.0 h1:6kAoLA9FMMnNGSehX0s1PdjbEaACznAv/W219j2uvyo=
|
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/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 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
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/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA=
|
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
|
||||||
github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
|
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 h1:8o5gBQn4ZA3NBA9DlTujCj2a4w0tqWrPVjDwhzkgTIs=
|
||||||
github.com/muhammadmuzzammil1998/jsonc v1.0.0/go.mod h1:saF2fIVw4banK0H4+/EuqfFLpRnoy5S+ECwTOCcRcSU=
|
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/nicksnyder/go-i18n/v2 v2.2.1 h1:aOzRCdwsJuoExfZhoiXHy4bjruwCMdt5otbYojM/PaA=
|
||||||
github.com/olebeck/dragonfly v0.8.5-1/go.mod h1:CHQ21j4LjSwHgUeMk06tcxEW6U+SI7avLCUeSvp9AXo=
|
github.com/nicksnyder/go-i18n/v2 v2.2.1/go.mod h1:fF2++lPHlo+/kPaj3nB0uxtPwzlPm+BlgwGX7MkeGj0=
|
||||||
github.com/olebeck/dragonfly v0.8.10-1 h1:oMeCcyoKfb9xEDxxbKM6YyUPlK/R8wix/zzZ4UzkYYQ=
|
github.com/olebeck/dragonfly v0.9.3-2 h1:FBO5raxeQvA286L+mUhnAsUSWxVfKmVFoofw0KdJlXA=
|
||||||
github.com/olebeck/dragonfly v0.8.10-1/go.mod h1:ZjzPME6I1nc73voUgr2s5lpkoTxnWuR54V6c1KbULX0=
|
github.com/olebeck/dragonfly v0.9.3-2/go.mod h1:ODAzVcmM7KvKgPB89hoYndQiVVgTm9FlDXUko1H3YVs=
|
||||||
github.com/olebeck/gophertunnel v1.24.11-1 h1:DKEqjK5nAcjlwejHAD/SHeolAM6y9EkyjJcMuoK7VVY=
|
github.com/olebeck/dragonfly v0.9.3-3 h1:sv4TJjdgFoCBRMd5mS0g5NyPeMsVfUifhxIlJPTj6xg=
|
||||||
github.com/olebeck/gophertunnel v1.24.11-1/go.mod h1:dYFetA6r62huhc1EgR9p8VFAFtKOuGgVE/iXf5CzZ4o=
|
github.com/olebeck/dragonfly v0.9.3-3/go.mod h1:ODAzVcmM7KvKgPB89hoYndQiVVgTm9FlDXUko1H3YVs=
|
||||||
github.com/olebeck/gophertunnel v1.24.13-1 h1:3Sj22hVFAGCnPMJAewoSeuC9N8ikzcWo0DvJNBnXvs0=
|
github.com/olebeck/gophertunnel v1.27.4-1 h1:rtB5wksJBKk3sy9lBFZ1aH2giCathINj4zQkl1rPTMg=
|
||||||
github.com/olebeck/gophertunnel v1.24.13-1/go.mod h1:dYFetA6r62huhc1EgR9p8VFAFtKOuGgVE/iXf5CzZ4o=
|
github.com/olebeck/gophertunnel v1.27.4-1/go.mod h1:ekREo7U9TPHh86kbuPMaWA93NMyWsfVvP/iNT3XhAb8=
|
||||||
github.com/olebeck/gophertunnel v1.25.0-1 h1:9vh6MIUo7/3EJI5oULr5F9KutAoa9qnpgWa566fImaI=
|
github.com/olebeck/gophertunnel v1.27.4-3 h1:RktAdTNTvCFn6PQou0H3RyqrTo3/xH0bqODrHb/oXAs=
|
||||||
github.com/olebeck/gophertunnel v1.25.0-1/go.mod h1:dYFetA6r62huhc1EgR9p8VFAFtKOuGgVE/iXf5CzZ4o=
|
github.com/olebeck/gophertunnel v1.27.4-3/go.mod h1:ekREo7U9TPHh86kbuPMaWA93NMyWsfVvP/iNT3XhAb8=
|
||||||
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/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
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 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
|
||||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
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/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 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
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=
|
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 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
|
||||||
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
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 h1:IHFNwZgM5bnZYWkEbzn5lWHMYr8WsRBdCJ/RBVY0xMM=
|
||||||
golang.design/x/lockfree v0.0.1/go.mod h1:iaZUx6UgZaOdePjzI6wFd+seYMl1i0rsG8+xKvA8c4I=
|
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-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-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.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
|
||||||
golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||||
golang.org/x/crypto v0.3.0 h1:a06MkbcxBrEFc0w0QIZWXrH/9cCX6KJyWbBOIwAn+7A=
|
golang.org/x/exp v0.0.0-20230304125523-9ff063c70017 h1:3Ea9SZLCB0aRIhSEjM+iaGIlzzeDJdpi579El/YIhEE=
|
||||||
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
golang.org/x/exp v0.0.0-20230304125523-9ff063c70017/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
|
||||||
golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 h1:RjggHMcaTVp0LOVZcW0bo8alwHrOaCrGUDgfWUHhnN4=
|
golang.org/x/exp/shiny v0.0.0-20220827204233-334a2380cb91 h1:ryT6Nf0R83ZgD8WnFFdfI8wCeyqgdXWN4+CkFVNPAT0=
|
||||||
golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
|
golang.org/x/exp/shiny v0.0.0-20220827204233-334a2380cb91/go.mod h1:VjAR7z0ngyATZTELrBSkxOOHhhlnVUxDye4mcjx5h/8=
|
||||||
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/image v0.0.0-20190321063152-3fc05d484e9f/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
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-20210504121937-7319ad40d33e/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
golang.org/x/image v0.0.0-20220902085622-e7cb96979f69/go.mod h1:doUCurBvlfPMKfmIpRIywoHmhN3VyhnoFDbvIEWF4hY=
|
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
|
||||||
golang.org/x/image v0.1.0 h1:r8Oj8ZA2Xy12/b5KZYj3tuv7NG/fBz3TwQVvpJ9l8Rk=
|
golang.org/x/image v0.6.0 h1:bR8b5okrPI3g/gyZakLZHeWxAR8Dn5CyxXv1hLH5g/4=
|
||||||
golang.org/x/image v0.1.0/go.mod h1:iyPr49SD/G/TBxYVB/9RRtGUT5eNbo2u4NamWeQcD5c=
|
golang.org/x/image v0.6.0/go.mod h1:MXLdDR43H7cDJq5GEGXEVeeNhPgi+YYEQ2pC1byI1x0=
|
||||||
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/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
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.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-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-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-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-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-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/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-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/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/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=
|
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
|
||||||
golang.org/x/oauth2 v0.2.0 h1:GtQkldQ9m7yvzCL1V+LrYow3Khe0eJH0w7RbX/VbaIU=
|
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||||
golang.org/x/oauth2 v0.2.0/go.mod h1:Cwn6afJ8jrQwYMxQDTpISoXmXW9I6qF6vDeuuoX3Ibs=
|
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-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-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.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 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-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-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-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-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-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-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-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-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-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.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-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.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.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.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.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.6/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.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.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-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.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.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.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-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-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 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
|
||||||
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
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-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.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
|
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
|
||||||
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
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/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 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
|
||||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
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/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.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
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-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"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/bedrock-tool/bedrocktool/locale"
|
||||||
"github.com/bedrock-tool/bedrocktool/utils"
|
"github.com/bedrock-tool/bedrocktool/utils"
|
||||||
|
|
||||||
"github.com/google/subcommands"
|
|
||||||
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
|
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
@ -22,66 +22,67 @@ func init() {
|
||||||
utils.RegisterCommand(&CaptureCMD{})
|
utils.RegisterCommand(&CaptureCMD{})
|
||||||
}
|
}
|
||||||
|
|
||||||
var dump_lock sync.Mutex
|
var dumpLock sync.Mutex
|
||||||
|
|
||||||
func dump_packet(f io.WriteCloser, toServer bool, payload []byte) {
|
func dumpPacket(f io.WriteCloser, toServer bool, payload []byte) {
|
||||||
dump_lock.Lock()
|
dumpLock.Lock()
|
||||||
defer dump_lock.Unlock()
|
defer dumpLock.Unlock()
|
||||||
f.Write([]byte{0xAA, 0xAA, 0xAA, 0xAA})
|
f.Write([]byte{0xAA, 0xAA, 0xAA, 0xAA})
|
||||||
packet_size := uint32(len(payload))
|
packetSize := uint32(len(payload))
|
||||||
binary.Write(f, binary.LittleEndian, packet_size)
|
binary.Write(f, binary.LittleEndian, packetSize)
|
||||||
binary.Write(f, binary.LittleEndian, toServer)
|
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 {
|
if err != nil {
|
||||||
logrus.Error(err)
|
logrus.Error(err)
|
||||||
}
|
}
|
||||||
|
if n < int(packetSize) {
|
||||||
|
f.Write(make([]byte, int(packetSize)-n))
|
||||||
|
}
|
||||||
f.Write([]byte{0xBB, 0xBB, 0xBB, 0xBB})
|
f.Write([]byte{0xBB, 0xBB, 0xBB, 0xBB})
|
||||||
}
|
}
|
||||||
|
|
||||||
type CaptureCMD struct {
|
type CaptureCMD struct {
|
||||||
server_address string
|
ServerAddress string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*CaptureCMD) Name() string { return "capture" }
|
func (*CaptureCMD) Name() string { return "capture" }
|
||||||
func (*CaptureCMD) Synopsis() string { return "capture packets in a pcap file" }
|
func (*CaptureCMD) Synopsis() string { return locale.Loc("capture_synopsis", nil) }
|
||||||
|
func (c *CaptureCMD) SetFlags(f *flag.FlagSet) {
|
||||||
func (p *CaptureCMD) SetFlags(f *flag.FlagSet) {
|
f.StringVar(&c.ServerAddress, "address", "", "remote server address")
|
||||||
f.StringVar(&p.server_address, "address", "", "remote server address")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CaptureCMD) Usage() string {
|
func (c *CaptureCMD) Execute(ctx context.Context, ui utils.UI) error {
|
||||||
return c.Name() + ": " + c.Synopsis() + "\n" + utils.SERVER_ADDRESS_HELP
|
address, hostname, err := utils.ServerInput(ctx, c.ServerAddress)
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CaptureCMD) Execute(ctx context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
|
|
||||||
address, hostname, err := utils.ServerInput(ctx, c.server_address)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Fatal(err)
|
return err
|
||||||
return 1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
if err != nil {
|
||||||
logrus.Fatal(err)
|
return err
|
||||||
return 1
|
|
||||||
}
|
}
|
||||||
defer fio.Close()
|
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) {
|
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)
|
buf := bytes.NewBuffer(nil)
|
||||||
header.Write(buf)
|
header.Write(buf)
|
||||||
buf.Write(payload)
|
buf.Write(payload)
|
||||||
dump_packet(fio, from_client, buf.Bytes())
|
dumpPacket(fio, IsfromClient, buf.Bytes())
|
||||||
}
|
}
|
||||||
|
|
||||||
err = proxy.Run(ctx, address)
|
err = proxy.Run(ctx, address)
|
||||||
time.Sleep(2 * time.Second)
|
time.Sleep(2 * time.Second)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Fatal(err)
|
return err
|
||||||
return 1
|
|
||||||
}
|
}
|
||||||
return 0
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,49 +7,46 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/bedrock-tool/bedrocktool/locale"
|
||||||
"github.com/bedrock-tool/bedrocktool/utils"
|
"github.com/bedrock-tool/bedrocktool/utils"
|
||||||
|
|
||||||
"github.com/google/subcommands"
|
|
||||||
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
|
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ChatLogCMD struct {
|
type ChatLogCMD struct {
|
||||||
Address string
|
ServerAddress string
|
||||||
verbose bool
|
Verbose bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*ChatLogCMD) Name() string { return "chat-log" }
|
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) {
|
func (c *ChatLogCMD) SetFlags(f *flag.FlagSet) {
|
||||||
f.StringVar(&c.Address, "address", "", "remote server address")
|
f.StringVar(&c.ServerAddress, "address", "", "remote server address")
|
||||||
f.BoolVar(&c.verbose, "v", false, "verbose")
|
f.BoolVar(&c.Verbose, "v", false, "verbose")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ChatLogCMD) Usage() string {
|
func (c *ChatLogCMD) Execute(ctx context.Context, ui utils.UI) error {
|
||||||
return c.Name() + ": " + c.Synopsis() + "\n" + utils.SERVER_ADDRESS_HELP
|
address, hostname, err := utils.ServerInput(ctx, c.ServerAddress)
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ChatLogCMD) Execute(ctx context.Context, flags *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
|
|
||||||
address, hostname, err := utils.ServerInput(ctx, c.Address)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Error(err)
|
return err
|
||||||
return 1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
filename := fmt.Sprintf("%s_%s_chat.log", hostname, time.Now().Format("2006-01-02_15-04-05_Z07"))
|
filename := fmt.Sprintf("%s_%s_chat.log", hostname, time.Now().Format("2006-01-02_15-04-05_Z07"))
|
||||||
f, err := os.Create(filename)
|
f, err := os.Create(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Fatal(err)
|
return err
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
proxy := utils.NewProxy(logrus.StandardLogger())
|
proxy, err := utils.NewProxy()
|
||||||
proxy.PacketCB = func(pk packet.Packet, proxy *utils.ProxyContext, toServer bool) (packet.Packet, error) {
|
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 {
|
if text, ok := pk.(*packet.Text); ok {
|
||||||
logLine := text.Message
|
logLine := text.Message
|
||||||
if c.verbose {
|
if c.Verbose {
|
||||||
logLine += fmt.Sprintf(" (TextType: %d | XUID: %s | PlatformChatID: %s)", text.TextType, text.XUID, text.PlatformChatID)
|
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)))
|
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
|
return pk, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := proxy.Run(ctx, address); err != nil {
|
err = proxy.Run(ctx, address)
|
||||||
logrus.Error(err)
|
return err
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|
|
@ -5,39 +5,31 @@ import (
|
||||||
"flag"
|
"flag"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/bedrock-tool/bedrocktool/locale"
|
||||||
"github.com/bedrock-tool/bedrocktool/utils"
|
"github.com/bedrock-tool/bedrocktool/utils"
|
||||||
|
|
||||||
"github.com/google/subcommands"
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type DebugProxyCMD struct {
|
type DebugProxyCMD struct {
|
||||||
Address string
|
ServerAddress string
|
||||||
filter string
|
Filter string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*DebugProxyCMD) Name() string { return "debug-proxy" }
|
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) {
|
func (c *DebugProxyCMD) SetFlags(f *flag.FlagSet) {
|
||||||
f.StringVar(&c.Address, "address", "", "remote server address")
|
f.StringVar(&c.ServerAddress, "address", "", locale.Loc("remote_address", nil))
|
||||||
f.StringVar(&c.filter, "filter", "", "packets to not show")
|
f.StringVar(&c.Filter, "filter", "", locale.Loc("packet_filter", nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *DebugProxyCMD) Usage() string {
|
func (c *DebugProxyCMD) Execute(ctx context.Context, ui utils.UI) error {
|
||||||
return c.Name() + ": " + c.Synopsis() + "\n" + utils.SERVER_ADDRESS_HELP
|
address, _, err := utils.ServerInput(ctx, c.ServerAddress)
|
||||||
}
|
|
||||||
|
|
||||||
func (c *DebugProxyCMD) Execute(ctx context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
|
|
||||||
address, _, err := utils.ServerInput(ctx, c.Address)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Error(err)
|
return err
|
||||||
return 1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
utils.G_debug = true
|
utils.Options.Debug = true
|
||||||
|
|
||||||
filters := strings.Split(c.filter, ",")
|
filters := strings.Split(c.Filter, ",")
|
||||||
if len(filters) > 0 {
|
if len(filters) > 0 {
|
||||||
for _, v := range filters {
|
for _, v := range filters {
|
||||||
if len(v) == 0 {
|
if len(v) == 0 {
|
||||||
|
@ -52,12 +44,12 @@ func (c *DebugProxyCMD) Execute(ctx context.Context, f *flag.FlagSet, _ ...inter
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
proxy := utils.NewProxy(logrus.StandardLogger())
|
proxy, err := utils.NewProxy()
|
||||||
if err := proxy.Run(ctx, address); err != nil {
|
if err != nil {
|
||||||
logrus.Error(err)
|
return err
|
||||||
return 1
|
|
||||||
}
|
}
|
||||||
return 0
|
err = proxy.Run(ctx, address)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
//go:build false
|
||||||
|
|
||||||
package subcommands
|
package subcommands
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -7,8 +9,10 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/bedrock-tool/bedrocktool/locale"
|
||||||
"github.com/bedrock-tool/bedrocktool/utils"
|
"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/dragonfly/server/world/mcdb"
|
||||||
"github.com/df-mc/goleveldb/leveldb/opt"
|
"github.com/df-mc/goleveldb/leveldb/opt"
|
||||||
"github.com/google/subcommands"
|
"github.com/google/subcommands"
|
||||||
|
@ -22,7 +26,7 @@ type MergeCMD struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*MergeCMD) Name() string { return "merge" }
|
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) {
|
func (c *MergeCMD) SetFlags(f *flag.FlagSet) {
|
||||||
f.BoolVar(&c.legacy, "legacy", false, "if the worlds are before 1.18")
|
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 {
|
func (c *MergeCMD) Execute(ctx context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
|
||||||
if f.NArg() == 0 {
|
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
|
return 1
|
||||||
}
|
}
|
||||||
c.worlds = f.Args()
|
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 {
|
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
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, world_name := range c.worlds {
|
for i, worldName := range c.worlds {
|
||||||
first := i == 0
|
first := i == 0
|
||||||
logrus.Infof("Adding %s", world_name)
|
logrus.Infof(locale.Loc("adding_world", locale.Strmap{"World": worldName}))
|
||||||
s, err := os.Stat(world_name)
|
s, err := os.Stat(worldName)
|
||||||
if errors.Is(err, os.ErrNotExist) {
|
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
|
if !s.IsDir() { // if its a zip temporarily unpack it to read it
|
||||||
f, _ := os.Open(world_name)
|
f, _ := os.Open(worldName)
|
||||||
world_name += "_unpack"
|
worldName += "_unpack"
|
||||||
utils.UnpackZip(f, s.Size(), world_name)
|
utils.UnpackZip(f, s.Size(), worldName)
|
||||||
}
|
}
|
||||||
// merge it into the state
|
// merge it into the state
|
||||||
err = c.merge_worlds(prov_out, world_name, first)
|
err = c.mergeWorlds(provOut, worldName, first)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Errorf("%s %s", world_name, err)
|
logrus.Errorf("%s %s", worldName, err)
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
if !s.IsDir() { // remove temp folder again
|
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)
|
logrus.Error(err)
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
time.Sleep(1 * time.Second)
|
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)
|
logrus.Infof("zipping: %s", err)
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
os.RemoveAll(out_name)
|
os.RemoveAll(outName)
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *MergeCMD) merge_worlds(prov_out *mcdb.Provider, folder string, first bool) error {
|
func (c *MergeCMD) mergeWorlds(provOut *mcdb.Provider, folder string, first bool) error {
|
||||||
prov_in, err := mcdb.New(logrus.StandardLogger(), folder, opt.DefaultCompression)
|
provIn, err := mcdb.New(logrus.StandardLogger(), folder, opt.DefaultCompression)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
count := 0
|
count := 0
|
||||||
existing := prov_out.Chunks(c.legacy)
|
existing := provOut.Chunks(c.legacy)
|
||||||
new := prov_in.Chunks(c.legacy)
|
new := provIn.Chunks(c.legacy)
|
||||||
for i := range new {
|
for i := range new {
|
||||||
if _, ok := existing[i]; !ok {
|
if _, ok := existing[i]; !ok {
|
||||||
d := i.D
|
d := i.D
|
||||||
// chunks
|
// chunks
|
||||||
ch, _, err := prov_in.LoadChunk(i.P, d)
|
ch, _, err := provIn.LoadChunk(i.P, d)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// blockNBT
|
// blockNBT
|
||||||
n, err := prov_in.LoadBlockNBT(i.P, i.D)
|
n, err := provIn.LoadBlockNBT(i.P, i.D)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// entities
|
// entities
|
||||||
entities, err := prov_in.LoadEntities(i.P, i.D)
|
entities, err := provIn.LoadEntities(i.P, i.D, entity.DefaultRegistry)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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
|
return err
|
||||||
}
|
}
|
||||||
count += 1
|
count += 1
|
||||||
|
@ -127,9 +131,9 @@ func (c *MergeCMD) merge_worlds(prov_out *mcdb.Provider, folder string, first bo
|
||||||
|
|
||||||
if first {
|
if first {
|
||||||
logrus.Debug("Applying Settings and level.dat")
|
logrus.Debug("Applying Settings and level.dat")
|
||||||
prov_out.SaveSettings(prov_in.Settings())
|
provOut.SaveSettings(provIn.Settings())
|
||||||
out_ld := prov_out.LevelDat()
|
outLd := provOut.LevelDat()
|
||||||
copier.Copy(out_ld, prov_in.LevelDat())
|
copier.Copy(outLd, provIn.LevelDat())
|
||||||
}
|
}
|
||||||
logrus.Infof("Added: %d", count)
|
logrus.Infof("Added: %d", count)
|
||||||
return nil
|
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
|
package skins
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"image"
|
|
||||||
"image/png"
|
|
||||||
"io"
|
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"strings"
|
"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"
|
||||||
|
|
||||||
"github.com/flytam/filenamify"
|
"github.com/google/uuid"
|
||||||
"github.com/google/subcommands"
|
|
||||||
"github.com/sandertv/gophertunnel/minecraft"
|
|
||||||
"github.com/sandertv/gophertunnel/minecraft/protocol"
|
"github.com/sandertv/gophertunnel/minecraft/protocol"
|
||||||
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
|
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Skin struct {
|
|
||||||
protocol.Skin
|
|
||||||
}
|
|
||||||
|
|
||||||
type SkinMeta struct {
|
type SkinMeta struct {
|
||||||
SkinID string
|
SkinID string
|
||||||
PlayFabID string
|
PlayFabID string
|
||||||
|
@ -39,267 +31,159 @@ type SkinMeta struct {
|
||||||
PersonaPieces []protocol.PersonaPiece
|
PersonaPieces []protocol.PersonaPiece
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteGeometry writes the geometry json for the skin to output_path
|
type skinsSession struct {
|
||||||
func (skin *Skin) WriteGeometry(output_path string) error {
|
PlayerNameFilter string
|
||||||
f, err := os.Create(output_path)
|
OnlyIfHasGeometry bool
|
||||||
if err != nil {
|
ServerName string
|
||||||
return fmt.Errorf("failed to write Geometry %s: %s", output_path, err)
|
Proxy *utils.ProxyContext
|
||||||
}
|
fpath string
|
||||||
defer f.Close()
|
|
||||||
io.Copy(f, bytes.NewReader(skin.SkinGeometry))
|
playerSkinPacks map[uuid.UUID]*SkinPack
|
||||||
return nil
|
playerNames map[uuid.UUID]string
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteCape writes the cape as a png at output_path
|
func NewSkinsSession(proxy *utils.ProxyContext, serverName, fpath string) *skinsSession {
|
||||||
func (skin *Skin) WriteCape(output_path string) error {
|
return &skinsSession{
|
||||||
f, err := os.Create(output_path)
|
ServerName: serverName,
|
||||||
if err != nil {
|
Proxy: proxy,
|
||||||
return fmt.Errorf("failed to write Cape %s: %s", output_path, err)
|
fpath: fpath,
|
||||||
}
|
|
||||||
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 {
|
playerSkinPacks: make(map[uuid.UUID]*SkinPack),
|
||||||
return fmt.Errorf("error writing skin: %s", err)
|
playerNames: make(map[uuid.UUID]string),
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteAnimations writes skin animations to the folder
|
func (s *skinsSession) AddPlayerSkin(playerID uuid.UUID, playerName string, skin *Skin) (added bool) {
|
||||||
func (skin *Skin) WriteAnimations(output_path string) error {
|
p, ok := s.playerSkinPacks[playerID]
|
||||||
logrus.Warnf("%s has animations (unimplemented)", output_path)
|
if !ok {
|
||||||
return nil
|
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 (s *skinsSession) AddSkin(playerName string, playerID uuid.UUID, playerSkin *protocol.Skin) (string, *Skin, bool) {
|
||||||
func (skin *Skin) WriteTexture(output_path string) error {
|
if playerName == "" {
|
||||||
f, err := os.Create(output_path)
|
playerName = s.playerNames[playerID]
|
||||||
if err != nil {
|
if playerName == "" {
|
||||||
return fmt.Errorf("error writing Texture: %s", err)
|
playerName = playerID.String()
|
||||||
}
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if have_cape {
|
if !strings.HasPrefix(playerName, s.PlayerNameFilter) {
|
||||||
if err := skin.WriteCape(path.Join(skin_dir, "cape.png")); err != nil {
|
return "", nil, false
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
s.playerNames[playerID] = playerName
|
||||||
|
|
||||||
if err := skin.WriteMeta(path.Join(skin_dir, "metadata.json")); err != nil {
|
skin := &Skin{playerSkin}
|
||||||
return err
|
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
|
type skinAdd struct {
|
||||||
// internally converts the struct so it can use the extra methods
|
PlayerName string
|
||||||
func write_skin(output_path, name string, skin *protocol.Skin) {
|
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
func (s *skinsSession) ProcessPacket(pk packet.Packet) (out []skinAdd) {
|
||||||
skin_players = make(map[string]string)
|
switch pk := pk.(type) {
|
||||||
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)
|
|
||||||
case *packet.PlayerList:
|
case *packet.PlayerList:
|
||||||
if _pk.ActionType == 1 { // remove
|
if pk.ActionType == 1 { // remove
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
for _, player := range _pk.Entries {
|
for _, player := range pk.Entries {
|
||||||
player_name := utils.CleanupName(player.Username)
|
playerName, skin, wasAdded := s.AddSkin(utils.CleanupName(player.Username), player.UUID, &player.Skin)
|
||||||
if player_name == "" {
|
if wasAdded {
|
||||||
player_name = player.UUID.String()
|
out = append(out, skinAdd{
|
||||||
|
PlayerName: playerName,
|
||||||
|
Skin: skin.Skin,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
if !strings.HasPrefix(player_name, filter) {
|
}
|
||||||
return
|
case *packet.AddPlayer:
|
||||||
}
|
if _, ok := s.playerNames[pk.UUID]; !ok {
|
||||||
if only_if_geom && len(player.Skin.SkinGeometry) == 0 {
|
s.playerNames[pk.UUID] = utils.CleanupName(pk.Username)
|
||||||
return
|
|
||||||
}
|
|
||||||
skin_players[player.UUID.String()] = player_name
|
|
||||||
save_player_skin(conn, out_path, player_name, &player.Skin)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
type SkinCMD struct {
|
type SkinCMD struct {
|
||||||
server_address string
|
ServerAddress string
|
||||||
filter string
|
Filter string
|
||||||
|
NoProxy bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*SkinCMD) Name() string { return "skins" }
|
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) {
|
func (c *SkinCMD) SetFlags(f *flag.FlagSet) {
|
||||||
f.StringVar(&c.server_address, "address", "", "remote server address")
|
f.StringVar(&c.ServerAddress, "address", "", locale.Loc("remote_address", nil))
|
||||||
f.StringVar(&c.filter, "filter", "", "player name filter prefix")
|
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 {
|
func (c *SkinCMD) Execute(ctx context.Context, ui utils.UI) error {
|
||||||
return c.Name() + ": " + c.Synopsis() + "\n"
|
address, hostname, err := utils.ServerInput(ctx, c.ServerAddress)
|
||||||
}
|
|
||||||
|
|
||||||
func (c *SkinCMD) Execute(ctx context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
|
|
||||||
address, hostname, err := utils.ServerInput(ctx, c.server_address)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprint(os.Stderr, err)
|
return err
|
||||||
return 1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
serverConn, err := utils.ConnectServer(ctx, address, nil, false, nil)
|
proxy, _ := utils.NewProxy()
|
||||||
if err != nil {
|
proxy.WithClient = !c.NoProxy
|
||||||
fmt.Fprint(os.Stderr, err)
|
proxy.OnClientConnect = func(proxy *utils.ProxyContext, hasClient bool) {
|
||||||
return 1
|
ui.Message(messages.SetUIState, messages.UIStateConnecting)
|
||||||
}
|
}
|
||||||
defer serverConn.Close()
|
proxy.ConnectCB = func(proxy *utils.ProxyContext, err error) bool {
|
||||||
|
|
||||||
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()
|
|
||||||
if err != nil {
|
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() {
|
func init() {
|
||||||
|
|
|
@ -1,45 +1,38 @@
|
||||||
|
// Package subcommands ...
|
||||||
package subcommands
|
package subcommands
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"flag"
|
"flag"
|
||||||
|
|
||||||
|
"github.com/bedrock-tool/bedrocktool/locale"
|
||||||
"github.com/bedrock-tool/bedrocktool/utils"
|
"github.com/bedrock-tool/bedrocktool/utils"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"github.com/google/subcommands"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type UpdateCMD struct{}
|
type UpdateCMD struct{}
|
||||||
|
|
||||||
func (*UpdateCMD) Name() string { return "update" }
|
func (*UpdateCMD) Name() string { return "update" }
|
||||||
func (*UpdateCMD) Synopsis() string { return "self updates to latest version" }
|
func (*UpdateCMD) Synopsis() string { return locale.Loc("update_synopsis", nil) }
|
||||||
|
|
||||||
func (c *UpdateCMD) SetFlags(f *flag.FlagSet) {}
|
func (c *UpdateCMD) SetFlags(f *flag.FlagSet) {}
|
||||||
|
|
||||||
func (c *UpdateCMD) Usage() string {
|
func (c *UpdateCMD) Execute(ctx context.Context, ui utils.UI) error {
|
||||||
return c.Name() + ": " + c.Synopsis() + "\n"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *UpdateCMD) Execute(ctx context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
|
|
||||||
newVersion, err := utils.Updater.UpdateAvailable()
|
newVersion, err := utils.Updater.UpdateAvailable()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Error(err)
|
return err
|
||||||
return 1
|
|
||||||
}
|
}
|
||||||
if newVersion == "" {
|
if newVersion == "" {
|
||||||
logrus.Info("No Updates available.")
|
logrus.Info(locale.Loc("no_update", nil))
|
||||||
return 0
|
return nil
|
||||||
}
|
}
|
||||||
logrus.Infof("Updating to %s", newVersion)
|
logrus.Infof(locale.Loc("updating", locale.Strmap{"Version": newVersion}))
|
||||||
|
|
||||||
if err := utils.Updater.Update(); err != nil {
|
if err := utils.Updater.Update(); err != nil {
|
||||||
logrus.Error(err)
|
return err
|
||||||
return 1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
logrus.Infof("Updated!")
|
logrus.Infof(locale.Loc("updated", nil))
|
||||||
return 0
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
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 {
|
} else {
|
||||||
b, found := world.BlockByRuntimeID(rid)
|
b, found := world.BlockByRuntimeID(rid)
|
||||||
if found {
|
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 {
|
if _, ok := b.(block.Water); ok {
|
||||||
y2 := c.HeightMap().At(x, z)
|
y2 := c.HeightMap().At(x, z)
|
||||||
depth := y - y2
|
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 {
|
func chunkGetColorAt(c *chunk.Chunk, x uint8, y int16, z uint8) color.RGBA {
|
||||||
p := cube.Pos{int(x), int(y), int(z)}
|
p := cube.Pos{int(x), int(y), int(z)}
|
||||||
have_up := false
|
haveUp := false
|
||||||
p.Side(cube.FaceUp).Neighbours(func(neighbour cube.Pos) {
|
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() {
|
if neighbour.X() < 0 || neighbour.X() >= 16 || neighbour.Z() < 0 || neighbour.Z() >= 16 || neighbour.Y() > c.Range().Max() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !have_up {
|
if !haveUp {
|
||||||
block_rid := c.Block(uint8(neighbour[0]), int16(neighbour[1]), uint8(neighbour[2]), 0)
|
blockRid := c.Block(uint8(neighbour[0]), int16(neighbour[1]), uint8(neighbour[2]), 0)
|
||||||
if block_rid > 0 {
|
if blockRid > 0 {
|
||||||
b, found := world.BlockByRuntimeID(block_rid)
|
b, found := world.BlockByRuntimeID(blockRid)
|
||||||
if found {
|
if found {
|
||||||
if _, ok := b.(block.Air); !ok {
|
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)
|
col := blockColorAt(c, x, y, z)
|
||||||
|
|
||||||
if have_up {
|
if haveUp {
|
||||||
if col.R > 10 {
|
if col.R > 10 {
|
||||||
col.R -= 10
|
col.R -= 10
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,18 +1,19 @@
|
||||||
package world
|
package world_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"image/png"
|
"image/png"
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/bedrock-tool/bedrocktool/subcommands/world"
|
||||||
"github.com/df-mc/dragonfly/server/block/cube"
|
"github.com/df-mc/dragonfly/server/block/cube"
|
||||||
"github.com/df-mc/dragonfly/server/world/chunk"
|
"github.com/df-mc/dragonfly/server/world/chunk"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Test(t *testing.T) {
|
func Test(t *testing.T) {
|
||||||
data, _ := os.ReadFile("chunk.bin")
|
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)
|
||||||
i := Chunk2Img(ch)
|
i := world.Chunk2Img(ch)
|
||||||
f, _ := os.Create("chunk.png")
|
f, _ := os.Create("chunk.png")
|
||||||
png.Encode(f, i)
|
png.Encode(f, i)
|
||||||
f.Close()
|
f.Close()
|
||||||
|
@ -21,7 +22,7 @@ func Test(t *testing.T) {
|
||||||
func Benchmark_chunk_decode(b *testing.B) {
|
func Benchmark_chunk_decode(b *testing.B) {
|
||||||
data, _ := os.ReadFile("chunk.bin")
|
data, _ := os.ReadFile("chunk.bin")
|
||||||
for i := 0; i < b.N; i++ {
|
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 {
|
if err != nil {
|
||||||
b.Error(err)
|
b.Error(err)
|
||||||
}
|
}
|
||||||
|
@ -30,9 +31,9 @@ func Benchmark_chunk_decode(b *testing.B) {
|
||||||
|
|
||||||
func Benchmark_render_chunk(b *testing.B) {
|
func Benchmark_render_chunk(b *testing.B) {
|
||||||
data, _ := os.ReadFile("chunk.bin")
|
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++ {
|
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
|
package world
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"image"
|
"image"
|
||||||
"image/draw"
|
"image/draw"
|
||||||
"math"
|
"math"
|
||||||
"os"
|
"sync"
|
||||||
"time"
|
"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"
|
||||||
"golang.design/x/lockfree"
|
"golang.design/x/lockfree"
|
||||||
|
|
||||||
|
@ -15,47 +16,49 @@ import (
|
||||||
"github.com/sandertv/gophertunnel/minecraft/protocol"
|
"github.com/sandertv/gophertunnel/minecraft/protocol"
|
||||||
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
|
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
|
||||||
"github.com/sirupsen/logrus"
|
"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
|
// MapItemPacket tells the client that it has a map with id 0x424242 in the offhand
|
||||||
var MAP_ITEM_PACKET packet.InventoryContent = packet.InventoryContent{
|
var MapItemPacket packet.InventoryContent = packet.InventoryContent{
|
||||||
WindowID: 119,
|
WindowID: 119,
|
||||||
Content: []protocol.ItemInstance{
|
Content: []protocol.ItemInstance{
|
||||||
{
|
{
|
||||||
StackNetworkID: 1,
|
StackNetworkID: 1, // random if auth inv
|
||||||
Stack: protocol.ItemStack{
|
Stack: protocol.ItemStack{
|
||||||
ItemType: protocol.ItemType{
|
ItemType: protocol.ItemType{
|
||||||
NetworkID: 420,
|
NetworkID: 420, // overwritten in onconnect
|
||||||
MetadataValue: 0,
|
MetadataValue: 0,
|
||||||
},
|
},
|
||||||
BlockRuntimeID: 0,
|
BlockRuntimeID: 0,
|
||||||
Count: 1,
|
Count: 1,
|
||||||
NBTData: map[string]interface{}{
|
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
|
// get the chunk coord bounds
|
||||||
|
i := 0
|
||||||
for _ch := range m.renderedChunks {
|
for _ch := range m.renderedChunks {
|
||||||
if _ch.X() < min.X() {
|
if _ch.X() < min.X() || i == 0 {
|
||||||
min[0] = _ch.X()
|
min[0] = _ch.X()
|
||||||
}
|
}
|
||||||
if _ch.Z() < min.Z() {
|
if _ch.Z() < min.Z() || i == 0 {
|
||||||
min[1] = _ch.Z()
|
min[1] = _ch.Z()
|
||||||
}
|
}
|
||||||
if _ch.X() > max.X() {
|
if _ch.X() > max.X() || i == 0 {
|
||||||
max[0] = _ch.X()
|
max[0] = _ch.X()
|
||||||
}
|
}
|
||||||
if _ch.Z() > max.Z() {
|
if _ch.Z() > max.Z() || i == 0 {
|
||||||
max[1] = _ch.Z()
|
max[1] = _ch.Z()
|
||||||
}
|
}
|
||||||
|
i++
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -71,6 +74,8 @@ type MapUI struct {
|
||||||
renderQueue *lockfree.Queue
|
renderQueue *lockfree.Queue
|
||||||
renderedChunks map[protocol.ChunkPos]*image.RGBA // prerendered chunks
|
renderedChunks map[protocol.ChunkPos]*image.RGBA // prerendered chunks
|
||||||
needRedraw bool // when the map has updated this is true
|
needRedraw bool // when the map has updated this is true
|
||||||
|
showOnGui bool
|
||||||
|
l sync.RWMutex
|
||||||
|
|
||||||
ticker *time.Ticker
|
ticker *time.Ticker
|
||||||
w *WorldState
|
w *WorldState
|
||||||
|
@ -89,6 +94,27 @@ func NewMapUI(w *WorldState) *MapUI {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MapUI) Start() {
|
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)
|
m.ticker = time.NewTicker(33 * time.Millisecond)
|
||||||
go func() {
|
go func() {
|
||||||
for range m.ticker.C {
|
for range m.ticker.C {
|
||||||
|
@ -98,11 +124,12 @@ func (m *MapUI) Start() {
|
||||||
|
|
||||||
if m.w.proxy.Client != nil {
|
if m.w.proxy.Client != nil {
|
||||||
if err := m.w.proxy.Client.WritePacket(&packet.ClientBoundMapItemData{
|
if err := m.w.proxy.Client.WritePacket(&packet.ClientBoundMapItemData{
|
||||||
MapID: VIEW_MAP_ID,
|
MapID: ViewMapID,
|
||||||
|
Scale: 4,
|
||||||
Width: 128,
|
Width: 128,
|
||||||
Height: 128,
|
Height: 128,
|
||||||
Pixels: utils.Img2rgba(m.img),
|
Pixels: utils.Img2rgba(m.img),
|
||||||
UpdateFlags: 2,
|
UpdateFlags: packet.MapUpdateFlagTexture,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
logrus.Error(err)
|
logrus.Error(err)
|
||||||
return
|
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() {
|
func (m *MapUI) Stop() {
|
||||||
|
@ -121,7 +163,9 @@ func (m *MapUI) Stop() {
|
||||||
|
|
||||||
// Reset resets the map to inital state
|
// Reset resets the map to inital state
|
||||||
func (m *MapUI) Reset() {
|
func (m *MapUI) Reset() {
|
||||||
|
m.l.Lock()
|
||||||
m.renderedChunks = make(map[protocol.ChunkPos]*image.RGBA)
|
m.renderedChunks = make(map[protocol.ChunkPos]*image.RGBA)
|
||||||
|
m.l.Unlock()
|
||||||
m.SchedRedraw()
|
m.SchedRedraw()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -139,23 +183,10 @@ func (m *MapUI) SchedRedraw() {
|
||||||
m.needRedraw = true
|
m.needRedraw = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// draw_img_scaled_pos draws src onto dst at bottom_left, scaled to size
|
// Redraw draws chunk images to the map image
|
||||||
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
|
|
||||||
func (m *MapUI) Redraw() {
|
func (m *MapUI) Redraw() {
|
||||||
|
m.l.Lock()
|
||||||
|
updatedChunks := []protocol.ChunkPos{}
|
||||||
for {
|
for {
|
||||||
r, ok := m.renderQueue.Dequeue().(*RenderElem)
|
r, ok := m.renderQueue.Dequeue().(*RenderElem)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -164,9 +195,11 @@ func (m *MapUI) Redraw() {
|
||||||
if r.ch != nil {
|
if r.ch != nil {
|
||||||
m.renderedChunks[r.pos] = Chunk2Img(r.ch)
|
m.renderedChunks[r.pos] = Chunk2Img(r.ch)
|
||||||
} else {
|
} else {
|
||||||
m.renderedChunks[r.pos] = black_16x16
|
m.renderedChunks[r.pos] = black16x16
|
||||||
}
|
}
|
||||||
|
updatedChunks = append(updatedChunks, r.pos)
|
||||||
}
|
}
|
||||||
|
m.l.Unlock()
|
||||||
|
|
||||||
middle := protocol.ChunkPos{
|
middle := protocol.ChunkPos{
|
||||||
int32(m.w.PlayerPos.Position.X()),
|
int32(m.w.PlayerPos.Position.X()),
|
||||||
|
@ -174,60 +207,62 @@ func (m *MapUI) Redraw() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// total_width := 32 * math.Ceil(float64(chunks_x)/32)
|
// total_width := 32 * math.Ceil(float64(chunks_x)/32)
|
||||||
chunks_per_line := float64(128 / m.zoomLevel)
|
chunksPerLine := float64(128 / m.zoomLevel)
|
||||||
px_per_block := 128 / chunks_per_line / 16 // how many pixels per block
|
pxPerBlock := 128 / chunksPerLine / 16 // how many pixels per block
|
||||||
sz_chunk := int(math.Floor(px_per_block * 16))
|
pxSizeChunk := int(math.Floor(pxPerBlock * 16))
|
||||||
|
|
||||||
for i := 0; i < len(m.img.Pix); i++ { // clear canvas
|
for i := 0; i < len(m.img.Pix); i++ { // clear canvas
|
||||||
m.img.Pix[i] = 0
|
m.img.Pix[i] = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m.l.RLock()
|
||||||
for _ch := range m.renderedChunks {
|
for _ch := range m.renderedChunks {
|
||||||
relative_middle_x := float64(_ch.X()*16 - middle.X())
|
relativeMiddleX := float64(_ch.X()*16 - middle.X())
|
||||||
relative_middle_z := float64(_ch.Z()*16 - middle.Z())
|
relativeMiddleZ := float64(_ch.Z()*16 - middle.Z())
|
||||||
px_pos := image.Point{ // bottom left corner of the chunk on the map
|
px := image.Point{ // bottom left corner of the chunk on the map
|
||||||
X: int(math.Floor(relative_middle_x*px_per_block)) + 64,
|
X: int(math.Floor(relativeMiddleX*pxPerBlock)) + 64,
|
||||||
Y: int(math.Floor(relative_middle_z*px_per_block)) + 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() {
|
if !m.img.Rect.Intersect(image.Rect(px.X, px.Y, px.X+pxSizeChunk, px.Y+pxSizeChunk)).Empty() {
|
||||||
draw_img_scaled_pos(m.img, m.renderedChunks[_ch], px_pos, sz_chunk)
|
utils.DrawImgScaledPos(m.img, m.renderedChunks[_ch], px, pxSizeChunk)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
ChunkCount := len(m.renderedChunks)
|
||||||
draw_full := false
|
if m.showOnGui {
|
||||||
|
min, max := m.GetBounds()
|
||||||
if draw_full {
|
m.w.gui.Message(messages.UpdateMap, messages.UpdateMapPayload{
|
||||||
img2 := m.ToImage()
|
ChunkCount: ChunkCount,
|
||||||
buf := bytes.NewBuffer(nil)
|
Rotation: m.w.PlayerPos.Yaw,
|
||||||
bmp.Encode(buf, img2)
|
UpdatedTiles: updatedChunks,
|
||||||
os.WriteFile("test.bmp", buf.Bytes(), 0o777)
|
Tiles: m.renderedChunks,
|
||||||
|
BoundsMin: min,
|
||||||
|
BoundsMax: max,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
m.l.RUnlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MapUI) ToImage() *image.RGBA {
|
func (m *MapUI) ToImage() *image.RGBA {
|
||||||
// get the chunk coord bounds
|
// get the chunk coord bounds
|
||||||
min, max := m.get_bounds()
|
min, max := m.GetBounds()
|
||||||
chunks_x := int(max[0] - min[0] + 1) // how many chunk lengths is x coordinate
|
chunksX := int(max[0] - min[0] + 1) // how many chunk lengths is x coordinate
|
||||||
chunks_y := int(max[1] - min[1] + 1)
|
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
|
m.l.RLock()
|
||||||
middle_block_y := chunks_y / 2 * 16
|
for pos, tile := range m.renderedChunks {
|
||||||
|
px := image.Pt(
|
||||||
for pos := range m.renderedChunks {
|
int((pos.X()-min.X())*16),
|
||||||
px_pos := image.Point{
|
int((pos.Z()-min.Z())*16),
|
||||||
X: int(pos.X()*16) - middle_block_x + img2.Rect.Dx(),
|
)
|
||||||
Y: int(pos.Z()*16) - middle_block_y + img2.Rect.Dy(),
|
|
||||||
}
|
|
||||||
draw.Draw(img2, image.Rect(
|
draw.Draw(img2, image.Rect(
|
||||||
px_pos.X,
|
px.X, px.Y,
|
||||||
px_pos.Y,
|
px.X+16, px.Y+16,
|
||||||
px_pos.X+16,
|
), tile, image.Point{}, draw.Src)
|
||||||
px_pos.Y+16,
|
|
||||||
), m.renderedChunks[pos], image.Point{}, draw.Src)
|
|
||||||
}
|
}
|
||||||
|
m.l.RUnlock()
|
||||||
return img2
|
return img2
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -235,3 +270,27 @@ func (m *MapUI) SetChunk(pos protocol.ChunkPos, ch *chunk.Chunk) {
|
||||||
m.renderQueue.Enqueue(&RenderElem{pos, ch})
|
m.renderQueue.Enqueue(&RenderElem{pos, ch})
|
||||||
m.SchedRedraw()
|
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 (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"image"
|
"image"
|
||||||
"image/draw"
|
|
||||||
"image/png"
|
"image/png"
|
||||||
|
"math/rand"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"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"
|
||||||
"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/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"
|
||||||
"github.com/df-mc/dragonfly/server/world/chunk"
|
"github.com/df-mc/dragonfly/server/world/chunk"
|
||||||
"github.com/df-mc/dragonfly/server/world/mcdb"
|
"github.com/df-mc/dragonfly/server/world/mcdb"
|
||||||
"github.com/df-mc/goleveldb/leveldb/opt"
|
"github.com/df-mc/goleveldb/leveldb/opt"
|
||||||
"github.com/go-gl/mathgl/mgl32"
|
"github.com/go-gl/mathgl/mgl32"
|
||||||
"github.com/google/subcommands"
|
|
||||||
"github.com/sandertv/gophertunnel/minecraft/protocol"
|
"github.com/sandertv/gophertunnel/minecraft/protocol"
|
||||||
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
|
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
|
||||||
"github.com/sandertv/gophertunnel/minecraft/resource"
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
//_ "github.com/df-mc/dragonfly/server/block" // to load blocks
|
|
||||||
//_ "net/http/pprof"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type TPlayerPos struct {
|
type TPlayerPos struct {
|
||||||
|
@ -42,54 +38,61 @@ type TPlayerPos struct {
|
||||||
HeadYaw float32
|
HeadYaw float32
|
||||||
}
|
}
|
||||||
|
|
||||||
type itemContainer struct {
|
|
||||||
OpenPacket *packet.ContainerOpen
|
|
||||||
Content *packet.InventoryContent
|
|
||||||
}
|
|
||||||
|
|
||||||
// the state used for drawing and saving
|
// the state used for drawing and saving
|
||||||
|
|
||||||
type WorldState struct {
|
type WorldState struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
ispre118 bool
|
proxy *utils.ProxyContext
|
||||||
voidgen bool
|
mapUI *MapUI
|
||||||
|
gui utils.UI
|
||||||
|
bp *behaviourpack.BehaviourPack
|
||||||
|
|
||||||
|
// save state
|
||||||
chunks map[protocol.ChunkPos]*chunk.Chunk
|
chunks map[protocol.ChunkPos]*chunk.Chunk
|
||||||
blockNBT map[protocol.SubChunkPos][]map[string]any
|
blockNBT map[protocol.SubChunkPos][]map[string]any
|
||||||
openItemContainers map[byte]*itemContainer
|
openItemContainers map[byte]*itemContainer
|
||||||
airRid uint32
|
entities map[uint64]*entityState
|
||||||
|
Dim world.Dimension
|
||||||
Dim world.Dimension
|
PlayerPos TPlayerPos
|
||||||
WorldName string
|
worldCounter int
|
||||||
ServerName string
|
WorldName string
|
||||||
worldCounter int
|
ServerName string
|
||||||
packs map[string]*resource.Pack
|
ispre118 bool
|
||||||
|
|
||||||
|
// settings
|
||||||
|
voidGen bool
|
||||||
withPacks bool
|
withPacks bool
|
||||||
saveImage bool
|
saveImage bool
|
||||||
experimentInventory 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{
|
w := &WorldState{
|
||||||
|
ctx: ctx,
|
||||||
|
proxy: proxy,
|
||||||
|
mapUI: nil,
|
||||||
|
gui: ui,
|
||||||
|
bp: behaviourpack.New(ServerName),
|
||||||
|
ServerName: ServerName,
|
||||||
|
|
||||||
chunks: make(map[protocol.ChunkPos]*chunk.Chunk),
|
chunks: make(map[protocol.ChunkPos]*chunk.Chunk),
|
||||||
blockNBT: make(map[protocol.SubChunkPos][]map[string]any),
|
blockNBT: make(map[protocol.SubChunkPos][]map[string]any),
|
||||||
openItemContainers: make(map[byte]*itemContainer),
|
openItemContainers: make(map[byte]*itemContainer),
|
||||||
|
entities: make(map[uint64]*entityState),
|
||||||
Dim: nil,
|
Dim: nil,
|
||||||
WorldName: "world",
|
WorldName: "world",
|
||||||
PlayerPos: TPlayerPos{},
|
PlayerPos: TPlayerPos{},
|
||||||
airRid: 6692,
|
|
||||||
}
|
}
|
||||||
w.ui = NewMapUI(w)
|
w.mapUI = NewMapUI(w)
|
||||||
|
|
||||||
|
w.gui.Message(messages.Init, messages.InitPayload{
|
||||||
|
Handler: w.uiMessage,
|
||||||
|
})
|
||||||
|
|
||||||
return w
|
return w
|
||||||
}
|
}
|
||||||
|
|
||||||
var dimension_ids = map[uint8]world.Dimension{
|
var dimensionIDMap = map[uint8]world.Dimension{
|
||||||
0: world.Overworld,
|
0: world.Overworld,
|
||||||
1: world.Nether,
|
1: world.Nether,
|
||||||
2: world.End,
|
2: world.End,
|
||||||
|
@ -100,190 +103,104 @@ var dimension_ids = map[uint8]world.Dimension{
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
black_16x16 = image.NewRGBA(image.Rect(0, 0, 16, 16))
|
black16x16 = image.NewRGBA(image.Rect(0, 0, 16, 16))
|
||||||
Offset_table [24]protocol.SubChunkOffset
|
offsetTable [24]protocol.SubChunkOffset
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
for i := range Offset_table {
|
for i := range offsetTable {
|
||||||
Offset_table[i] = protocol.SubChunkOffset{0, int8(i), 0}
|
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{})
|
utils.RegisterCommand(&WorldCMD{})
|
||||||
}
|
}
|
||||||
|
|
||||||
type WorldCMD struct {
|
type WorldCMD struct {
|
||||||
Address string
|
ServerAddress string
|
||||||
packs bool
|
Packs bool
|
||||||
enableVoid bool
|
EnableVoid bool
|
||||||
saveImage bool
|
SaveImage bool
|
||||||
experimentInventory bool
|
ExperimentInventory bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*WorldCMD) Name() string { return "worlds" }
|
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) {
|
func (c *WorldCMD) SetFlags(f *flag.FlagSet) {
|
||||||
f.StringVar(&p.Address, "address", "", "remote server address")
|
f.StringVar(&c.ServerAddress, "address", "", locale.Loc("remote_address", nil))
|
||||||
f.BoolVar(&p.packs, "packs", false, "save resourcepacks to the worlds")
|
f.BoolVar(&c.Packs, "packs", false, locale.Loc("save_packs_with_world", nil))
|
||||||
f.BoolVar(&p.enableVoid, "void", true, "if false, saves with default flat generator")
|
f.BoolVar(&c.EnableVoid, "void", true, locale.Loc("enable_void", nil))
|
||||||
f.BoolVar(&p.saveImage, "image", false, "saves an png of the map at the end")
|
f.BoolVar(&c.SaveImage, "image", false, locale.Loc("save_image", nil))
|
||||||
f.BoolVar(&p.experimentInventory, "inv", false, "enable experimental block inventory saving")
|
f.BoolVar(&c.ExperimentInventory, "inv", false, locale.Loc("test_block_inv", nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *WorldCMD) Usage() string {
|
func (c *WorldCMD) Execute(ctx context.Context, ui utils.UI) error {
|
||||||
return c.Name() + ": " + c.Synopsis() + "\n" + utils.SERVER_ADDRESS_HELP
|
serverAddress, hostname, err := ui.ServerInput(ctx, c.ServerAddress)
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintln(os.Stderr, err)
|
return err
|
||||||
return 1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
w := NewWorldState()
|
proxy, err := utils.NewProxy()
|
||||||
w.voidgen = c.enableVoid
|
if err != nil {
|
||||||
w.ServerName = hostname
|
return err
|
||||||
w.withPacks = c.packs
|
}
|
||||||
w.saveImage = c.saveImage
|
|
||||||
w.experimentInventory = c.experimentInventory
|
|
||||||
w.ctx = ctx
|
|
||||||
|
|
||||||
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.ConnectCB = w.OnConnect
|
||||||
proxy.PacketCB = func(pk packet.Packet, proxy *utils.ProxyContext, toServer bool) (packet.Packet, error) {
|
proxy.OnClientConnect = func(proxy *utils.ProxyContext, hasClient bool) {
|
||||||
var forward 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 {
|
if toServer {
|
||||||
pk, forward = w.ProcessPacketClient(pk)
|
// from client
|
||||||
|
pk = w.processItemPacketsClient(pk, &forward)
|
||||||
|
pk = w.processMapPacketsClient(pk, &forward)
|
||||||
} else {
|
} else {
|
||||||
pk, forward = w.ProcessPacketServer(pk)
|
// from server
|
||||||
|
pk = w.processItemPacketsServer(pk)
|
||||||
|
pk = w.ProcessChunkPackets(pk)
|
||||||
|
pk = w.ProcessEntityPackets(pk)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !forward {
|
if !forward {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
return pk, 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 {
|
if err != nil {
|
||||||
fmt.Fprintln(os.Stderr, err)
|
return err
|
||||||
}
|
}
|
||||||
w.SaveAndReset()
|
w.SaveAndReset()
|
||||||
return 0
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *WorldState) setnameCommand(cmdline []string) bool {
|
func (w *WorldState) uiMessage(name string, data interface{}) messages.MessageResponse {
|
||||||
w.WorldName = strings.Join(cmdline, " ")
|
r := messages.MessageResponse{
|
||||||
w.proxy.SendMessage(fmt.Sprintf("worldName is now: %s", w.WorldName))
|
Ok: false,
|
||||||
return true
|
Data: nil,
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
switch name {
|
||||||
ch, blockNBTs, err := chunk.NetworkDecode(w.airRid, pk.RawPayload, int(pk.SubChunkCount), w.Dim.Range(), w.ispre118)
|
case messages.SetVoidGen:
|
||||||
if err != nil {
|
set_void_gen := data.(messages.SetVoidGenPayload)
|
||||||
logrus.Error(err)
|
r.Ok = w.setVoidGen(set_void_gen.Value, true)
|
||||||
return
|
case messages.SetWorldName:
|
||||||
|
set_world_name := data.(messages.SetWorldNamePayload)
|
||||||
|
r.Ok = w.setWorldName(set_world_name.WorldName, true)
|
||||||
}
|
}
|
||||||
if blockNBTs != nil {
|
return r
|
||||||
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)]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *WorldState) SetPlayerPos(Position mgl32.Vec3, Pitch, Yaw, HeadYaw float32) {
|
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()) {
|
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() {
|
func (w *WorldState) Reset() {
|
||||||
w.chunks = make(map[protocol.ChunkPos]*chunk.Chunk)
|
w.chunks = make(map[protocol.ChunkPos]*chunk.Chunk)
|
||||||
w.WorldName = fmt.Sprintf("world-%d", w.worldCounter)
|
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() {
|
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
|
// open world
|
||||||
folder := path.Join("worlds", fmt.Sprintf("%s/%s", w.ServerName, w.WorldName))
|
folder := path.Join("worlds", fmt.Sprintf("%s/%s", w.ServerName, w.WorldName))
|
||||||
|
@ -326,13 +298,26 @@ func (w *WorldState) SaveAndReset() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// save block nbt data
|
// 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
|
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...)
|
blockNBT[cp] = append(blockNBT[cp], v...)
|
||||||
}
|
}
|
||||||
for cp, v := range blockNBT {
|
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 {
|
if err != nil {
|
||||||
logrus.Error(err)
|
logrus.Error(err)
|
||||||
}
|
}
|
||||||
|
@ -415,66 +400,143 @@ func (w *WorldState) SaveAndReset() {
|
||||||
ld.ShowBorderEffect = gr.Value.(bool)
|
ld.ShowBorderEffect = gr.Value.(bool)
|
||||||
// todo
|
// todo
|
||||||
default:
|
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)
|
ld.RandomSeed = int64(gd.WorldSeed)
|
||||||
|
|
||||||
// void world
|
// 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.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
|
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.SaveSettings(s)
|
||||||
provider.Close()
|
if err = provider.Close(); err != nil {
|
||||||
|
logrus.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
w.worldCounter += 1
|
w.worldCounter += 1
|
||||||
|
|
||||||
for k, p := range w.packs {
|
type dep struct {
|
||||||
logrus.Infof("Adding resource pack: %s\n", k)
|
PackID string `json:"pack_id"`
|
||||||
pack_folder := path.Join(folder, "resource_packs", k)
|
Version [3]int `json:"version"`
|
||||||
os.MkdirAll(pack_folder, 0o755)
|
}
|
||||||
data := make([]byte, p.Len())
|
addPacksJSON := func(name string, deps []dep) {
|
||||||
p.ReadAt(data, 0)
|
f, err := os.Create(path.Join(folder, name))
|
||||||
utils.UnpackZip(bytes.NewReader(data), int64(len(data)), pack_folder)
|
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 {
|
if w.saveImage {
|
||||||
f, _ := os.Create(folder + ".png")
|
f, _ := os.Create(folder + ".png")
|
||||||
png.Encode(f, w.ui.ToImage())
|
png.Encode(f, w.mapUI.ToImage())
|
||||||
f.Close()
|
f.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
// zip it
|
// zip it
|
||||||
filename := folder + ".mcworld"
|
filename := folder + ".mcworld"
|
||||||
|
|
||||||
if err := utils.ZipFolder(filename, folder); err != nil {
|
if err := utils.ZipFolder(filename, folder); err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
}
|
}
|
||||||
logrus.Infof("Saved: %s\n", filename)
|
logrus.Info(locale.Loc("saved", locale.Strmap{"Name": filename}))
|
||||||
os.RemoveAll(folder)
|
//os.RemoveAll(folder)
|
||||||
w.Reset()
|
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
|
w.proxy = proxy
|
||||||
gd := w.proxy.Server.GameData()
|
gd := w.proxy.Server.GameData()
|
||||||
|
|
||||||
if len(gd.CustomBlocks) > 0 {
|
world.InsertCustomItems(gd.Items)
|
||||||
logrus.Info("Using Custom Blocks")
|
|
||||||
/*
|
for _, ie := range gd.Items {
|
||||||
for _, be := range gd.CustomBlocks {
|
w.bp.AddItem(ie)
|
||||||
b := block.ServerCustomBlock(be)
|
|
||||||
world.RegisterBlock(b)
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if w.withPacks {
|
mapItemID, _ := world.ItemRidByName("minecraft:filled_map")
|
||||||
go func() {
|
MapItemPacket.Content[0].Stack.ItemType.NetworkID = mapItemID
|
||||||
w.packs, _ = utils.GetPacks(w.proxy.Server)
|
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
|
{ // check game version
|
||||||
|
@ -486,42 +548,28 @@ func (w *WorldState) OnConnect(proxy *utils.ProxyContext) {
|
||||||
w.ispre118 = ver < 18
|
w.ispre118 = ver < 18
|
||||||
}
|
}
|
||||||
if err != nil || len(gv) <= 1 {
|
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 {
|
if w.ispre118 {
|
||||||
logrus.Info("using legacy (< 1.18)")
|
logrus.Info(locale.Loc("using_under_118", nil))
|
||||||
dim_id += 10
|
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()
|
w.mapUI.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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
proxy.AddCommand(utils.IngameCommand{
|
proxy.AddCommand(utils.IngameCommand{
|
||||||
Exec: w.setnameCommand,
|
Exec: func(cmdline []string) bool {
|
||||||
|
return w.setWorldName(strings.Join(cmdline, " "), false)
|
||||||
|
},
|
||||||
Cmd: protocol.Command{
|
Cmd: protocol.Command{
|
||||||
Name: "setname",
|
Name: "setname",
|
||||||
Description: "set user defined name for this world",
|
Description: locale.Loc("setname_desc", nil),
|
||||||
Overloads: []protocol.CommandOverload{
|
Overloads: []protocol.CommandOverload{
|
||||||
{
|
{
|
||||||
Parameters: []protocol.CommandParameter{
|
Parameters: []protocol.CommandParameter{
|
||||||
|
@ -537,139 +585,14 @@ func (w *WorldState) OnConnect(proxy *utils.ProxyContext) {
|
||||||
})
|
})
|
||||||
|
|
||||||
proxy.AddCommand(utils.IngameCommand{
|
proxy.AddCommand(utils.IngameCommand{
|
||||||
Exec: w.toggleVoid,
|
Exec: func(cmdline []string) bool {
|
||||||
|
return w.setVoidGen(!w.voidGen, false)
|
||||||
|
},
|
||||||
Cmd: protocol.Command{
|
Cmd: protocol.Command{
|
||||||
Name: "void",
|
Name: "void",
|
||||||
Description: "toggle if void generator should be used",
|
Description: locale.Loc("void_desc", nil),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
|
||||||
|
return true
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/bedrock-tool/bedrocktool/locale"
|
||||||
"github.com/sandertv/gophertunnel/minecraft/auth"
|
"github.com/sandertv/gophertunnel/minecraft/auth"
|
||||||
"github.com/sandertv/gophertunnel/minecraft/realms"
|
"github.com/sandertv/gophertunnel/minecraft/realms"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"golang.org/x/oauth2"
|
"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 {
|
func GetTokenSource() oauth2.TokenSource {
|
||||||
if G_token_src != nil {
|
if gTokenSrc != nil {
|
||||||
return G_token_src
|
return gTokenSrc
|
||||||
}
|
}
|
||||||
token := get_token()
|
token := getToken()
|
||||||
G_token_src = auth.RefreshTokenSource(&token)
|
gTokenSrc = auth.RefreshTokenSource(&token)
|
||||||
new_token, err := G_token_src.Token()
|
newToken, err := gTokenSrc.Token()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
if !token.Valid() {
|
if !token.Valid() {
|
||||||
logrus.Info("Refreshed token")
|
logrus.Info(locale.Loc("refreshed_token", nil))
|
||||||
write_token(new_token)
|
writeToken(newToken)
|
||||||
}
|
}
|
||||||
|
|
||||||
return G_token_src
|
return gTokenSrc
|
||||||
}
|
}
|
||||||
|
|
||||||
var G_realms_api *realms.Client
|
var RealmsEnv string
|
||||||
|
|
||||||
func GetRealmsApi() *realms.Client {
|
var gRealmsAPI *realms.Client
|
||||||
if G_realms_api == nil {
|
|
||||||
G_realms_api = realms.NewClient(GetTokenSource())
|
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)
|
buf, err := json.Marshal(token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
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
|
var token oauth2.Token
|
||||||
if _, err := os.Stat(TOKEN_FILE); err == nil {
|
if _, err := os.Stat(TokenFile); err == nil {
|
||||||
f, err := os.Open(TOKEN_FILE)
|
f, err := os.Open(TokenFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
@ -65,7 +72,7 @@ func get_token() oauth2.Token {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
write_token(_token)
|
writeToken(_token)
|
||||||
token = *_token
|
token = *_token
|
||||||
}
|
}
|
||||||
return 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
|
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) {
|
var ValidCMDs = make(map[string]Command, 0)
|
||||||
subcommands.Register(sub, "")
|
|
||||||
ValidCMDs[sub.Name()] = sub.Synopsis()
|
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"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
|
"github.com/bedrock-tool/bedrocktool/locale"
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
var override_dns = map[string]bool{
|
var overrideDNS = map[string]bool{
|
||||||
"geo.hivebedrock.network.": true,
|
"geo.hivebedrock.network.": true,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,15 +24,15 @@ func (d *DNSServer) answerQuery(remote net.Addr, req *dns.Msg) (reply *dns.Msg)
|
||||||
case dns.TypeA:
|
case dns.TypeA:
|
||||||
logrus.Infof("Query for %s", q.Name)
|
logrus.Infof("Query for %s", q.Name)
|
||||||
|
|
||||||
if override_dns[q.Name] {
|
if overrideDNS[q.Name] {
|
||||||
host, _, _ := net.SplitHostPort(remote.String())
|
host, _, _ := net.SplitHostPort(remote.String())
|
||||||
remote_ip := net.ParseIP(host)
|
remoteIP := net.ParseIP(host)
|
||||||
|
|
||||||
addrs, _ := net.InterfaceAddrs()
|
addrs, _ := net.InterfaceAddrs()
|
||||||
var ip string
|
var ip string
|
||||||
for _, addr := range addrs {
|
for _, addr := range addrs {
|
||||||
if ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
|
if ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
|
||||||
if ipnet.Contains(remote_ip) {
|
if ipnet.Contains(remoteIP) {
|
||||||
ip = ipnet.IP.String()
|
ip = ipnet.IP.String()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -83,17 +84,20 @@ func (d *DNSServer) handler(w dns.ResponseWriter, req *dns.Msg) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func InitDNS() {
|
func InitDNS() {
|
||||||
|
if !Options.EnableDNS {
|
||||||
|
return
|
||||||
|
}
|
||||||
d := DNSServer{}
|
d := DNSServer{}
|
||||||
dns.HandleFunc(".", d.handler)
|
dns.HandleFunc(".", d.handler)
|
||||||
|
|
||||||
server := &dns.Server{Addr: ":53", Net: "udp"}
|
server := &dns.Server{Addr: ":53", Net: "udp"}
|
||||||
go func() {
|
go func() {
|
||||||
logrus.Infof("Starting dns at %s:53\n", GetLocalIP())
|
logrus.Infof(locale.Loc("starting_dns", locale.Strmap{"Ip": GetLocalIP()}))
|
||||||
err := server.ListenAndServe()
|
err := server.ListenAndServe()
|
||||||
defer server.Shutdown()
|
defer server.Shutdown()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Warnf("Failed to start dns server: %s\n ", err.Error())
|
logrus.Warnf(locale.Loc("failed_to_start_dns", locale.Strmap{"Err": err.Error()}))
|
||||||
logrus.Info("you may have to use bedrockconnect")
|
logrus.Info(locale.Loc("suggest_bedrockconnect", nil))
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,8 @@ package utils
|
||||||
import (
|
import (
|
||||||
"image"
|
"image"
|
||||||
"image/color"
|
"image/color"
|
||||||
|
"image/png"
|
||||||
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
)
|
)
|
||||||
|
@ -14,6 +16,18 @@ func Img2rgba(img *image.RGBA) []color.RGBA {
|
||||||
return *(*[]color.RGBA)(unsafe.Pointer(&header))
|
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
|
// LERP is a linear interpolation function
|
||||||
func LERP(p1, p2, alpha float64) float64 {
|
func LERP(p1, p2, alpha float64) float64 {
|
||||||
return (1-alpha)*p1 + alpha*p2
|
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)
|
ret.A = blendAlphaValue(c1.A, c2.A)
|
||||||
return ret
|
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"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/bedrock-tool/bedrocktool/locale"
|
||||||
"github.com/sirupsen/logrus"
|
"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)
|
c := make(chan string)
|
||||||
go func() {
|
go func() {
|
||||||
fmt.Print(q)
|
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)
|
host, _, err := net.SplitHostPort(server)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Fatalf("Invalid server: %s", err)
|
logrus.Fatalf(locale.Loc("invalid_server", locale.Strmap{"Err": err.Error()}))
|
||||||
}
|
}
|
||||||
return host
|
return host
|
||||||
}
|
}
|
||||||
|
|
||||||
func ServerInput(ctx context.Context, server string) (address, name string, err error) {
|
var (
|
||||||
if server == "" { // no arg provided, interactive input
|
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
|
var cancelled bool
|
||||||
server, cancelled = User_input(ctx, "Enter Server: ")
|
server, cancelled = UserInput(ctx, locale.Loc("enter_server", nil))
|
||||||
if cancelled {
|
if cancelled {
|
||||||
return "", "", context.Canceled
|
return "", "", context.Canceled
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.HasPrefix(server, "realm:") { // for realms use api to get ip address
|
// realm
|
||||||
realm_info := strings.Split(server, ":")
|
if realmRegex.MatchString(server) {
|
||||||
id := ""
|
p := regexGetParams(realmRegex, server)
|
||||||
if len(realm_info) == 3 {
|
name, address, err := getRealm(ctx, p["Name"], p["ID"])
|
||||||
id = realm_info[2]
|
|
||||||
}
|
|
||||||
name, address, err = get_realm(context.Background(), GetRealmsApi(), realm_info[1], id)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return "", "", err
|
||||||
}
|
}
|
||||||
name = CleanupName(name)
|
return address, CleanupName(name), nil
|
||||||
} 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, 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 {
|
func IPPrivate(ip net.IP) bool {
|
||||||
for _, ipNet := range PrivateIPNetworks {
|
for _, ipNet := range PrivateIPNetworks {
|
||||||
if ipNet.Contains(ip) {
|
if ipNet.Contains(ip) {
|
||||||
|
|
|
@ -2,9 +2,15 @@ package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/bedrock-tool/bedrocktool/locale"
|
||||||
"github.com/fatih/color"
|
"github.com/fatih/color"
|
||||||
"github.com/sandertv/gophertunnel/minecraft/protocol"
|
"github.com/sandertv/gophertunnel/minecraft/protocol"
|
||||||
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
|
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
|
||||||
|
@ -12,7 +18,7 @@ import (
|
||||||
"golang.org/x/exp/slices"
|
"golang.org/x/exp/slices"
|
||||||
)
|
)
|
||||||
|
|
||||||
var Pool = packet.NewPool()
|
var pool = packet.NewPool()
|
||||||
|
|
||||||
var MutedPackets = []string{
|
var MutedPackets = []string{
|
||||||
"packet.UpdateBlock",
|
"packet.UpdateBlock",
|
||||||
|
@ -42,52 +48,175 @@ var MutedPackets = []string{
|
||||||
"packet.PlaySound",
|
"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) {
|
func PacketLogger(header packet.Header, payload []byte, src, dst net.Addr) {
|
||||||
var pk packet.Packet
|
var pk packet.Packet
|
||||||
if pkFunc, ok := Pool[header.PacketID]; ok {
|
if pkFunc, ok := pool[header.PacketID]; ok {
|
||||||
pk = pkFunc()
|
pk = pkFunc()
|
||||||
} else {
|
} else {
|
||||||
pk = &packet.Unknown{PacketID: header.PacketID}
|
pk = &packet.Unknown{PacketID: header.PacketID}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if pk.ID() == packet.IDRequestNetworkSettings {
|
||||||
|
ClientAddr = src
|
||||||
|
}
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
if recoveredErr := recover(); recoveredErr != nil {
|
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.Unmarshal(protocol.NewReader(bytes.NewBuffer(payload), 0))
|
||||||
|
|
||||||
pk_name := reflect.TypeOf(pk).String()[1:]
|
if FLog != nil {
|
||||||
if slices.Contains(MutedPackets, pk_name) {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
switch pk := pk.(type) {
|
switch pk := pk.(type) {
|
||||||
case *packet.Disconnect:
|
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")
|
dirS2C := color.GreenString("S") + "->" + color.CyanString("C")
|
||||||
dir_C2S := color.CyanString("C") + "->" + color.GreenString("S")
|
dirC2S := color.CyanString("C") + "->" + color.GreenString("S")
|
||||||
var dir string = dir_S2C
|
var dir string = dirS2C
|
||||||
|
|
||||||
if Client_addr != nil {
|
if ClientAddr != nil {
|
||||||
if src == Client_addr {
|
if src == ClientAddr {
|
||||||
dir = dir_C2S
|
dir = dirC2S
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
src_addr, _, _ := net.SplitHostPort(src.String())
|
srcAddr, _, _ := net.SplitHostPort(src.String())
|
||||||
if IPPrivate(net.ParseIP(src_addr)) {
|
if IPPrivate(net.ParseIP(srcAddr)) {
|
||||||
dir = dir_C2S
|
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)
|
logrus.Debugf("%+v", pk)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
323
utils/proxy.go
323
utils/proxy.go
|
@ -2,20 +2,26 @@ package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/bedrock-tool/bedrocktool/locale"
|
||||||
"github.com/sandertv/gophertunnel/minecraft"
|
"github.com/sandertv/gophertunnel/minecraft"
|
||||||
"github.com/sandertv/gophertunnel/minecraft/protocol"
|
"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/protocol/packet"
|
||||||
"github.com/sandertv/gophertunnel/minecraft/resource"
|
"github.com/sandertv/gophertunnel/minecraft/resource"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
var G_disconnect_reason = "Connection lost"
|
var DisconnectReason = "Connection lost"
|
||||||
|
|
||||||
type dummyProto struct {
|
type dummyProto struct {
|
||||||
id int32
|
id int32
|
||||||
|
@ -33,27 +39,127 @@ func (p dummyProto) ConvertFromLatest(pk packet.Packet, _ *minecraft.Conn) []pac
|
||||||
return []packet.Packet{pk}
|
return []packet.Packet{pk}
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProxyContext struct {
|
type (
|
||||||
Server *minecraft.Conn
|
PacketFunc func(header packet.Header, payload []byte, src, dst net.Addr)
|
||||||
Client *minecraft.Conn
|
PacketCallback func(pk packet.Packet, proxy *ProxyContext, toServer bool, timeReceived time.Time) (packet.Packet, error)
|
||||||
Listener *minecraft.Listener
|
ClientConnectCallback func(proxy *ProxyContext, hasClient bool)
|
||||||
commands map[string]IngameCommand
|
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
|
// called for every packet
|
||||||
PacketFunc PacketFunc
|
PacketFunc PacketFunc
|
||||||
|
// called after client connected
|
||||||
|
OnClientConnect ClientConnectCallback
|
||||||
// called after game started
|
// called after game started
|
||||||
ConnectCB ConnectCallback
|
ConnectCB ConnectCallback
|
||||||
// called on every packet after login
|
// called on every packet after login
|
||||||
PacketCB PacketCallback
|
PacketCB PacketCallback
|
||||||
}
|
}
|
||||||
|
|
||||||
type (
|
func NewProxy() (*ProxyContext, error) {
|
||||||
PacketFunc func(header packet.Header, payload []byte, src, dst net.Addr)
|
p := &ProxyContext{
|
||||||
PacketCallback func(pk packet.Packet, proxy *ProxyContext, toServer bool) (packet.Packet, error)
|
commands: make(map[string]IngameCommand),
|
||||||
ConnectCallback func(proxy *ProxyContext)
|
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) {
|
func (p *ProxyContext) SendMessage(text string) {
|
||||||
if p.Client != nil {
|
if p.Client != nil {
|
||||||
|
@ -73,16 +179,7 @@ func (p *ProxyContext) SendPopup(text string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type IngameCommand struct {
|
func (p *ProxyContext) CommandHandlerPacketCB(pk packet.Packet, proxy *ProxyContext, toServer bool, _ time.Time) (packet.Packet, error) {
|
||||||
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) {
|
|
||||||
switch _pk := pk.(type) {
|
switch _pk := pk.(type) {
|
||||||
case *packet.CommandRequest:
|
case *packet.CommandRequest:
|
||||||
cmd := strings.Split(_pk.CommandLine, " ")
|
cmd := strings.Split(_pk.CommandLine, " ")
|
||||||
|
@ -105,14 +202,14 @@ func (p *ProxyContext) CommandHandlerPacketCB(pk packet.Packet, proxy *ProxyCont
|
||||||
return pk, nil
|
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
|
var c1, c2 *minecraft.Conn
|
||||||
if toServer {
|
if toServer {
|
||||||
c1 = proxy.Client
|
c1 = p.Client
|
||||||
c2 = proxy.Server
|
c2 = p.Server
|
||||||
} else {
|
} else {
|
||||||
c1 = proxy.Server
|
c1 = p.Server
|
||||||
c2 = proxy.Client
|
c2 = p.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
|
@ -126,16 +223,16 @@ func proxyLoop(ctx context.Context, proxy *ProxyContext, toServer bool, packetCB
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, packetCB := range packetCBs {
|
for _, packetCB := range packetCBs {
|
||||||
pk, err = packetCB(pk, proxy, toServer)
|
pk, err = packetCB(pk, p, toServer, time.Now())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if pk != nil {
|
if pk != nil && c2 != nil {
|
||||||
if err := c2.WritePacket(pk); err != nil {
|
if err := c2.WritePacket(pk); err != nil {
|
||||||
if disconnect, ok := errors.Unwrap(err).(minecraft.DisconnectError); ok {
|
if disconnect, ok := errors.Unwrap(err).(minecraft.DisconnectError); ok {
|
||||||
G_disconnect_reason = disconnect.Error()
|
DisconnectReason = disconnect.Error()
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -143,87 +240,99 @@ func proxyLoop(ctx context.Context, proxy *ProxyContext, toServer bool, packetCB
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewProxy(log *logrus.Logger) *ProxyContext {
|
var ClientAddr net.Addr
|
||||||
if log == nil {
|
|
||||||
log = logrus.StandardLogger()
|
|
||||||
}
|
|
||||||
return &ProxyContext{
|
|
||||||
log: log,
|
|
||||||
commands: make(map[string]IngameCommand),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var Client_addr net.Addr
|
func (p *ProxyContext) Run(ctx context.Context, serverAddress string) (err error) {
|
||||||
|
if strings.HasPrefix(serverAddress, "PCAP!") {
|
||||||
func (p *ProxyContext) Run(ctx context.Context, server_address string) (err error) {
|
return createReplayConnection(ctx, serverAddress[5:], p.ConnectCB, p.PacketCB)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
GetTokenSource() // ask for login before listening
|
GetTokenSource() // ask for login before listening
|
||||||
var packs []*resource.Pack
|
|
||||||
if G_preload_packs {
|
var cdp *login.ClientData = nil
|
||||||
p.log.Info("Preloading resourcepacks")
|
if p.WithClient {
|
||||||
var serverConn *minecraft.Conn
|
var packs []*resource.Pack
|
||||||
serverConn, err = ConnectServer(ctx, server_address, nil, true, nil)
|
if Options.Preload {
|
||||||
if err != nil {
|
logrus.Info(locale.Loc("preloading_packs", nil))
|
||||||
err = fmt.Errorf("failed to connect to %s: %s", server_address, err)
|
var serverConn *minecraft.Conn
|
||||||
return
|
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()
|
_status := minecraft.NewStatusProvider("Server")
|
||||||
p.log.Infof("%d packs loaded", len(packs))
|
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")
|
if p.OnClientConnect != nil {
|
||||||
p.Listener, err = minecraft.ListenConfig{
|
p.OnClientConnect(p, p.WithClient)
|
||||||
StatusProvider: _status,
|
}
|
||||||
ResourcePacks: packs,
|
|
||||||
AcceptedProtocols: []minecraft.Protocol{
|
if p.CustomClientData != nil {
|
||||||
dummyProto{id: 544, ver: "1.19.20"},
|
cdp = p.CustomClientData
|
||||||
},
|
}
|
||||||
}.Listen("raknet", ":19132")
|
|
||||||
|
p.Server, err = connectServer(ctx, serverAddress, cdp, p.AlwaysGetPacks, p.PacketFunc)
|
||||||
if err != nil {
|
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.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 {
|
if p.ConnectCB != nil {
|
||||||
p.ConnectCB(p)
|
if !p.ConnectCB(p, nil) {
|
||||||
|
return errors.New("Cancelled")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
wg := sync.WaitGroup{}
|
wg := sync.WaitGroup{}
|
||||||
cbs := []PacketCallback{
|
|
||||||
p.CommandHandlerPacketCB,
|
var cbs []PacketCallback
|
||||||
}
|
cbs = append(cbs, p.CommandHandlerPacketCB)
|
||||||
if p.PacketCB != nil {
|
if p.PacketCB != nil {
|
||||||
cbs = append(cbs, p.PacketCB)
|
cbs = append(cbs, p.PacketCB)
|
||||||
}
|
}
|
||||||
|
@ -232,21 +341,23 @@ func (p *ProxyContext) Run(ctx context.Context, server_address string) (err erro
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func() {
|
go func() {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
if err := proxyLoop(ctx, p, false, cbs); err != nil {
|
if err := p.proxyLoop(ctx, false, cbs); err != nil {
|
||||||
p.log.Error(err)
|
logrus.Error(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// client to server
|
// client to server
|
||||||
wg.Add(1)
|
if p.Client != nil {
|
||||||
go func() {
|
wg.Add(1)
|
||||||
defer wg.Done()
|
go func() {
|
||||||
if err := proxyLoop(ctx, p, true, cbs); err != nil {
|
defer wg.Done()
|
||||||
p.log.Error(err)
|
if err := p.proxyLoop(ctx, true, cbs); err != nil {
|
||||||
return
|
logrus.Error(err)
|
||||||
}
|
return
|
||||||
}()
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -6,18 +6,16 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/google/subcommands"
|
"github.com/bedrock-tool/bedrocktool/locale"
|
||||||
"github.com/sandertv/gophertunnel/minecraft/realms"
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func get_realm(ctx context.Context, api *realms.Client, realm_name, id string) (name string, address string, err error) {
|
func getRealm(ctx context.Context, realmName, id string) (name string, address string, err error) {
|
||||||
realms, err := api.Realms(ctx)
|
realms, err := GetRealmsAPI().Realms(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return "", "", err
|
||||||
}
|
}
|
||||||
for _, realm := range realms {
|
for _, realm := range realms {
|
||||||
if strings.HasPrefix(realm.Name, realm_name) {
|
if strings.HasPrefix(realm.Name, realmName) {
|
||||||
if id != "" && id != fmt.Sprint(id) {
|
if id != "" && id != fmt.Sprint(id) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -34,25 +32,18 @@ func get_realm(ctx context.Context, api *realms.Client, realm_name, id string) (
|
||||||
|
|
||||||
type RealmListCMD struct{}
|
type RealmListCMD struct{}
|
||||||
|
|
||||||
func (*RealmListCMD) Name() string { return "list-realms" }
|
func (*RealmListCMD) Name() string { return "list-realms" }
|
||||||
func (*RealmListCMD) Synopsis() string { return "prints all realms you have access to" }
|
func (*RealmListCMD) Synopsis() string { return locale.Loc("list_realms_synopsis", nil) }
|
||||||
|
|
||||||
func (c *RealmListCMD) SetFlags(f *flag.FlagSet) {}
|
func (c *RealmListCMD) SetFlags(f *flag.FlagSet) {}
|
||||||
func (c *RealmListCMD) Usage() string {
|
func (c *RealmListCMD) Execute(ctx context.Context, ui UI) error {
|
||||||
return c.Name() + ": " + c.Synopsis() + "\n"
|
realms, err := GetRealmsAPI().Realms(ctx)
|
||||||
}
|
|
||||||
|
|
||||||
func (c *RealmListCMD) Execute(ctx context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
|
|
||||||
api := realms.NewClient(GetTokenSource())
|
|
||||||
realms, err := api.Realms(ctx)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Error(err)
|
return err
|
||||||
return 1
|
|
||||||
}
|
}
|
||||||
for _, realm := range realms {
|
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() {
|
func init() {
|
||||||
|
|
105
utils/replay.go
105
utils/replay.go
|
@ -1,89 +1,136 @@
|
||||||
package utils
|
package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"time"
|
||||||
|
|
||||||
"github.com/sandertv/gophertunnel/minecraft"
|
"github.com/sandertv/gophertunnel/minecraft"
|
||||||
|
"github.com/sandertv/gophertunnel/minecraft/protocol"
|
||||||
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
|
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
func create_replay_connection(ctx context.Context, log *logrus.Logger, filename string, onConnect ConnectCallback, packetCB PacketCallback) error {
|
type replayHeader struct {
|
||||||
log.Infof("Reading replay %s", filename)
|
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)
|
f, err := os.Open(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
var size int64
|
stat, err := f.Stat()
|
||||||
{
|
if err != nil {
|
||||||
stat, err := f.Stat()
|
return err
|
||||||
if err != nil {
|
}
|
||||||
|
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
|
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()
|
proxy.Server = minecraft.NewConn()
|
||||||
|
|
||||||
game_started := false
|
gameStarted := false
|
||||||
i := 0
|
i := 0
|
||||||
for {
|
for {
|
||||||
i += 1
|
i += 1
|
||||||
var magic uint32 = 0
|
var magic uint32 = 0
|
||||||
var packet_length uint32 = 0
|
var packetLength uint32 = 0
|
||||||
var toServer bool = false
|
var toServer bool = false
|
||||||
|
timeReceived := time.Now()
|
||||||
|
|
||||||
offset, _ := f.Seek(0, io.SeekCurrent)
|
offset, _ := f.Seek(0, io.SeekCurrent)
|
||||||
if offset == size {
|
if offset == totalSize {
|
||||||
log.Info("Reached End")
|
logrus.Info("Reached End")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
binary.Read(f, binary.LittleEndian, &magic)
|
binary.Read(f, binary.LittleEndian, &magic)
|
||||||
if magic != 0xAAAAAAAA {
|
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)
|
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)
|
n, err := f.Read(payload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(err)
|
logrus.Error(err)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if n != int(packet_length) {
|
if n != int(packetLength) {
|
||||||
log.Errorf("Truncated %d", i)
|
return fmt.Errorf("truncated %d", i)
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var magic2 uint32
|
var magic2 uint32
|
||||||
binary.Read(f, binary.LittleEndian, &magic2)
|
binary.Read(f, binary.LittleEndian, &magic2)
|
||||||
if magic2 != 0xBBBBBBBB {
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
pks, err := pk_data.Decode(proxy.Server)
|
pks, err := pkData.Decode(proxy.Server)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(err)
|
logrus.Error(err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, pk := range pks {
|
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 {
|
if packetCB != nil {
|
||||||
packetCB(pk, proxy, toServer)
|
packetCB(pk, proxy, toServer, timeReceived)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
switch pk := pk.(type) {
|
switch pk := pk.(type) {
|
||||||
|
@ -117,9 +164,9 @@ func create_replay_connection(ctx context.Context, log *logrus.Logger, filename
|
||||||
ChatRestrictionLevel: pk.ChatRestrictionLevel,
|
ChatRestrictionLevel: pk.ChatRestrictionLevel,
|
||||||
DisablePlayerInteractions: pk.DisablePlayerInteractions,
|
DisablePlayerInteractions: pk.DisablePlayerInteractions,
|
||||||
})
|
})
|
||||||
game_started = true
|
gameStarted = true
|
||||||
if onConnect != nil {
|
if onConnect != nil {
|
||||||
onConnect(proxy)
|
onConnect(proxy, nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Binary file not shown.
|
@ -1,14 +1,82 @@
|
||||||
package utils
|
package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
|
||||||
"github.com/sandertv/gophertunnel/minecraft"
|
"github.com/sandertv/gophertunnel/minecraft"
|
||||||
"github.com/sandertv/gophertunnel/minecraft/resource"
|
"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) {
|
func GetPacks(server *minecraft.Conn) (packs map[string]*resource.Pack, err error) {
|
||||||
packs = make(map[string]*resource.Pack)
|
packs = make(map[string]*resource.Pack)
|
||||||
for _, pack := range server.ResourcePacks() {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,50 @@
|
||||||
package utils
|
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 Version string
|
||||||
|
var CmdName = "bedrocktool"
|
||||||
|
|
||||||
const updateServer = "https://updates.yuv.pink/"
|
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{
|
var Updater = &selfupdate.Updater{
|
||||||
CurrentVersion: Version,
|
CurrentVersion: Version,
|
||||||
ApiURL: updateServer,
|
ApiURL: updateServer,
|
||||||
BinURL: updateServer,
|
BinURL: updateServer,
|
||||||
Dir: "update/",
|
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
|
package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"compress/gzip"
|
|
||||||
"context"
|
"context"
|
||||||
"encoding/base64"
|
"crypto/aes"
|
||||||
|
"crypto/sha256"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/bedrock-tool/bedrocktool/locale"
|
||||||
|
"github.com/bedrock-tool/bedrocktool/utils/crypt"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/sandertv/gophertunnel/minecraft"
|
"github.com/sandertv/gophertunnel/minecraft"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
|
@ -21,34 +26,21 @@ import (
|
||||||
|
|
||||||
"github.com/sandertv/gophertunnel/minecraft/protocol/login"
|
"github.com/sandertv/gophertunnel/minecraft/protocol/login"
|
||||||
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
|
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
|
||||||
|
"github.com/sandertv/gophertunnel/minecraft/resource"
|
||||||
)
|
)
|
||||||
|
|
||||||
const SERVER_ADDRESS_HELP = `accepted server address formats:
|
var Options struct {
|
||||||
123.234.123.234
|
Debug bool
|
||||||
123.234.123.234:19132
|
Preload bool
|
||||||
realm:<Realmname>
|
IsInteractive bool
|
||||||
realm:<Realmname>:<Id>
|
ExtraDebug bool
|
||||||
|
EnableDNS bool
|
||||||
`
|
PathCustomUserData string
|
||||||
|
|
||||||
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 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 {
|
func CleanupName(name string) string {
|
||||||
name = strings.Split(name, "\n")[0]
|
name = strings.Split(name, "\n")[0]
|
||||||
var _tmp struct {
|
var _tmp struct {
|
||||||
|
@ -58,71 +50,77 @@ func CleanupName(name string) string {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
name = _tmp.K
|
name = _tmp.K
|
||||||
}
|
}
|
||||||
name = string(name_regexp.ReplaceAll([]byte(name), []byte("")))
|
name = string(nameRegexp.ReplaceAll([]byte(name), []byte("")))
|
||||||
name = strings.TrimSpace(name)
|
name = strings.TrimSpace(name)
|
||||||
return name
|
return name
|
||||||
}
|
}
|
||||||
|
|
||||||
// connections
|
// connections
|
||||||
|
|
||||||
func ConnectServer(ctx context.Context, address string, ClientData *login.ClientData, want_packs bool, packetFunc PacketFunc) (serverConn *minecraft.Conn, err error) {
|
func connectServer(ctx context.Context, address string, ClientData *login.ClientData, wantPacks 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cd := login.ClientData{}
|
cd := login.ClientData{}
|
||||||
if ClientData != nil {
|
if ClientData != nil {
|
||||||
cd = *ClientData
|
cd = *ClientData
|
||||||
}
|
}
|
||||||
|
|
||||||
logrus.Infof("Connecting to %s\n", address)
|
logrus.Info(locale.Loc("connecting", locale.Strmap{"Address": address}))
|
||||||
serverConn, err = minecraft.Dialer{
|
serverConn, err = minecraft.Dialer{
|
||||||
TokenSource: GetTokenSource(),
|
TokenSource: GetTokenSource(),
|
||||||
ClientData: cd,
|
ClientData: cd,
|
||||||
PacketFunc: packet_func,
|
PacketFunc: func(header packet.Header, payload []byte, src, dst net.Addr) {
|
||||||
DownloadResourcePack: func(id uuid.UUID, version string) bool {
|
if Options.Debug {
|
||||||
return want_packs
|
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)
|
}.DialContext(ctx, "raknet", address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return serverConn, err
|
||||||
}
|
}
|
||||||
|
|
||||||
logrus.Debug("Connected.")
|
logrus.Debug(locale.Loc("connected", nil))
|
||||||
Client_addr = serverConn.LocalAddr()
|
ClientAddr = serverConn.LocalAddr()
|
||||||
return serverConn, nil
|
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)
|
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() {
|
go func() {
|
||||||
errs <- clientConn.StartGame(serverConn.GameData())
|
defer wg.Done()
|
||||||
}()
|
|
||||||
go func() {
|
|
||||||
errs <- serverConn.DoSpawn()
|
errs <- serverConn.DoSpawn()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// wait for both to finish
|
wg.Wait()
|
||||||
for i := 0; i < 2; i++ {
|
for i := 0; i < 2; i++ {
|
||||||
select {
|
select {
|
||||||
case err := <-errs:
|
case err := <-errs:
|
||||||
if err != nil {
|
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():
|
case <-ctx.Done():
|
||||||
return fmt.Errorf("connection cancelled")
|
return errors.New(locale.Loc("connection_cancelled", nil))
|
||||||
|
default:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// get longest line length
|
// get longest line length
|
||||||
func max_len(lines []string) int {
|
func maxLen(lines []string) int {
|
||||||
o := 0
|
o := 0
|
||||||
for _, line := range lines {
|
for _, line := range lines {
|
||||||
if o < len(line) {
|
if o < len(line) {
|
||||||
|
@ -132,10 +130,10 @@ func max_len(lines []string) int {
|
||||||
return o
|
return o
|
||||||
}
|
}
|
||||||
|
|
||||||
// make text centered
|
// MarginLines makes text centered
|
||||||
func MarginLines(lines []string) string {
|
func MarginLines(lines []string) string {
|
||||||
ret := ""
|
ret := ""
|
||||||
max := max_len(lines)
|
max := maxLen(lines)
|
||||||
for _, line := range lines {
|
for _, line := range lines {
|
||||||
if len(line) != max {
|
if len(line) != max {
|
||||||
ret += strings.Repeat(" ", max/2-len(line)/4)
|
ret += strings.Repeat(" ", max/2-len(line)/4)
|
||||||
|
@ -163,3 +161,82 @@ func Clamp(a, b int) int {
|
||||||
}
|
}
|
||||||
return a
|
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 (
|
import (
|
||||||
"archive/zip"
|
"archive/zip"
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"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)
|
zr, _ := zip.NewReader(r, size)
|
||||||
for _, src_file := range zr.File {
|
for _, srcFile := range zr.File {
|
||||||
out_path := path.Join(unpack_folder, src_file.Name)
|
srcName := strings.ReplaceAll(srcFile.Name, "\\", "/")
|
||||||
if src_file.Mode().IsDir() {
|
outPath := path.Join(unpackFolder, srcName)
|
||||||
os.Mkdir(out_path, 0o755)
|
if srcFile.Mode().IsDir() {
|
||||||
|
os.Mkdir(outPath, 0o755)
|
||||||
} else {
|
} else {
|
||||||
os.MkdirAll(path.Dir(out_path), 0o755)
|
os.MkdirAll(path.Dir(outPath), 0o755)
|
||||||
fr, _ := src_file.Open()
|
fr, _ := srcFile.Open()
|
||||||
f, _ := os.Create(path.Join(unpack_folder, src_file.Name))
|
f, _ := os.Create(path.Join(unpackFolder, srcName))
|
||||||
io.Copy(f, fr)
|
io.Copy(f, fr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,7 +31,7 @@ func UnpackZip(r io.ReaderAt, size int64, unpack_folder string) {
|
||||||
func ZipFolder(filename, folder string) error {
|
func ZipFolder(filename, folder string) error {
|
||||||
f, err := os.Create(filename)
|
f, err := os.Create(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
logrus.Fatal(err)
|
||||||
}
|
}
|
||||||
zw := zip.NewWriter(f)
|
zw := zip.NewWriter(f)
|
||||||
err = filepath.WalkDir(folder, func(path string, d fs.DirEntry, err error) error {
|
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)
|
zwf, _ := zw.Create(rel)
|
||||||
data, err := os.ReadFile(path)
|
data, err := os.ReadFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
logrus.Error(err)
|
||||||
}
|
}
|
||||||
zwf.Write(data)
|
zwf.Write(data)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue