Compare commits
146 Commits
d0ecf1c261
...
e13856a4ff
Author | SHA1 | Date |
---|---|---|
olebeck | e13856a4ff | |
olebeck | 2761cf094c | |
olebeck | 0eb3d09046 | |
olebeck | 4968181f0e | |
olebeck | bbf7cc1d75 | |
olebeck | b867221a66 | |
olebeck | 3cc13a7bbf | |
olebeck | ba7435c822 | |
olebeck | 5a550002d3 | |
olebeck | 63b94197ec | |
olebeck | 5149487c18 | |
olebeck | 87cd32e280 | |
olebeck | aa73c86d11 | |
olebeck | c20e017f0d | |
olebeck | 2c87715966 | |
olebeck | 7726c707c6 | |
olebeck | 2e920a7bf0 | |
olebeck | 796a846b8e | |
olebeck | 99f7b012bf | |
olebeck | 3b1aa74100 | |
olebeck | 3dadabf706 | |
olebeck | a5f77eca3c | |
olebeck | 2996a752fc | |
olebeck | 085244e656 | |
olebeck | 543e1335ee | |
olebeck | 9a677feeac | |
olebeck | 32bfdfa319 | |
olebeck | bf786b1010 | |
olebeck | e8dae60049 | |
olebeck | 17d86c9ddb | |
olebeck | 4e7cfeb3b4 | |
olebeck | f1cb7df05e | |
olebeck | 9b3a31879a | |
olebeck | 88589933ea | |
olebeck | 29417b600c | |
olebeck | eca888e3f2 | |
olebeck | 4c0dd665f1 | |
olebeck | b7ad339a64 | |
olebeck | 191f1ecd9a | |
olebeck | 1e990d7fcb | |
olebeck | d3a8219d99 | |
olebeck | 94e02d677c | |
olebeck | c5e7146f24 | |
olebeck | cdf7caea5b | |
olebeck | 47fd5cc2ec | |
olebeck | cef4a5dbfe | |
olebeck | 6376195732 | |
olebeck | 1b1bddc17f | |
olebeck | ae496e9238 | |
olebeck | d7b559a7a8 | |
olebeck | 75a3ad3674 | |
olebeck | b98b509907 | |
olebeck | ed910330e8 | |
olebeck | 5742a6b850 | |
olebeck | 21566df29f | |
olebeck | 68890eddd2 | |
olebeck | b21ed74bea | |
olebeck | 75956eabcb | |
olebeck | 8f88c7f576 | |
olebeck | 82de60369a | |
olebeck | 83a38c57cc | |
olebeck | 073407346e | |
olebeck | a2653cffad | |
olebeck | d10805f4eb | |
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
|
|
@ -8,7 +8,8 @@ assignees: ''
|
|||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
A clear and concise description of what the bug is,
|
||||
and what server it occurs on.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
|
@ -24,15 +25,20 @@ A clear and concise description of what you expected to happen.
|
|||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Desktop (please complete the following information):**
|
||||
- OS: [e.g. iOS]
|
||||
- Browser [e.g. chrome, safari]
|
||||
- Version [e.g. 22]
|
||||
- OS: [e.g. windows]
|
||||
- Version [e.g. 1.28.0-36]
|
||||
- Minecraft Version [e.g. 1.19.73]
|
||||
|
||||
**Smartphone (please complete the following information):**
|
||||
- Device: [e.g. iPhone6]
|
||||
- OS: [e.g. iOS8.1]
|
||||
- Browser [e.g. stock browser, safari]
|
||||
- Version [e.g. 22]
|
||||
- OS: [e.g. iOS12]
|
||||
- Version [e.g. 1.28.0-36]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
|
||||
|
||||
**attach packets.log.gpg (not always necessary)**
|
||||
this file can be helpful for debugging without having to connect to the server.
|
||||
it can be created by running with -extra-debug [e.g. bedrocktool.exe -extra-debug worlds -address play.mojang.com ]
|
||||
be sure to only attach the .gpg file which is not publicly readable.
|
|
@ -1,6 +1,8 @@
|
|||
name: ci-build
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '**'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
|
@ -13,51 +15,56 @@ jobs:
|
|||
fetch-depth: 0
|
||||
- run: |
|
||||
git fetch --force --tags
|
||||
|
||||
- uses: actions/setup-go@v3
|
||||
|
||||
- name: Setup Golang with cache
|
||||
uses: magnetikonline/action-golang-cache@v3
|
||||
with:
|
||||
go-version: '1.19'
|
||||
check-latest: true
|
||||
cache: true
|
||||
go-version-file: go.mod
|
||||
|
||||
- name: Install SSH Key
|
||||
if: ${{ env.SSH_PRIVATE_KEY != '' }}
|
||||
uses: shimataro/ssh-key-action@v2
|
||||
with:
|
||||
key: ${{ secrets.SSH_PRIVATE_KEY }}
|
||||
known_hosts: ${{ secrets.KNOWN_HOSTS }}
|
||||
env:
|
||||
SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
|
||||
|
||||
- uses: awalsh128/cache-apt-pkgs-action@latest
|
||||
if: ${{ env.REPO_KEY != '' }}
|
||||
with:
|
||||
packages: git-crypt xxd
|
||||
packages: git-crypt xxd gcc pkg-config libwayland-dev libx11-dev libx11-xcb-dev libxkbcommon-x11-dev libgles2-mesa-dev libegl1-mesa-dev libffi-dev libxcursor-dev libvulkan-dev
|
||||
version: 1.0
|
||||
env:
|
||||
REPO_KEY: ${{ secrets.REPO_KEY }}
|
||||
|
||||
- name: decrypt
|
||||
if: ${{ !env.ACT }}
|
||||
if: ${{ env.REPO_KEY != '' }}
|
||||
run: |
|
||||
echo ${REPO_KEY} | xxd -r -p > ../bedrock-repo-key.key
|
||||
git-crypt unlock ../bedrock-repo-key.key
|
||||
rm ../bedrock-repo-key.key
|
||||
env:
|
||||
REPO_KEY: ${{ secrets.REPO_KEY }}
|
||||
|
||||
- run: go install github.com/sanbornm/go-selfupdate/cmd/go-selfupdate
|
||||
- run: go get ./cmd/bedrocktool
|
||||
|
||||
- name: Install SSH Key
|
||||
uses: shimataro/ssh-key-action@v2
|
||||
with:
|
||||
key: ${{ secrets.SSH_PRIVATE_KEY }}
|
||||
known_hosts: ${{ secrets.KNOWN_HOSTS }}
|
||||
- name: dependencies
|
||||
run: |
|
||||
go get ./cmd/bedrocktool
|
||||
go install gioui.org/cmd/gogio@latest
|
||||
|
||||
- name: build
|
||||
id: build
|
||||
run: |
|
||||
make -j dists updates
|
||||
run: python build.py
|
||||
|
||||
- name: Deploy with rsync
|
||||
run: rsync -avz ./public/ olebeck@${{ secrets.SSH_HOST }}:/var/www/updates/bedrocktool/
|
||||
|
||||
- name: 'Get Previous tag'
|
||||
id: previoustag
|
||||
uses: "WyriHaximus/github-action-get-previous-tag@v1"
|
||||
if: ${{ env.SSH_HOST != '' }}
|
||||
run: rsync -avzO ./updates/ olebeck@${SSH_HOST}:/var/www/updates/
|
||||
env:
|
||||
SSH_HOST: ${{ secrets.SSH_HOST }}
|
||||
|
||||
- uses: "marvinpinto/action-automatic-releases@latest"
|
||||
with:
|
||||
repo_token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
automatic_release_tag: ${{ steps.previoustag.outputs.tag }}
|
||||
files: dist/*
|
||||
automatic_release_tag: ${{ steps.build.outputs.release_tag }}
|
||||
files: ./builds/*
|
||||
prerelease: false
|
||||
|
|
|
@ -8,6 +8,7 @@ token.json
|
|||
*.bmp
|
||||
*.bin
|
||||
*.log
|
||||
*.syso
|
||||
|
||||
/bedrocktool
|
||||
/bedrocktool-*
|
||||
|
@ -17,6 +18,18 @@ __debug_bin
|
|||
keys.db
|
||||
/skins/
|
||||
/worlds/
|
||||
/packs/
|
||||
/dist/
|
||||
/public/
|
||||
/builds/
|
||||
/builds/
|
||||
/updates/
|
||||
/fyne-cross/
|
||||
/tmp/
|
||||
/cmd/test
|
||||
|
||||
packets.log.gpg
|
||||
customdata.json
|
||||
/other-projects/
|
||||
__pycache__/entityflags.cpython-310.pyc
|
||||
gathering.py
|
||||
*.ipynb
|
||||
entityflags.py
|
||||
|
|
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,187 @@
|
|||
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}")
|
||||
print(flush=True)
|
||||
|
||||
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():
|
||||
shutil.rmtree("./tmp", True)
|
||||
shutil.rmtree("./builds", True)
|
||||
shutil.rmtree("./updates", True)
|
||||
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,66 @@ import (
|
|||
"os"
|
||||
"os/signal"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/bedrock-tool/bedrocktool/locale"
|
||||
"github.com/bedrock-tool/bedrocktool/utils"
|
||||
"gopkg.in/square/go-jose.v2/json"
|
||||
|
||||
_ "github.com/bedrock-tool/bedrocktool/subcommands"
|
||||
_ "github.com/bedrock-tool/bedrocktool/subcommands/skins"
|
||||
_ "github.com/bedrock-tool/bedrocktool/subcommands/world"
|
||||
_ "github.com/bedrock-tool/bedrocktool/ui"
|
||||
|
||||
"github.com/google/subcommands"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type CLI struct {
|
||||
utils.BaseUI
|
||||
}
|
||||
|
||||
func (c *CLI) Init() bool {
|
||||
utils.SetCurrentUI(c)
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *CLI) Start(ctx context.Context, cancel context.CancelFunc) error {
|
||||
flag.Parse()
|
||||
subcommands.Execute(ctx)
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
cancel()
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
/*
|
||||
cf, _ := os.Create("cpu.pprof")
|
||||
err := pprof.StartCPUProfile(cf)
|
||||
if err != nil {
|
||||
logrus.Error(err)
|
||||
}
|
||||
defer pprof.StopCPUProfile()
|
||||
*/
|
||||
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
logrus.Errorf("Fatal Error occurred.")
|
||||
logrus.Errorf(locale.Loc("fatal_error", nil))
|
||||
println("")
|
||||
println("--COPY FROM HERE--")
|
||||
logrus.Infof("Version: %s", utils.Version)
|
||||
logrus.Infof("Cmdline: %s", os.Args)
|
||||
logrus.Errorf("Error: %s", err)
|
||||
fmt.Println("stacktrace from panic: \n" + string(debug.Stack()))
|
||||
println("stacktrace from panic: \n" + string(debug.Stack()))
|
||||
println("--END COPY HERE--")
|
||||
println("")
|
||||
println("if you want to report this error, please open an issue at")
|
||||
println("https://github.com/bedrock-tool/bedrocktool/issues")
|
||||
println("And attach the error info, describe what you did to get this error.")
|
||||
println("Thanks!\n")
|
||||
if utils.G_interactive {
|
||||
println(locale.Loc("report_issue", nil))
|
||||
if utils.Options.ExtraDebug {
|
||||
println(locale.Loc("used_extra_debug_report", nil))
|
||||
}
|
||||
if utils.Options.IsInteractive {
|
||||
input := bufio.NewScanner(os.Stdin)
|
||||
input.Scan()
|
||||
}
|
||||
|
@ -47,57 +77,43 @@ func main() {
|
|||
|
||||
logrus.SetLevel(logrus.DebugLevel)
|
||||
if utils.Version != "" {
|
||||
logrus.Infof("bedrocktool version: %s", utils.Version)
|
||||
logrus.Infof(locale.Loc("bedrocktool_version", locale.Strmap{"Version": utils.Version}))
|
||||
}
|
||||
|
||||
newVersion, err := utils.Updater.UpdateAvailable()
|
||||
if err != nil {
|
||||
logrus.Error(err)
|
||||
}
|
||||
go func() {
|
||||
newVersion, err := utils.Updater.UpdateAvailable()
|
||||
if err != nil {
|
||||
logrus.Error(err)
|
||||
}
|
||||
|
||||
if newVersion != "" && utils.Version != "" {
|
||||
logrus.Infof("Update Available: %s", newVersion)
|
||||
}
|
||||
if newVersion != "" && utils.Version != "" {
|
||||
logrus.Infof(locale.Loc("update_available", locale.Strmap{"Version": newVersion}))
|
||||
}
|
||||
}()
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
flag.BoolVar(&utils.G_debug, "debug", false, "debug mode")
|
||||
flag.BoolVar(&utils.G_preload_packs, "preload", false, "preload resourcepacks for proxy")
|
||||
enable_dns := flag.Bool("dns", false, "enable dns server for consoles")
|
||||
flag.StringVar(&utils.RealmsEnv, "realms-env", "", "realms env")
|
||||
flag.BoolVar(&utils.Options.Debug, "debug", false, locale.Loc("debug_mode", nil))
|
||||
flag.BoolVar(&utils.Options.Preload, "preload", false, locale.Loc("preload_packs", nil))
|
||||
flag.BoolVar(&utils.Options.ExtraDebug, "extra-debug", false, locale.Loc("extra_debug", nil))
|
||||
flag.StringVar(&utils.Options.PathCustomUserData, "userdata", "", locale.Loc("custom_user_data", nil))
|
||||
flag.String("lang", "", "lang")
|
||||
flag.BoolVar(&utils.Options.Capture, "capture", false, "Capture Packet log")
|
||||
|
||||
subcommands.Register(subcommands.HelpCommand(), "")
|
||||
subcommands.ImportantFlag("debug")
|
||||
subcommands.ImportantFlag("dns")
|
||||
subcommands.ImportantFlag("capture")
|
||||
subcommands.ImportantFlag("preload")
|
||||
subcommands.HelpCommand()
|
||||
|
||||
{ // interactive input
|
||||
if len(os.Args) < 2 {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
default:
|
||||
fmt.Println("Available commands:")
|
||||
for name, desc := range utils.ValidCMDs {
|
||||
fmt.Printf("\t%s\t%s\n", name, desc)
|
||||
}
|
||||
fmt.Printf("Use '%s <command>' to run a command\n", os.Args[0])
|
||||
var ui utils.UI
|
||||
|
||||
cmd, cancelled := utils.User_input(ctx, "Input Command: ")
|
||||
if cancelled {
|
||||
return
|
||||
}
|
||||
_cmd := strings.Split(cmd, " ")
|
||||
os.Args = append(os.Args, _cmd...)
|
||||
utils.G_interactive = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
flag.Parse()
|
||||
|
||||
if *enable_dns {
|
||||
utils.InitDNS()
|
||||
if len(os.Args) < 2 {
|
||||
ui = utils.MakeGui()
|
||||
utils.Options.IsInteractive = true
|
||||
} else {
|
||||
ui = &CLI{}
|
||||
}
|
||||
|
||||
// exit cleanup
|
||||
|
@ -109,12 +125,14 @@ func main() {
|
|||
cancel()
|
||||
}()
|
||||
|
||||
subcommands.Execute(ctx)
|
||||
|
||||
if utils.G_interactive {
|
||||
logrus.Info("Press Enter to exit.")
|
||||
input := bufio.NewScanner(os.Stdin)
|
||||
input.Scan()
|
||||
if !ui.Init() {
|
||||
logrus.Error("Failed to init UI!")
|
||||
return
|
||||
}
|
||||
err := ui.Start(ctx, cancel)
|
||||
cancel()
|
||||
if err != nil {
|
||||
logrus.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -124,31 +142,52 @@ type TransCMD struct {
|
|||
|
||||
func (*TransCMD) Name() string { return "trans" }
|
||||
func (*TransCMD) Synopsis() string { return "" }
|
||||
|
||||
func (c *TransCMD) SetFlags(f *flag.FlagSet) {
|
||||
f.BoolVar(&c.auth, "auth", false, "if it should login to xbox")
|
||||
f.BoolVar(&c.auth, "auth", false, locale.Loc("should_login_xbox", nil))
|
||||
}
|
||||
|
||||
func (c *TransCMD) Usage() string {
|
||||
return c.Name() + ": " + c.Synopsis() + "\n"
|
||||
}
|
||||
|
||||
func (c *TransCMD) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
|
||||
func (c *TransCMD) Execute(_ context.Context, ui utils.UI) error {
|
||||
const (
|
||||
BLACK_FG = "\033[30m"
|
||||
BOLD = "\033[1m"
|
||||
BLUE = "\033[46m"
|
||||
PINK = "\033[45m"
|
||||
WHITE = "\033[47m"
|
||||
RESET = "\033[0m"
|
||||
BlackFg = "\033[30m"
|
||||
Bold = "\033[1m"
|
||||
Blue = "\033[46m"
|
||||
Pink = "\033[45m"
|
||||
White = "\033[47m"
|
||||
Reset = "\033[0m"
|
||||
)
|
||||
if c.auth {
|
||||
utils.GetTokenSource()
|
||||
}
|
||||
fmt.Println(BLACK_FG + BOLD + BLUE + " Trans " + PINK + " Rights " + WHITE + " Are " + PINK + " Human " + BLUE + " Rights " + RESET)
|
||||
return 0
|
||||
fmt.Println(BlackFg + Bold + Blue + " Trans " + Pink + " Rights " + White + " Are " + Pink + " Human " + Blue + " Rights " + Reset)
|
||||
return nil
|
||||
}
|
||||
|
||||
type CreateCustomDataCMD struct {
|
||||
path string
|
||||
}
|
||||
|
||||
func (*CreateCustomDataCMD) Name() string { return "create-customdata" }
|
||||
func (*CreateCustomDataCMD) Synopsis() string { return "" }
|
||||
|
||||
func (c *CreateCustomDataCMD) SetFlags(f *flag.FlagSet) {
|
||||
f.StringVar(&c.path, "path", "customdata.json", "where to save")
|
||||
}
|
||||
|
||||
func (c *CreateCustomDataCMD) Execute(_ context.Context, ui utils.UI) error {
|
||||
var data utils.CustomClientData
|
||||
fio, err := os.Create(c.path)
|
||||
if err == nil {
|
||||
defer fio.Close()
|
||||
var bdata []byte
|
||||
bdata, err = json.MarshalIndent(&data, "", "\t")
|
||||
fio.Write(bdata)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
utils.RegisterCommand(&TransCMD{})
|
||||
utils.RegisterCommand(&CreateCustomDataCMD{})
|
||||
}
|
||||
|
|
75
go.mod
75
go.mod
|
@ -1,54 +1,81 @@
|
|||
module github.com/bedrock-tool/bedrocktool
|
||||
|
||||
go 1.19
|
||||
go 1.20
|
||||
|
||||
//replace github.com/sandertv/gophertunnel => ./gophertunnel
|
||||
replace github.com/sandertv/gophertunnel => github.com/olebeck/gophertunnel v1.26.1
|
||||
replace github.com/sandertv/gophertunnel => github.com/olebeck/gophertunnel v1.29.0-1
|
||||
|
||||
//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.4-13
|
||||
|
||||
//replace gioui.org => ./gio
|
||||
replace gioui.org => github.com/olebeck/gio v0.0.0-20230427194143-c9c9d8bc704d
|
||||
|
||||
require (
|
||||
github.com/df-mc/dragonfly v0.8.5
|
||||
gioui.org v0.0.0-20230427133431-816bda7ac7bd
|
||||
gioui.org/x v0.0.0-20230426160849-752f112c7a59
|
||||
github.com/cloudfoundry-attic/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21
|
||||
github.com/df-mc/dragonfly v0.9.4
|
||||
github.com/df-mc/goleveldb v1.1.9
|
||||
github.com/fatih/color v1.13.0
|
||||
github.com/flytam/filenamify v1.1.1
|
||||
github.com/fatih/color v1.15.0
|
||||
github.com/flytam/filenamify v1.1.2
|
||||
github.com/go-gl/mathgl v1.0.0
|
||||
github.com/google/subcommands v1.2.0
|
||||
github.com/miekg/dns v1.1.50
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/jinzhu/copier v0.3.5
|
||||
github.com/miekg/dns v1.1.53
|
||||
github.com/nicksnyder/go-i18n/v2 v2.2.1
|
||||
github.com/repeale/fp-go v0.11.1
|
||||
github.com/sanbornm/go-selfupdate v0.0.0-20210106163404-c9b625feac49
|
||||
github.com/sandertv/gophertunnel v1.26.0
|
||||
github.com/sandertv/go-raknet v1.12.0
|
||||
github.com/sandertv/gophertunnel v1.29.0
|
||||
github.com/shirou/gopsutil/v3 v3.23.3
|
||||
github.com/sirupsen/logrus v1.9.0
|
||||
golang.design/x/lockfree v0.0.1
|
||||
golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9
|
||||
golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1
|
||||
golang.org/x/crypto v0.8.0
|
||||
golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53
|
||||
golang.org/x/oauth2 v0.7.0
|
||||
golang.org/x/text v0.9.0
|
||||
gopkg.in/square/go-jose.v2 v2.6.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
||||
require (
|
||||
gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2 // indirect
|
||||
gioui.org/shader v1.0.6 // indirect
|
||||
git.wow.st/gmp/jni v0.0.0-20210610011705-34026c7e22d0 // indirect
|
||||
github.com/brentp/intintmap v0.0.0-20190211203843-30dc0ade9af9 // indirect
|
||||
github.com/cespare/xxhash v1.1.0 // 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/worldupgrader v1.0.3 // indirect
|
||||
github.com/dlclark/regexp2 v1.9.0 // indirect
|
||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||
github.com/go-text/typesetting v0.0.0-20230413204129-b4f0492bf7ae // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/jinzhu/copier v0.3.5
|
||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
|
||||
github.com/klauspost/compress v1.15.11 // indirect
|
||||
github.com/klauspost/compress v1.15.15 // indirect
|
||||
github.com/kr/binarydist v0.1.0 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.16 // indirect
|
||||
github.com/mattn/go-isatty v0.0.17 // indirect
|
||||
github.com/muhammadmuzzammil1998/jsonc v1.0.0 // indirect
|
||||
github.com/sandertv/go-raknet v1.12.0 // indirect
|
||||
github.com/sirupsen/logrus v1.9.0
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||
github.com/rogpeppe/go-internal v1.9.0 // indirect
|
||||
github.com/shoenig/go-m1cpu v0.1.4 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.11 // indirect
|
||||
github.com/tklauser/numcpus v0.6.0 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
||||
go.uber.org/atomic v1.10.0 // indirect
|
||||
golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be // indirect
|
||||
golang.org/x/image v0.0.0-20220902085622-e7cb96979f69
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
|
||||
golang.org/x/net v0.0.0-20220930213112-107f3e3c3b0b // indirect
|
||||
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
golang.org/x/tools v0.1.12 // indirect
|
||||
golang.org/x/exp/shiny v0.0.0-20220827204233-334a2380cb91 // indirect
|
||||
golang.org/x/image v0.7.0 // indirect
|
||||
golang.org/x/mod v0.8.0 // indirect
|
||||
golang.org/x/net v0.9.0 // indirect
|
||||
golang.org/x/sys v0.7.0 // indirect
|
||||
golang.org/x/tools v0.6.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/protobuf v1.28.1 // indirect
|
||||
gopkg.in/inconshreveable/go-update.v0 v0.0.0-20150814200126-d8b0b1d421aa // indirect
|
||||
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
|
||||
)
|
||||
|
|
189
go.sum
189
go.sum
|
@ -1,7 +1,27 @@
|
|||
eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d h1:ARo7NCVvN2NdhLlJE9xAbKweuI9L6UgfTbYb0YwPacY=
|
||||
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-20230426160849-752f112c7a59 h1:IdvHx1hSmuL9xs/p3rppWJnH01RXcBhz5HvB7pj9LAg=
|
||||
gioui.org/x v0.0.0-20230426160849-752f112c7a59/go.mod h1:nMctdnZS2HKxfSXb+bCPnhw1n2LLsXoxtTarZjtIBuI=
|
||||
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/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
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/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/changkun/lockfree v0.0.1 h1:5WefVJLglY4IHRqOQmh6Ao6wkJYaJkarshKU8VUtId4=
|
||||
github.com/changkun/lockfree v0.0.1/go.mod h1:3bKiaXn/iNzIPlSvSOMSVbRQUQtAp8qUAyBUtzU11s4=
|
||||
github.com/cloudfoundry-attic/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21 h1:Yg2hDs4b13Evkpj42FU2idX2cVXVFqQSheXYKM86Qsk=
|
||||
github.com/cloudfoundry-attic/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21/go.mod h1:MgJyK38wkzZbiZSKeIeFankxxSA8gayko/nr5x5bgBA=
|
||||
github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21 h1:tuijfIjZyjZaHq9xDUh0tNitwXshJpbLkqMOJv4H3do=
|
||||
github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21/go.mod h1:po7NpZ/QiTKzBKyrsEAxwnTamCoh8uDk/egRpQ7siIc=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
|
@ -9,13 +29,22 @@ github.com/df-mc/atomic v1.10.0 h1:0ZuxBKwR/hxcFGorKiHIp+hY7hgY+XBTzhCYD2NqSEg=
|
|||
github.com/df-mc/atomic v1.10.0/go.mod h1:Gw9rf+rPIbydMjA329Jn4yjd/O2c/qusw3iNp4tFGSc=
|
||||
github.com/df-mc/goleveldb v1.1.9 h1:ihdosZyy5jkQKrxucTQmN90jq/2lUwQnJZjIYIC/9YU=
|
||||
github.com/df-mc/goleveldb v1.1.9/go.mod h1:+NHCup03Sci5q84APIA21z3iPZCuk6m6ABtg4nANCSk=
|
||||
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
|
||||
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
||||
github.com/flytam/filenamify v1.1.1 h1:508gP8QR6vtbv46S3oz2ob9l7JGFdDFfqqMeh/TwzTk=
|
||||
github.com/flytam/filenamify v1.1.1/go.mod h1:Dzf9kVycwcsBlr2ATg6uxjqiFgKGH+5SKFuhdeP5zu8=
|
||||
github.com/df-mc/worldupgrader v1.0.3 h1:3nbthy6vfSNQZdqHBR+E5Fh3mCeWmCwLtqrYDiPUG5I=
|
||||
github.com/df-mc/worldupgrader v1.0.3/go.mod h1:6ybkJ/BV9b0XkcPzcLmvgT9Nv/xgBXdDQTmRhu7b8zQ=
|
||||
github.com/dlclark/regexp2 v1.9.0 h1:pTK/l/3qYIKaRXuHnEnIf7Y5NxfRPfpb7dis6/gdlVI=
|
||||
github.com/dlclark/regexp2 v1.9.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
|
||||
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
|
||||
github.com/flytam/filenamify v1.1.2 h1:dGlfWU4zrhDlsmvob4IFcfgjG5vIjfo4UwLyec6Wx94=
|
||||
github.com/flytam/filenamify v1.1.2/go.mod h1:Dzf9kVycwcsBlr2ATg6uxjqiFgKGH+5SKFuhdeP5zu8=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/go-gl/mathgl v1.0.0 h1:t9DznWJlXxxjeeKLIdovCOVJQk/GzDEL7h/h+Ro2B68=
|
||||
github.com/go-gl/mathgl v1.0.0/go.mod h1:yhpkQzEiH9yPyxDUGzkmgScbaBVlhC06qodikEM0ZwQ=
|
||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-text/typesetting v0.0.0-20230413204129-b4f0492bf7ae h1:LCcaQgYrnS+sx9Tc3oGUvbRBRt+5oFnKWakaxeAvNVI=
|
||||
github.com/go-text/typesetting v0.0.0-20230413204129-b4f0492bf7ae/go.mod h1:KmrpWuSMFcO2yjmyhGpnBGQHSKAoEgMTSSzvLDzCuEA=
|
||||
github.com/go-text/typesetting-utils v0.0.0-20230412163830-89e4bcfa3ecc h1:9Kf84pnrmmjdRzZIkomfjowmGUhHs20jkrWYw/I6CYc=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
|
@ -25,7 +54,9 @@ github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEW
|
|||
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
||||
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/subcommands v1.2.0 h1:vWQspBTo2nEqTUFita5/KeEWlUL8kQObDFbub/EN9oE=
|
||||
github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
|
@ -37,35 +68,29 @@ github.com/jinzhu/copier v0.3.5/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S
|
|||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 h1:iQTw/8FWTuc7uiaSepXwyf3o52HaUYcV+Tu66S3F5GA=
|
||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
|
||||
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||
github.com/klauspost/compress v1.15.11 h1:Lcadnb3RKGin4FYM/orgq0qde+nc15E5Cbqg4B9Sx9c=
|
||||
github.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM=
|
||||
github.com/klauspost/compress v1.15.12 h1:YClS/PImqYbn+UILDnqxQCZ3RehC9N318SU3kElDUEM=
|
||||
github.com/klauspost/compress v1.15.12/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM=
|
||||
github.com/klauspost/compress v1.15.15 h1:EF27CXIuDsYJ6mmvtBRlEuB2UVOqHG1tAXgZ7yIO+lw=
|
||||
github.com/klauspost/compress v1.15.15/go.mod h1:ZcK2JAFqKOpnBlxcLsJzYfrS9X1akm9fHZNnD9+Vo/4=
|
||||
github.com/kr/binarydist v0.1.0 h1:6kAoLA9FMMnNGSehX0s1PdjbEaACznAv/W219j2uvyo=
|
||||
github.com/kr/binarydist v0.1.0/go.mod h1:DY7S//GCoz1BCd0B0EVrinCKAZN3pXe+MDaIZbXQVgM=
|
||||
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA=
|
||||
github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
|
||||
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
|
||||
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/miekg/dns v1.1.53 h1:ZBkuHr5dxHtB1caEOlZTLPo7D3L3TWckgUUs/RHfDxw=
|
||||
github.com/miekg/dns v1.1.53/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY=
|
||||
github.com/muhammadmuzzammil1998/jsonc v1.0.0 h1:8o5gBQn4ZA3NBA9DlTujCj2a4w0tqWrPVjDwhzkgTIs=
|
||||
github.com/muhammadmuzzammil1998/jsonc v1.0.0/go.mod h1:saF2fIVw4banK0H4+/EuqfFLpRnoy5S+ECwTOCcRcSU=
|
||||
github.com/olebeck/dragonfly v0.8.5-1 h1:+z1WO0W4jCc1jcPMGDz85AxLWz59haEakD0yAVEXeWQ=
|
||||
github.com/olebeck/dragonfly v0.8.5-1/go.mod h1:CHQ21j4LjSwHgUeMk06tcxEW6U+SI7avLCUeSvp9AXo=
|
||||
github.com/olebeck/dragonfly v0.8.10-1 h1:oMeCcyoKfb9xEDxxbKM6YyUPlK/R8wix/zzZ4UzkYYQ=
|
||||
github.com/olebeck/dragonfly v0.8.10-1/go.mod h1:ZjzPME6I1nc73voUgr2s5lpkoTxnWuR54V6c1KbULX0=
|
||||
github.com/olebeck/gophertunnel v1.24.11-1 h1:DKEqjK5nAcjlwejHAD/SHeolAM6y9EkyjJcMuoK7VVY=
|
||||
github.com/olebeck/gophertunnel v1.24.11-1/go.mod h1:dYFetA6r62huhc1EgR9p8VFAFtKOuGgVE/iXf5CzZ4o=
|
||||
github.com/olebeck/gophertunnel v1.24.13-1 h1:3Sj22hVFAGCnPMJAewoSeuC9N8ikzcWo0DvJNBnXvs0=
|
||||
github.com/olebeck/gophertunnel v1.24.13-1/go.mod h1:dYFetA6r62huhc1EgR9p8VFAFtKOuGgVE/iXf5CzZ4o=
|
||||
github.com/olebeck/gophertunnel v1.25.0-1 h1:9vh6MIUo7/3EJI5oULr5F9KutAoa9qnpgWa566fImaI=
|
||||
github.com/olebeck/gophertunnel v1.25.0-1/go.mod h1:dYFetA6r62huhc1EgR9p8VFAFtKOuGgVE/iXf5CzZ4o=
|
||||
github.com/olebeck/gophertunnel v1.26.1 h1:mkIthqpU5vSksh/mxUlhqFN/nsK9CcP9hAn1LjjqKnQ=
|
||||
github.com/olebeck/gophertunnel v1.26.1/go.mod h1:dYFetA6r62huhc1EgR9p8VFAFtKOuGgVE/iXf5CzZ4o=
|
||||
github.com/nicksnyder/go-i18n/v2 v2.2.1 h1:aOzRCdwsJuoExfZhoiXHy4bjruwCMdt5otbYojM/PaA=
|
||||
github.com/nicksnyder/go-i18n/v2 v2.2.1/go.mod h1:fF2++lPHlo+/kPaj3nB0uxtPwzlPm+BlgwGX7MkeGj0=
|
||||
github.com/olebeck/dragonfly v0.9.4-13 h1:JF72hfG3/BBCXU1GSBaEKKXQy/gt+0mEOua3RdKXdJ8=
|
||||
github.com/olebeck/dragonfly v0.9.4-13/go.mod h1:ZNcbAATEeTNyN3Cumtwzox7STtFve469HHzL5c1K3nY=
|
||||
github.com/olebeck/gio v0.0.0-20230427194143-c9c9d8bc704d h1:D+Ryca52xv37/p0FsEWfGwAGUZ1vPWpvimA2eMfBijc=
|
||||
github.com/olebeck/gio v0.0.0-20230427194143-c9c9d8bc704d/go.mod h1:8CFQM/4LurRd9G3NUYdacFb9j2pK0LrAyVO2mAZo4mw=
|
||||
github.com/olebeck/gophertunnel v1.29.0-1 h1:3x2cZoe8O54xVFgEZqTBJpFEXlzbjlLFoo/d9cWGv+g=
|
||||
github.com/olebeck/gophertunnel v1.29.0-1/go.mod h1:HxQfl/8mZzvjzhekEH8RO6xLAgan9i/wIyrQzw0tIPY=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
|
||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
|
@ -73,110 +98,114 @@ github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU=
|
|||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
github.com/repeale/fp-go v0.11.1 h1:Q/e+gNyyHaxKAyfdbBqvip3DxhVWH453R+kthvSr9Mk=
|
||||
github.com/repeale/fp-go v0.11.1/go.mod h1:4KrwQJB1VRY+06CA+jTc4baZetr6o2PeuqnKr5ybQUc=
|
||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/sanbornm/go-selfupdate v0.0.0-20210106163404-c9b625feac49 h1:LuxslTBxJrrNeKfqoywIERWWhH43TgiVAiPEVlhgNBA=
|
||||
github.com/sanbornm/go-selfupdate v0.0.0-20210106163404-c9b625feac49/go.mod h1:fY313ZGG810aWruFYcyq3coFpHDrWJVoMfSRI81y1r4=
|
||||
github.com/sandertv/go-raknet v1.12.0 h1:olUzZlIJyX/pgj/mrsLCZYjKLNDsYiWdvQ4NIm3z0DA=
|
||||
github.com/sandertv/go-raknet v1.12.0/go.mod h1:Gx+WgZBMQ0V2UoouGoJ8Wj6CDrMBQ4SB2F/ggpl5/+Y=
|
||||
github.com/shirou/gopsutil/v3 v3.23.3 h1:Syt5vVZXUDXPEXpIBt5ziWsJ4LdSAAxF4l/xZeQgSEE=
|
||||
github.com/shirou/gopsutil/v3 v3.23.3/go.mod h1:lSBNN6t3+D6W5e5nXTxc8KIMMVxAcS+6IJlffjRRlMU=
|
||||
github.com/shoenig/go-m1cpu v0.1.4 h1:SZPIgRM2sEF9NJy50mRHu9PKGwxyyTTJIWvCtgVbozs=
|
||||
github.com/shoenig/go-m1cpu v0.1.4/go.mod h1:Wwvst4LR89UxjeFtLRMrpgRiyY4xPsejnVZym39dbAQ=
|
||||
github.com/shoenig/test v0.6.3 h1:GVXWJFk9PiOjN0KoJ7VrJGH6uLPnqxR7/fe3HUPfE0c=
|
||||
github.com/shoenig/test v0.6.3/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
|
||||
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
|
||||
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
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/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
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/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
|
||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM=
|
||||
github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI=
|
||||
github.com/tklauser/numcpus v0.6.0 h1:kebhY2Qt+3U6RNK7UqpYNA+tJ23IBEGKkB7JQBfDYms=
|
||||
github.com/tklauser/numcpus v0.6.0/go.mod h1:FEZLMke0lhOUG6w2JadTzp0a+Nl8PF/GFkQ5UVIcaL4=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg=
|
||||
github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
|
||||
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||
golang.design/x/lockfree v0.0.1 h1:IHFNwZgM5bnZYWkEbzn5lWHMYr8WsRBdCJ/RBVY0xMM=
|
||||
golang.design/x/lockfree v0.0.1/go.mod h1:iaZUx6UgZaOdePjzI6wFd+seYMl1i0rsG8+xKvA8c4I=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be h1:fmw3UbQh+nxngCAHrDCCztao/kbYFnWjoqop8dHx05A=
|
||||
golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.3.0 h1:a06MkbcxBrEFc0w0QIZWXrH/9cCX6KJyWbBOIwAn+7A=
|
||||
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
||||
golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 h1:RjggHMcaTVp0LOVZcW0bo8alwHrOaCrGUDgfWUHhnN4=
|
||||
golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
|
||||
golang.org/x/exp v0.0.0-20221126150942-6ab00d035af9 h1:yZNXmy+j/JpX19vZkVktWqAo7Gny4PBWYYK3zskGpx4=
|
||||
golang.org/x/exp v0.0.0-20221126150942-6ab00d035af9/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
|
||||
golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ=
|
||||
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
|
||||
golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53 h1:5llv2sWeaMSnA3w2kS57ouQQ4pudlXrR0dCgw51QK9o=
|
||||
golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
|
||||
golang.org/x/exp/shiny v0.0.0-20220827204233-334a2380cb91 h1:ryT6Nf0R83ZgD8WnFFdfI8wCeyqgdXWN4+CkFVNPAT0=
|
||||
golang.org/x/exp/shiny v0.0.0-20220827204233-334a2380cb91/go.mod h1:VjAR7z0ngyATZTELrBSkxOOHhhlnVUxDye4mcjx5h/8=
|
||||
golang.org/x/image v0.0.0-20190321063152-3fc05d484e9f/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20220902085622-e7cb96979f69 h1:Lj6HJGCSn5AjxRAH2+r35Mir4icalbqku+CLUtjnvXY=
|
||||
golang.org/x/image v0.0.0-20220902085622-e7cb96979f69/go.mod h1:doUCurBvlfPMKfmIpRIywoHmhN3VyhnoFDbvIEWF4hY=
|
||||
golang.org/x/image v0.1.0 h1:r8Oj8ZA2Xy12/b5KZYj3tuv7NG/fBz3TwQVvpJ9l8Rk=
|
||||
golang.org/x/image v0.1.0/go.mod h1:iyPr49SD/G/TBxYVB/9RRtGUT5eNbo2u4NamWeQcD5c=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
|
||||
golang.org/x/image v0.7.0 h1:gzS29xtG1J5ybQlv0PuyfE3nmc6R4qB73m6LUUmvFuw=
|
||||
golang.org/x/image v0.7.0/go.mod h1:nd/q4ef1AKKYl/4kft7g+6UyGbdiqWqTP1ZAbRoV7Rg=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA=
|
||||
golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.0.0-20220930213112-107f3e3c3b0b h1:uKO3Js8lXGjpjdc4J3rqs0/Ex5yDKUGfk43tTYWVLas=
|
||||
golang.org/x/net v0.0.0-20220930213112-107f3e3c3b0b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
golang.org/x/net v0.2.0 h1:sZfSu1wtKLGlWI4ZZayP0ck9Y73K1ynO6gqzTdBVdPU=
|
||||
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
||||
golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1 h1:lxqLZaMad/dJHMFZH0NiNpiEZI/nhgWhe4wgzpE+MuA=
|
||||
golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=
|
||||
golang.org/x/oauth2 v0.2.0 h1:GtQkldQ9m7yvzCL1V+LrYow3Khe0eJH0w7RbX/VbaIU=
|
||||
golang.org/x/oauth2 v0.2.0/go.mod h1:Cwn6afJ8jrQwYMxQDTpISoXmXW9I6qF6vDeuuoX3Ibs=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
|
||||
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||
golang.org/x/oauth2 v0.7.0 h1:qe6s0zUXlPX80/dITx3440hWZ7GwMwgDDyrSGTPJG/g=
|
||||
golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec h1:BkDtF2Ih9xZ7le9ndzTA7KJow28VbQW3odyk/8drmuI=
|
||||
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
|
||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.3.0 h1:SrNbZl6ECOS1qFzgTdQfWXZM9XBkiA6tkFrH9YSTPHM=
|
||||
golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k=
|
||||
golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
|
||||
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
|
||||
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
|
@ -188,5 +217,7 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkep
|
|||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
package handlers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/bedrock-tool/bedrocktool/utils"
|
||||
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
|
||||
)
|
||||
|
||||
func (p *packetCapturer) dumpPacket(toServer bool, payload []byte) {
|
||||
p.dumpLock.Lock()
|
||||
defer p.dumpLock.Unlock()
|
||||
p.fio.Write([]byte{0xAA, 0xAA, 0xAA, 0xAA})
|
||||
packetSize := uint32(len(payload))
|
||||
binary.Write(p.fio, binary.LittleEndian, packetSize)
|
||||
binary.Write(p.fio, binary.LittleEndian, toServer)
|
||||
binary.Write(p.fio, binary.LittleEndian, time.Now().UnixMilli())
|
||||
p.fio.Write(payload)
|
||||
p.fio.Write([]byte{0xBB, 0xBB, 0xBB, 0xBB})
|
||||
}
|
||||
|
||||
type packetCapturer struct {
|
||||
proxy *utils.ProxyContext
|
||||
fio *os.File
|
||||
dumpLock sync.Mutex
|
||||
}
|
||||
|
||||
func (p *packetCapturer) AddressAndName(address, hostname string) error {
|
||||
os.Mkdir("captures", 0o775)
|
||||
fio, err := os.Create(fmt.Sprintf("captures/%s-%s.pcap2", hostname, time.Now().Format("2006-01-02_15-04-05")))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
utils.WriteReplayHeader(fio)
|
||||
p.fio = fio
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *packetCapturer) PacketFunc(header packet.Header, payload []byte, src, dst net.Addr) {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
header.Write(buf)
|
||||
buf.Write(payload)
|
||||
p.dumpPacket(p.proxy.IsClient(src), buf.Bytes())
|
||||
}
|
||||
|
||||
func NewPacketCapturer() *utils.ProxyHandler {
|
||||
p := &packetCapturer{}
|
||||
return &utils.ProxyHandler{
|
||||
Name: "Packet Capturer",
|
||||
ProxyRef: func(pc *utils.ProxyContext) {
|
||||
p.proxy = pc
|
||||
},
|
||||
AddressAndName: p.AddressAndName,
|
||||
PacketFunc: p.PacketFunc,
|
||||
OnEnd: func() {
|
||||
p.dumpLock.Lock()
|
||||
defer p.dumpLock.Unlock()
|
||||
p.fio.Close()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
utils.NewPacketCapturer = NewPacketCapturer
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
package handlers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/bedrock-tool/bedrocktool/utils"
|
||||
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type chatLogger struct {
|
||||
Verbose bool
|
||||
fio *os.File
|
||||
}
|
||||
|
||||
func (c *chatLogger) PacketCB(pk packet.Packet, toServer bool, t time.Time) (packet.Packet, error) {
|
||||
if text, ok := pk.(*packet.Text); ok {
|
||||
logLine := text.Message
|
||||
if c.Verbose {
|
||||
logLine += fmt.Sprintf(" (TextType: %d | XUID: %s | PlatformChatID: %s)", text.TextType, text.XUID, text.PlatformChatID)
|
||||
}
|
||||
c.fio.WriteString(fmt.Sprintf("[%s] ", t.Format(time.RFC3339)))
|
||||
logrus.Info(logLine)
|
||||
if toServer {
|
||||
c.fio.WriteString("SENT: ")
|
||||
}
|
||||
c.fio.WriteString(logLine + "\n")
|
||||
}
|
||||
return pk, nil
|
||||
}
|
||||
|
||||
func NewChatLogger() *utils.ProxyHandler {
|
||||
c := &chatLogger{}
|
||||
return &utils.ProxyHandler{
|
||||
Name: "Packet Capturer",
|
||||
PacketCB: c.PacketCB,
|
||||
AddressAndName: func(address, hostname string) error {
|
||||
filename := fmt.Sprintf("%s_%s_chat.log", hostname, time.Now().Format("2006-01-02_15-04-05_Z07"))
|
||||
f, err := os.Create(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.fio = f
|
||||
return nil
|
||||
},
|
||||
OnEnd: func() {
|
||||
c.fio.Close()
|
||||
},
|
||||
}
|
||||
}
|
|
@ -0,0 +1,122 @@
|
|||
package handlers
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"reflect"
|
||||
"sync"
|
||||
|
||||
"github.com/bedrock-tool/bedrocktool/utils"
|
||||
"github.com/bedrock-tool/bedrocktool/utils/crypt"
|
||||
"github.com/fatih/color"
|
||||
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
var MutedPackets = []string{
|
||||
"packet.UpdateBlock",
|
||||
"packet.MoveActorAbsolute",
|
||||
"packet.SetActorMotion",
|
||||
"packet.SetTime",
|
||||
"packet.RemoveActor",
|
||||
"packet.AddActor",
|
||||
"packet.UpdateAttributes",
|
||||
"packet.Interact",
|
||||
"packet.LevelEvent",
|
||||
"packet.SetActorData",
|
||||
"packet.MoveActorDelta",
|
||||
"packet.MovePlayer",
|
||||
"packet.BlockActorData",
|
||||
"packet.PlayerAuthInput",
|
||||
"packet.LevelChunk",
|
||||
"packet.LevelSoundEvent",
|
||||
"packet.ActorEvent",
|
||||
"packet.NetworkChunkPublisherUpdate",
|
||||
"packet.UpdateSubChunkBlocks",
|
||||
"packet.SubChunk",
|
||||
"packet.SubChunkRequest",
|
||||
"packet.Animate",
|
||||
"packet.NetworkStackLatency",
|
||||
"packet.InventoryTransaction",
|
||||
"packet.PlaySound",
|
||||
}
|
||||
|
||||
var dirS2C = color.GreenString("S") + "->" + color.CyanString("C")
|
||||
var dirC2S = color.CyanString("C") + "->" + color.GreenString("S")
|
||||
var pool = packet.NewPool()
|
||||
|
||||
func NewDebugLogger(extraVerbose bool) *utils.ProxyHandler {
|
||||
var logPlain, logCrypt, logCryptEnc io.WriteCloser
|
||||
var packetsLogF *bufio.Writer
|
||||
var dmpLock sync.Mutex
|
||||
|
||||
if extraVerbose {
|
||||
// open plain text log
|
||||
logPlain, err := os.Create("packets.log")
|
||||
if err != nil {
|
||||
logrus.Error(err)
|
||||
}
|
||||
// open gpg log
|
||||
logCryptEnc, err = crypt.Encer("packets.log.gpg")
|
||||
if err != nil {
|
||||
logrus.Error(err)
|
||||
}
|
||||
if logPlain != nil || logCryptEnc != nil {
|
||||
packetsLogF = bufio.NewWriter(io.MultiWriter(logPlain, logCryptEnc))
|
||||
}
|
||||
}
|
||||
|
||||
var proxy *utils.ProxyContext
|
||||
|
||||
return &utils.ProxyHandler{
|
||||
Name: "Debug",
|
||||
ProxyRef: func(pc *utils.ProxyContext) {
|
||||
proxy = pc
|
||||
},
|
||||
PacketFunc: func(header packet.Header, payload []byte, src, dst net.Addr) {
|
||||
pk := utils.DecodePacket(header, payload)
|
||||
if pk == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if packetsLogF != nil {
|
||||
dmpLock.Lock()
|
||||
packetsLogF.Write([]byte(utils.DumpStruct(0, pk, true, false) + "\n\n\n"))
|
||||
dmpLock.Unlock()
|
||||
}
|
||||
|
||||
pkName := reflect.TypeOf(pk).String()[1:]
|
||||
if !slices.Contains(MutedPackets, pkName) {
|
||||
var dir string = dirS2C
|
||||
if proxy.IsClient(src) {
|
||||
dir = dirC2S
|
||||
}
|
||||
logrus.Debugf("%s 0x%02x, %s", dir, pk.ID(), pkName)
|
||||
}
|
||||
},
|
||||
OnEnd: func() {
|
||||
dmpLock.Lock()
|
||||
if packetsLogF != nil {
|
||||
packetsLogF.Flush()
|
||||
}
|
||||
if logPlain != nil {
|
||||
logPlain.Close()
|
||||
}
|
||||
if logCryptEnc != nil {
|
||||
logCryptEnc.Close()
|
||||
}
|
||||
if logCrypt != nil {
|
||||
logCrypt.Close()
|
||||
}
|
||||
dmpLock.Unlock()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
// hacky solution to allow proxy to add this
|
||||
utils.NewDebugLogger = NewDebugLogger
|
||||
}
|
|
@ -0,0 +1,123 @@
|
|||
package seconduser
|
||||
|
||||
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 (s *secondaryUser) ResetWorld() {
|
||||
s.chunks = make(map[world.ChunkPos]*chunk.Chunk)
|
||||
s.blockNBTs = make(map[protocol.BlockPos][]map[string]any)
|
||||
}
|
||||
|
||||
func (s *secondaryUser) processChangeDimension(pk *packet.ChangeDimension) {
|
||||
s.ResetWorld()
|
||||
dimensionID := pk.Dimension
|
||||
if s.ispre118 && dimensionID == 0 {
|
||||
dimensionID += 10
|
||||
}
|
||||
s.dimension, _ = world.DimensionByID(int(dimensionID))
|
||||
}
|
||||
|
||||
func (s *secondaryUser) processLevelChunk(pk *packet.LevelChunk) {
|
||||
// ignore empty chunks THANKS WEIRD SERVER SOFTWARE DEVS
|
||||
if len(pk.RawPayload) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
var subChunkCount int
|
||||
switch pk.SubChunkCount {
|
||||
case protocol.SubChunkRequestModeLimited:
|
||||
fallthrough
|
||||
case protocol.SubChunkRequestModeLimitless:
|
||||
subChunkCount = 0
|
||||
default:
|
||||
subChunkCount = int(pk.SubChunkCount)
|
||||
}
|
||||
|
||||
ch, blockNBTs, err := chunk.NetworkDecode(world.AirRID(), pk.RawPayload, subChunkCount, s.dimension.Range(), s.ispre118, s.hasCustomBlocks)
|
||||
if err != nil {
|
||||
logrus.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, blockNBT := range blockNBTs {
|
||||
x := blockNBT["x"].(int32)
|
||||
y := blockNBT["y"].(int32)
|
||||
z := blockNBT["z"].(int32)
|
||||
s.blockNBTs[protocol.BlockPos{x, y, z}] = blockNBTs
|
||||
}
|
||||
|
||||
s.chunks[world.ChunkPos(pk.Position)] = ch
|
||||
|
||||
for _, p := range s.server.Players() {
|
||||
p.Session().ViewChunk(world.ChunkPos(pk.Position), ch, nil)
|
||||
}
|
||||
|
||||
max := s.dimension.Range().Height() / 16
|
||||
switch pk.SubChunkCount {
|
||||
case protocol.SubChunkRequestModeLimited:
|
||||
max = int(pk.HighestSubChunk)
|
||||
fallthrough
|
||||
case protocol.SubChunkRequestModeLimitless:
|
||||
var offsetTable []protocol.SubChunkOffset
|
||||
r := s.dimension.Range()
|
||||
for y := int8(r.Min() / 16); y < int8(r.Max()); y++ {
|
||||
offsetTable = append(offsetTable, protocol.SubChunkOffset{0, y, 0})
|
||||
}
|
||||
|
||||
dimId, _ := world.DimensionID(s.dimension)
|
||||
s.proxy.Server.WritePacket(&packet.SubChunkRequest{
|
||||
Dimension: int32(dimId),
|
||||
Position: protocol.SubChunkPos{
|
||||
pk.Position.X(), 0, pk.Position.Z(),
|
||||
},
|
||||
Offsets: offsetTable[:max],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (s *secondaryUser) processSubChunk(pk *packet.SubChunk) {
|
||||
offsets := make(map[world.ChunkPos]bool, len(pk.SubChunkEntries))
|
||||
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])
|
||||
pos = world.ChunkPos{absX, absZ}
|
||||
)
|
||||
offsets[pos] = true
|
||||
ch, ok := s.chunks[pos]
|
||||
if !ok {
|
||||
logrus.Error(locale.Loc("subchunk_before_chunk", nil))
|
||||
continue
|
||||
}
|
||||
blockNBTs, err := ch.ApplySubChunkEntry(uint8(absY), &sub)
|
||||
if err != nil {
|
||||
logrus.Error(err)
|
||||
}
|
||||
for _, blockNBT := range blockNBTs {
|
||||
x := blockNBT["x"].(int32)
|
||||
y := blockNBT["y"].(int32)
|
||||
z := blockNBT["z"].(int32)
|
||||
s.blockNBTs[protocol.BlockPos{x, y, z}] = blockNBTs
|
||||
}
|
||||
|
||||
chunk.LightArea([]*chunk.Chunk{ch}, 0, 0).Fill()
|
||||
}
|
||||
|
||||
for _, p := range s.server.Players() {
|
||||
for pos := range offsets {
|
||||
ch, ok := s.chunks[pos]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
p.Session().ViewChunk(pos, ch, nil)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
package seconduser
|
||||
|
||||
import (
|
||||
"github.com/df-mc/dragonfly/server/block/cube"
|
||||
"github.com/df-mc/dragonfly/server/world"
|
||||
"github.com/go-gl/mathgl/mgl64"
|
||||
)
|
||||
|
||||
type entityType struct {
|
||||
name string
|
||||
}
|
||||
|
||||
func (t *entityType) EncodeEntity() string {
|
||||
return t.name
|
||||
}
|
||||
|
||||
func (t *entityType) BBox(e world.Entity) cube.BBox {
|
||||
return cube.BBox{}
|
||||
}
|
||||
|
||||
type serverEntity struct {
|
||||
t world.EntityType
|
||||
pos mgl64.Vec3
|
||||
rot cube.Rotation
|
||||
}
|
||||
|
||||
func (s *serverEntity) Close() error {
|
||||
return nil
|
||||
}
|
||||
func (s *serverEntity) Position() mgl64.Vec3 {
|
||||
return s.pos
|
||||
}
|
||||
func (s *serverEntity) Rotation() cube.Rotation {
|
||||
return s.rot
|
||||
}
|
||||
func (e *serverEntity) World() *world.World {
|
||||
w, _ := world.OfEntity(e)
|
||||
return w
|
||||
}
|
||||
func (s *serverEntity) Type() world.EntityType {
|
||||
return s.t
|
||||
}
|
||||
|
||||
func newServerEntity(typename string) *serverEntity {
|
||||
return &serverEntity{
|
||||
t: &entityType{name: typename},
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
package seconduser
|
||||
|
||||
import "github.com/df-mc/dragonfly/server/session"
|
||||
|
||||
type fwdlistener struct {
|
||||
Conn chan session.Conn
|
||||
}
|
||||
|
||||
func (l *fwdlistener) Accept() (session.Conn, error) {
|
||||
return <-l.Conn, nil
|
||||
}
|
||||
|
||||
func (l *fwdlistener) Disconnect(conn session.Conn, reason string) error {
|
||||
return conn.Close()
|
||||
}
|
||||
|
||||
func (l *fwdlistener) Close() error {
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
package seconduser
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/df-mc/dragonfly/server/block/cube"
|
||||
"github.com/df-mc/dragonfly/server/world"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type provider struct {
|
||||
s *secondaryUser
|
||||
}
|
||||
|
||||
func (p *provider) Settings() *world.Settings {
|
||||
return &world.Settings{
|
||||
Mutex: sync.Mutex{},
|
||||
Name: "world",
|
||||
Spawn: cube.Pos{0, 0, 0},
|
||||
DefaultGameMode: world.GameModeCreative,
|
||||
Difficulty: world.DifficultyNormal,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *provider) SaveSettings(*world.Settings) {
|
||||
}
|
||||
|
||||
func (p *provider) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *provider) LoadPlayerSpawnPosition(uuid uuid.UUID) (pos cube.Pos, exists bool, err error) {
|
||||
return cube.Pos{0, 0, 0}, false, nil
|
||||
}
|
||||
|
||||
func (p *provider) SavePlayerSpawnPosition(uuid uuid.UUID, pos cube.Pos) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *provider) LoadColumn(pos world.ChunkPos, dim world.Dimension) (*world.Column, error) {
|
||||
return &world.Column{
|
||||
Chunk: p.s.chunks[pos],
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *provider) StoreColumn(pos world.ChunkPos, dim world.Dimension, col *world.Column) error {
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,113 @@
|
|||
package seconduser
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/bedrock-tool/bedrocktool/utils"
|
||||
"github.com/df-mc/dragonfly/server"
|
||||
"github.com/df-mc/dragonfly/server/player"
|
||||
"github.com/df-mc/dragonfly/server/player/skin"
|
||||
"github.com/df-mc/dragonfly/server/session"
|
||||
"github.com/df-mc/dragonfly/server/world"
|
||||
"github.com/df-mc/dragonfly/server/world/chunk"
|
||||
"github.com/go-gl/mathgl/mgl64"
|
||||
"github.com/sandertv/gophertunnel/minecraft"
|
||||
"github.com/sandertv/gophertunnel/minecraft/protocol"
|
||||
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type secondaryUser struct {
|
||||
listener *fwdlistener
|
||||
server *server.Server
|
||||
proxy *utils.ProxyContext
|
||||
|
||||
ispre118 bool
|
||||
hasCustomBlocks bool
|
||||
|
||||
chunks map[world.ChunkPos]*chunk.Chunk
|
||||
blockNBTs map[protocol.BlockPos][]map[string]any
|
||||
dimension world.Dimension
|
||||
entities map[int64]*serverEntity
|
||||
|
||||
mainPlayer *player.Player
|
||||
}
|
||||
|
||||
func NewSecondUser() *utils.ProxyHandler {
|
||||
s := &secondaryUser{
|
||||
listener: &fwdlistener{
|
||||
Conn: make(chan session.Conn),
|
||||
},
|
||||
|
||||
chunks: make(map[world.ChunkPos]*chunk.Chunk),
|
||||
blockNBTs: make(map[protocol.BlockPos][]map[string]any),
|
||||
dimension: world.Overworld,
|
||||
entities: make(map[int64]*serverEntity),
|
||||
}
|
||||
|
||||
s.server = server.Config{
|
||||
Listeners: []func(conf server.Config) (server.Listener, error){
|
||||
func(conf server.Config) (server.Listener, error) {
|
||||
return s.listener, nil
|
||||
},
|
||||
},
|
||||
Log: logrus.StandardLogger(),
|
||||
Name: "Secondary",
|
||||
Generator: func(dim world.Dimension) world.Generator { return &world.NopGenerator{} },
|
||||
WorldProvider: &provider{s: s},
|
||||
ReadOnlyWorld: true,
|
||||
}.New()
|
||||
|
||||
go s.loop()
|
||||
|
||||
return &utils.ProxyHandler{
|
||||
Name: "Secondary User",
|
||||
ProxyRef: func(pc *utils.ProxyContext) {
|
||||
s.proxy = pc
|
||||
},
|
||||
SecondaryClientCB: s.SecondaryClientCB,
|
||||
OnClientConnect: func(conn minecraft.IConn) {
|
||||
id := conn.IdentityData()
|
||||
s.mainPlayer = player.New(id.DisplayName, skin.New(64, 64), mgl64.Vec3{0, 00})
|
||||
s.server.World().AddEntity(s.mainPlayer)
|
||||
},
|
||||
PacketCB: func(pk packet.Packet, toServer bool, timeReceived time.Time) (packet.Packet, error) {
|
||||
|
||||
switch pk := pk.(type) {
|
||||
case *packet.LevelChunk:
|
||||
s.processLevelChunk(pk)
|
||||
case *packet.SubChunk:
|
||||
s.processSubChunk(pk)
|
||||
case *packet.ChangeDimension:
|
||||
s.processChangeDimension(pk)
|
||||
|
||||
case *packet.MovePlayer:
|
||||
v := mgl64.Vec3{float64(pk.Position.X()), float64(pk.Position.Y()), float64(pk.Position.Z())}
|
||||
s.mainPlayer.Teleport(v)
|
||||
case *packet.PlayerAuthInput:
|
||||
v := mgl64.Vec3{float64(pk.Position.X()), float64(pk.Position.Y()), float64(pk.Position.Z())}
|
||||
s.mainPlayer.Teleport(v)
|
||||
|
||||
case *packet.AddActor:
|
||||
e := newServerEntity(pk.EntityType)
|
||||
s.entities[pk.EntityUniqueID] = e
|
||||
s.server.World().AddEntity(e)
|
||||
}
|
||||
|
||||
return pk, nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (s *secondaryUser) SecondaryClientCB(conn minecraft.IConn) {
|
||||
s.listener.Conn <- conn
|
||||
}
|
||||
|
||||
func (s *secondaryUser) loop() {
|
||||
s.server.Listen()
|
||||
for s.server.Accept(func(p *player.Player) {
|
||||
logrus.Infof("%s Joined", p.Name())
|
||||
p.Teleport(s.mainPlayer.Position())
|
||||
}) {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,127 @@
|
|||
package handlers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/bedrock-tool/bedrocktool/utils"
|
||||
"github.com/google/uuid"
|
||||
"github.com/sandertv/gophertunnel/minecraft/protocol"
|
||||
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type SkinSaver struct {
|
||||
PlayerNameFilter string
|
||||
OnlyIfHasGeometry bool
|
||||
ServerName string
|
||||
Proxy *utils.ProxyContext
|
||||
fpath string
|
||||
|
||||
playerSkinPacks map[uuid.UUID]*utils.SkinPack
|
||||
playerNames map[uuid.UUID]string
|
||||
}
|
||||
|
||||
func (s *SkinSaver) AddPlayerSkin(playerID uuid.UUID, playerName string, skin *utils.Skin) (added bool) {
|
||||
p, ok := s.playerSkinPacks[playerID]
|
||||
if !ok {
|
||||
creating := fmt.Sprintf("Creating Skinpack for %s", playerName)
|
||||
s.Proxy.SendPopup(creating)
|
||||
logrus.Info(creating)
|
||||
p = utils.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
|
||||
}
|
||||
|
||||
func (s *SkinSaver) AddSkin(playerName string, playerID uuid.UUID, playerSkin *protocol.Skin) (string, *utils.Skin, bool) {
|
||||
if playerName == "" {
|
||||
playerName = s.playerNames[playerID]
|
||||
if playerName == "" {
|
||||
playerName = playerID.String()
|
||||
}
|
||||
}
|
||||
if !strings.HasPrefix(playerName, s.PlayerNameFilter) {
|
||||
return playerName, nil, false
|
||||
}
|
||||
s.playerNames[playerID] = playerName
|
||||
|
||||
skin := &utils.Skin{Skin: playerSkin}
|
||||
if s.OnlyIfHasGeometry && !skin.HaveGeometry() {
|
||||
return playerName, nil, false
|
||||
}
|
||||
wasAdded := s.AddPlayerSkin(playerID, playerName, skin)
|
||||
|
||||
return playerName, skin, wasAdded
|
||||
}
|
||||
|
||||
type SkinAdd struct {
|
||||
PlayerName string
|
||||
Skin *protocol.Skin
|
||||
}
|
||||
|
||||
func (s *SkinSaver) ProcessPacket(pk packet.Packet) (out []SkinAdd) {
|
||||
switch pk := pk.(type) {
|
||||
case *packet.PlayerList:
|
||||
if pk.ActionType == 1 { // remove
|
||||
return nil
|
||||
}
|
||||
for _, player := range pk.Entries {
|
||||
playerName, skin, wasAdded := s.AddSkin(utils.CleanupName(player.Username), player.UUID, &player.Skin)
|
||||
if wasAdded {
|
||||
out = append(out, SkinAdd{
|
||||
PlayerName: playerName,
|
||||
Skin: skin.Skin,
|
||||
})
|
||||
}
|
||||
}
|
||||
case *packet.AddPlayer:
|
||||
if _, ok := s.playerNames[pk.UUID]; !ok {
|
||||
s.playerNames[pk.UUID] = utils.CleanupName(pk.Username)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func NewSkinSaver(skinCB func(SkinAdd)) *utils.ProxyHandler {
|
||||
s := &SkinSaver{
|
||||
playerSkinPacks: make(map[uuid.UUID]*utils.SkinPack),
|
||||
playerNames: make(map[uuid.UUID]string),
|
||||
}
|
||||
return &utils.ProxyHandler{
|
||||
Name: "Skin Saver",
|
||||
ProxyRef: func(pc *utils.ProxyContext) {
|
||||
s.Proxy = pc
|
||||
},
|
||||
AddressAndName: func(address, hostname string) error {
|
||||
outPathBase := fmt.Sprintf("skins/%s", hostname)
|
||||
os.MkdirAll(outPathBase, 0o755)
|
||||
s.fpath = outPathBase
|
||||
return nil
|
||||
},
|
||||
PacketCB: func(pk packet.Packet, toServer bool, timeReceived time.Time) (packet.Packet, error) {
|
||||
if !toServer {
|
||||
for _, s := range s.ProcessPacket(pk) {
|
||||
if skinCB != nil {
|
||||
skinCB(s)
|
||||
}
|
||||
}
|
||||
}
|
||||
return pk, nil
|
||||
},
|
||||
}
|
||||
}
|
|
@ -0,0 +1,168 @@
|
|||
package worlds
|
||||
|
||||
import (
|
||||
"github.com/bedrock-tool/bedrocktool/locale"
|
||||
"github.com/df-mc/dragonfly/server/block/cube"
|
||||
"github.com/df-mc/dragonfly/server/world"
|
||||
"github.com/df-mc/dragonfly/server/world/chunk"
|
||||
"github.com/repeale/fp-go"
|
||||
"github.com/sandertv/gophertunnel/minecraft/protocol"
|
||||
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func (w *worldsHandler) processChangeDimension(pk *packet.ChangeDimension) {
|
||||
w.SaveAndReset()
|
||||
dimensionID := pk.Dimension
|
||||
if w.serverState.ispre118 && dimensionID == 0 {
|
||||
dimensionID += 10
|
||||
}
|
||||
w.worldState.dimension, _ = world.DimensionByID(int(dimensionID))
|
||||
}
|
||||
|
||||
func (w *worldsHandler) processLevelChunk(pk *packet.LevelChunk) {
|
||||
// ignore empty chunks THANKS WEIRD SERVER SOFTWARE DEVS
|
||||
if len(pk.RawPayload) == 0 {
|
||||
logrus.Info(locale.Loc("empty_chunk", nil))
|
||||
return
|
||||
}
|
||||
|
||||
var subChunkCount int
|
||||
switch pk.SubChunkCount {
|
||||
case protocol.SubChunkRequestModeLimited:
|
||||
fallthrough
|
||||
case protocol.SubChunkRequestModeLimitless:
|
||||
subChunkCount = 0
|
||||
default:
|
||||
subChunkCount = int(pk.SubChunkCount)
|
||||
}
|
||||
|
||||
ch, blockNBTs, err := chunk.NetworkDecode(world.AirRID(), pk.RawPayload, subChunkCount, w.worldState.dimension.Range(), w.serverState.ispre118, w.bp.HasBlocks())
|
||||
if err != nil {
|
||||
logrus.Error(err)
|
||||
return
|
||||
}
|
||||
for _, blockNBT := range blockNBTs {
|
||||
x := int(blockNBT["x"].(int32))
|
||||
y := int(blockNBT["y"].(int32))
|
||||
z := int(blockNBT["z"].(int32))
|
||||
w.worldState.blockNBTs[cube.Pos{x, y, z}] = blockNBT
|
||||
}
|
||||
|
||||
w.worldState.chunks[(world.ChunkPos)(pk.Position)] = ch
|
||||
|
||||
max := w.worldState.dimension.Range().Height() / 16
|
||||
switch pk.SubChunkCount {
|
||||
case protocol.SubChunkRequestModeLimited:
|
||||
max = int(pk.HighestSubChunk)
|
||||
fallthrough
|
||||
case protocol.SubChunkRequestModeLimitless:
|
||||
var offsetTable []protocol.SubChunkOffset
|
||||
r := w.worldState.dimension.Range()
|
||||
for y := int8(r.Min() / 16); y < int8(r.Max()); y++ {
|
||||
offsetTable = append(offsetTable, protocol.SubChunkOffset{0, y, 0})
|
||||
}
|
||||
|
||||
dimId, _ := world.DimensionID(w.worldState.dimension)
|
||||
w.proxy.Server.WritePacket(&packet.SubChunkRequest{
|
||||
Dimension: int32(dimId),
|
||||
Position: protocol.SubChunkPos{
|
||||
pk.Position.X(), 0, pk.Position.Z(),
|
||||
},
|
||||
Offsets: offsetTable[:max],
|
||||
})
|
||||
default:
|
||||
// legacy
|
||||
empty := fp.Every(func(sub *chunk.SubChunk) bool {
|
||||
return sub.Empty()
|
||||
})(ch.Sub())
|
||||
if !empty {
|
||||
w.mapUI.SetChunk((world.ChunkPos)(pk.Position), ch, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (w *worldsHandler) processSubChunk(pk *packet.SubChunk) {
|
||||
posToRedraw := make(map[world.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])
|
||||
pos = world.ChunkPos{absX, absZ}
|
||||
)
|
||||
ch, ok := w.worldState.chunks[pos]
|
||||
if !ok {
|
||||
logrus.Error(locale.Loc("subchunk_before_chunk", nil))
|
||||
continue
|
||||
}
|
||||
blockNBTs, err := ch.ApplySubChunkEntry(uint8(absY), &sub)
|
||||
if err != nil {
|
||||
logrus.Error(err)
|
||||
}
|
||||
for _, blockNBT := range blockNBTs {
|
||||
x := int(blockNBT["x"].(int32))
|
||||
y := int(blockNBT["y"].(int32))
|
||||
z := int(blockNBT["z"].(int32))
|
||||
w.worldState.blockNBTs[cube.Pos{x, y, z}] = blockNBT
|
||||
}
|
||||
|
||||
posToRedraw[pos] = true
|
||||
}
|
||||
|
||||
// redraw the chunks
|
||||
for pos := range posToRedraw {
|
||||
w.mapUI.SetChunk(pos, w.worldState.chunks[pos], true)
|
||||
}
|
||||
w.mapUI.SchedRedraw()
|
||||
}
|
||||
|
||||
func blockPosInChunk(pos protocol.BlockPos) (uint8, int16, uint8) {
|
||||
return uint8(pos.X() & 0x0f), int16(pos.Y() & 0x0f), uint8(pos.Z() & 0x0f)
|
||||
}
|
||||
|
||||
func (w *worldsHandler) 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.worldState.chunks),
|
||||
"Name": w.worldState.Name,
|
||||
}, len(w.worldState.chunks)))
|
||||
case *packet.SubChunk:
|
||||
w.processSubChunk(pk)
|
||||
case *packet.BlockActorData:
|
||||
p := pk.Position
|
||||
w.worldState.blockNBTs[cube.Pos{int(p.X()), int(p.Y()), int(p.Z())}] = pk.NBTData
|
||||
case *packet.UpdateBlock:
|
||||
if w.settings.BlockUpdates {
|
||||
cp := world.ChunkPos{pk.Position.X() >> 4, pk.Position.Z() >> 4}
|
||||
c, ok := w.worldState.chunks[cp]
|
||||
if ok {
|
||||
x, y, z := blockPosInChunk(pk.Position)
|
||||
c.SetBlock(x, y, z, uint8(pk.Layer), pk.NewBlockRuntimeID)
|
||||
w.mapUI.SetChunk(cp, c, true)
|
||||
}
|
||||
}
|
||||
case *packet.UpdateSubChunkBlocks:
|
||||
if w.settings.BlockUpdates {
|
||||
cp := world.ChunkPos{pk.Position.X(), pk.Position.Z()}
|
||||
c, ok := w.worldState.chunks[cp]
|
||||
if ok {
|
||||
for _, bce := range pk.Blocks {
|
||||
x, y, z := blockPosInChunk(bce.BlockPos)
|
||||
if bce.SyncedUpdateType == packet.BlockToEntityTransition {
|
||||
c.SetBlock(x, y, z, 0, world.AirRID())
|
||||
} else {
|
||||
c.SetBlock(x, y, z, 0, bce.BlockRuntimeID)
|
||||
}
|
||||
}
|
||||
w.mapUI.SetChunk(cp, c, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
return pk
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 432 B |
|
@ -1,18 +1,20 @@
|
|||
package world
|
||||
package worlds_test
|
||||
|
||||
import (
|
||||
"image/png"
|
||||
"os"
|
||||
"runtime/pprof"
|
||||
"testing"
|
||||
|
||||
"github.com/bedrock-tool/bedrocktool/utils"
|
||||
"github.com/df-mc/dragonfly/server/block/cube"
|
||||
"github.com/df-mc/dragonfly/server/world/chunk"
|
||||
)
|
||||
|
||||
func Test(t *testing.T) {
|
||||
data, _ := os.ReadFile("chunk.bin")
|
||||
ch, _, _ := chunk.NetworkDecode(33, data, 6, cube.Range{0, 255}, true)
|
||||
i := Chunk2Img(ch)
|
||||
ch, _, _ := chunk.NetworkDecode(33, data, 6, cube.Range{0, 255}, true, false)
|
||||
i := utils.Chunk2Img(ch)
|
||||
f, _ := os.Create("chunk.png")
|
||||
png.Encode(f, i)
|
||||
f.Close()
|
||||
|
@ -20,19 +22,32 @@ func Test(t *testing.T) {
|
|||
|
||||
func Benchmark_chunk_decode(b *testing.B) {
|
||||
data, _ := os.ReadFile("chunk.bin")
|
||||
|
||||
cf, _ := os.Create("cpu.pprof")
|
||||
err := pprof.StartCPUProfile(cf)
|
||||
if err != nil {
|
||||
b.Error(err)
|
||||
}
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, _, err := chunk.NetworkDecode(33, data, 6, cube.Range{0, 255}, true)
|
||||
_, _, err := chunk.NetworkDecode(33, data, 6, cube.Range{0, 255}, true, false)
|
||||
if err != nil {
|
||||
b.Error(err)
|
||||
}
|
||||
}
|
||||
pprof.StopCPUProfile()
|
||||
}
|
||||
|
||||
func Benchmark_render_chunk(b *testing.B) {
|
||||
data, _ := os.ReadFile("chunk.bin")
|
||||
ch, _, _ := chunk.NetworkDecode(33, data, 6, cube.Range{0, 255}, true)
|
||||
ch, _, _ := chunk.NetworkDecode(33, data, 6, cube.Range{0, 255}, true, false)
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
Chunk2Img(ch)
|
||||
cf, _ := os.Create("cpu.pprof")
|
||||
err := pprof.StartCPUProfile(cf)
|
||||
if err != nil {
|
||||
b.Error(err)
|
||||
}
|
||||
for i := 0; i < b.N; i++ {
|
||||
utils.Chunk2Img(ch)
|
||||
}
|
||||
pprof.StopCPUProfile()
|
||||
}
|
|
@ -0,0 +1,297 @@
|
|||
package worlds
|
||||
|
||||
import (
|
||||
"github.com/bedrock-tool/bedrocktool/utils/behaviourpack"
|
||||
"github.com/bedrock-tool/bedrocktool/utils/nbtconv"
|
||||
"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
|
||||
Inventory map[byte]map[byte]protocol.ItemInstance
|
||||
|
||||
Helmet *protocol.ItemInstance
|
||||
Chestplate *protocol.ItemInstance
|
||||
Leggings *protocol.ItemInstance
|
||||
Boots *protocol.ItemInstance
|
||||
}
|
||||
|
||||
type serverEntityType struct {
|
||||
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
|
||||
}
|
||||
|
||||
type serverEntity struct {
|
||||
world.Entity
|
||||
EntityType serverEntityType
|
||||
}
|
||||
|
||||
var _ world.SaveableEntityType = &serverEntityType{}
|
||||
|
||||
func (e serverEntity) Type() world.EntityType {
|
||||
return e.EntityType
|
||||
}
|
||||
|
||||
func (w *worldsHandler) processAddActor(pk *packet.AddActor) {
|
||||
e, ok := w.getEntity(pk.EntityRuntimeID)
|
||||
if !ok {
|
||||
e = &entityState{
|
||||
RuntimeID: pk.EntityRuntimeID,
|
||||
UniqueID: pk.EntityUniqueID,
|
||||
EntityType: pk.EntityType,
|
||||
Inventory: make(map[byte]map[byte]protocol.ItemInstance),
|
||||
Metadata: make(map[uint32]any),
|
||||
}
|
||||
w.worldState.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
|
||||
}
|
||||
}
|
||||
|
||||
var flagNames = map[uint8]string{
|
||||
protocol.EntityDataFlagSheared: "Sheared",
|
||||
protocol.EntityDataFlagCaptain: "IsIllagerCaptain",
|
||||
protocol.EntityDataFlagSitting: "Sitting",
|
||||
protocol.EntityDataFlagBaby: "IsBaby",
|
||||
protocol.EntityDataFlagTamed: "IsTamed",
|
||||
protocol.EntityDataFlagTrusting: "IsTrusting",
|
||||
protocol.EntityDataFlagOrphaned: "IsOrphaned",
|
||||
protocol.EntityDataFlagAngry: "IsAngry",
|
||||
protocol.EntityDataFlagOutOfControl: "IsOutOfControl",
|
||||
protocol.EntityDataFlagSaddled: "Saddled",
|
||||
protocol.EntityDataFlagChested: "Chested",
|
||||
protocol.EntityDataFlagShowBottom: "ShowBottom",
|
||||
protocol.EntityDataFlagGliding: "IsGliding",
|
||||
protocol.EntityDataFlagSwimming: "IsSwimming",
|
||||
protocol.EntityDataFlagEating: "IsEating",
|
||||
protocol.EntityDataFlagScared: "IsScared",
|
||||
protocol.EntityDataFlagStunned: "IsStunned",
|
||||
protocol.EntityDataFlagRoaring: "IsRoaring",
|
||||
}
|
||||
|
||||
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 skinID, ok := metadata[protocol.EntityDataKeySkinID]; ok {
|
||||
nbt["SkinID"] = int32(skinID.(int32))
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
if _, ok := metadata[protocol.EntityDataKeyFlags]; ok {
|
||||
if metadata.Flag(protocol.EntityDataKeyFlags, protocol.EntityDataFlagNoAI) {
|
||||
nbt["IsAutonomous"] = false
|
||||
}
|
||||
for k, v := range flagNames {
|
||||
nbt[v] = metadata.Flag(protocol.EntityDataKeyFlags, k)
|
||||
}
|
||||
|
||||
AlwaysShowName := metadata.Flag(protocol.EntityDataKeyFlags, protocol.EntityDataFlagAlwaysShowName)
|
||||
if AlwaysShowName {
|
||||
nbt["CustomNameVisible"] = true
|
||||
}
|
||||
|
||||
type effect struct {
|
||||
Id byte
|
||||
Amplifier byte
|
||||
Duration int32
|
||||
DurationEasy int32
|
||||
DurationNormal int32
|
||||
DurationHard int32
|
||||
Ambient bool
|
||||
ShowParticles bool
|
||||
DisplayOnScreenTextureAnimation bool
|
||||
}
|
||||
|
||||
activeEffects := []effect{}
|
||||
addEffect := func(id int, showParticles bool) {
|
||||
activeEffects = append(activeEffects, effect{
|
||||
Id: byte(id),
|
||||
Amplifier: 1,
|
||||
Duration: 10000000,
|
||||
ShowParticles: false,
|
||||
})
|
||||
}
|
||||
|
||||
invisible := metadata.Flag(protocol.EntityDataKeyFlags, protocol.EntityDataFlagInvisible)
|
||||
if invisible {
|
||||
addEffect(packet.EffectInvisibility, false)
|
||||
}
|
||||
|
||||
if len(activeEffects) > 0 {
|
||||
nbt["ActiveEffects"] = activeEffects
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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.HeadYaw, s.Pitch},
|
||||
"Motion": vec3float32(s.Velocity),
|
||||
"UniqueID": int64(s.UniqueID),
|
||||
},
|
||||
},
|
||||
}
|
||||
entityMetadataToNBT(s.Metadata, e.EntityType.NBT)
|
||||
|
||||
if s.Helmet != nil || s.Chestplate != nil || s.Leggings != nil || s.Boots != nil {
|
||||
e.EntityType.NBT["Armor"] = []map[string]any{
|
||||
nbtconv.WriteItem(stackToItem(s.Helmet.Stack), true),
|
||||
nbtconv.WriteItem(stackToItem(s.Chestplate.Stack), true),
|
||||
nbtconv.WriteItem(stackToItem(s.Leggings.Stack), true),
|
||||
nbtconv.WriteItem(stackToItem(s.Boots.Stack), true),
|
||||
}
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
func (w *worldsHandler) getEntity(id uint64) (*entityState, bool) {
|
||||
e, ok := w.worldState.entities[id]
|
||||
return e, ok
|
||||
}
|
||||
|
||||
func (w *worldsHandler) ProcessEntityPackets(pk packet.Packet) packet.Packet {
|
||||
if !w.settings.SaveEntities {
|
||||
return pk
|
||||
}
|
||||
|
||||
switch pk := pk.(type) {
|
||||
case *packet.AddActor:
|
||||
w.processAddActor(pk)
|
||||
case *packet.RemoveActor:
|
||||
case *packet.SetActorData:
|
||||
e, ok := w.getEntity(pk.EntityRuntimeID)
|
||||
if ok {
|
||||
e.Metadata = pk.EntityMetadata
|
||||
w.bp.AddEntity(behaviourpack.EntityIn{
|
||||
Identifier: e.EntityType,
|
||||
Attr: nil,
|
||||
Meta: pk.EntityMetadata,
|
||||
})
|
||||
}
|
||||
case *packet.SetActorMotion:
|
||||
e, ok := w.getEntity(pk.EntityRuntimeID)
|
||||
if ok {
|
||||
e.Velocity = pk.Velocity
|
||||
}
|
||||
case *packet.MoveActorDelta:
|
||||
e, ok := w.getEntity(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.getEntity(pk.EntityRuntimeID)
|
||||
if ok {
|
||||
e.Position = pk.Position
|
||||
e.Pitch = pk.Rotation.X()
|
||||
e.Yaw = pk.Rotation.Y()
|
||||
}
|
||||
case *packet.MobEquipment:
|
||||
e, ok := w.getEntity(pk.EntityRuntimeID)
|
||||
if ok {
|
||||
w, ok := e.Inventory[pk.WindowID]
|
||||
if !ok {
|
||||
w = make(map[byte]protocol.ItemInstance)
|
||||
e.Inventory[pk.WindowID] = w
|
||||
}
|
||||
w[pk.HotBarSlot] = pk.NewItem
|
||||
}
|
||||
case *packet.MobArmourEquipment:
|
||||
e, ok := w.getEntity(pk.EntityRuntimeID)
|
||||
if ok {
|
||||
e.Helmet = &pk.Helmet
|
||||
e.Chestplate = &pk.Chestplate
|
||||
e.Leggings = &pk.Chestplate
|
||||
e.Boots = &pk.Boots
|
||||
}
|
||||
}
|
||||
return pk
|
||||
}
|
|
@ -0,0 +1,183 @@
|
|||
package worlds
|
||||
|
||||
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/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/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 *worldsHandler) processItemPacketsServer(pk packet.Packet) packet.Packet {
|
||||
if !w.settings.SaveInventories {
|
||||
return pk
|
||||
}
|
||||
|
||||
switch pk := pk.(type) {
|
||||
case *packet.ContainerOpen:
|
||||
// add to open containers
|
||||
existing, ok := w.worldState.openItemContainers[pk.WindowID]
|
||||
if !ok {
|
||||
existing = &itemContainer{}
|
||||
}
|
||||
w.worldState.openItemContainers[pk.WindowID] = &itemContainer{
|
||||
OpenPacket: pk,
|
||||
Content: existing.Content,
|
||||
}
|
||||
|
||||
case *packet.InventoryContent:
|
||||
if pk.WindowID == 0x0 { // inventory
|
||||
w.serverState.playerInventory = pk.Content
|
||||
} else {
|
||||
// save content
|
||||
existing, ok := w.worldState.openItemContainers[byte(pk.WindowID)]
|
||||
if ok {
|
||||
existing.Content = pk
|
||||
}
|
||||
}
|
||||
|
||||
case *packet.InventorySlot:
|
||||
if pk.WindowID == 0x0 {
|
||||
if w.serverState.playerInventory == nil {
|
||||
w.serverState.playerInventory = make([]protocol.ItemInstance, 36)
|
||||
}
|
||||
w.serverState.playerInventory[pk.Slot] = pk.NewItem
|
||||
} else {
|
||||
// save content
|
||||
existing, ok := w.worldState.openItemContainers[byte(pk.WindowID)]
|
||||
if ok {
|
||||
existing.Content.Content[pk.Slot] = pk.NewItem
|
||||
}
|
||||
}
|
||||
|
||||
case *packet.ItemStackResponse:
|
||||
|
||||
case *packet.ContainerClose:
|
||||
// find container info
|
||||
existing, ok := w.worldState.openItemContainers[byte(pk.WindowID)]
|
||||
|
||||
switch pk.WindowID {
|
||||
case protocol.WindowIDArmour: // todo handle
|
||||
case protocol.WindowIDOffHand: // todo handle
|
||||
case protocol.WindowIDUI:
|
||||
case protocol.WindowIDInventory: // todo handle
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
|
||||
default:
|
||||
if !ok {
|
||||
logrus.Warn(locale.Loc("warn_window_closed_not_open", nil))
|
||||
break
|
||||
}
|
||||
|
||||
if existing.Content == nil {
|
||||
break
|
||||
}
|
||||
|
||||
// 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
|
||||
p := existing.OpenPacket.ContainerPosition
|
||||
nbt, ok := w.worldState.blockNBTs[cube.Pos{int(p.X()), int(p.Y()), int(p.Z())}]
|
||||
if ok {
|
||||
nbt["Items"] = nbtconv.InvToNBT(inv)
|
||||
}
|
||||
|
||||
w.proxy.SendMessage(locale.Loc("saved_block_inv", nil))
|
||||
|
||||
// remove it again
|
||||
delete(w.worldState.openItemContainers, byte(pk.WindowID))
|
||||
}
|
||||
|
||||
case *packet.ItemComponent:
|
||||
w.bp.ApplyComponentEntries(pk.Items)
|
||||
}
|
||||
return pk
|
||||
}
|
||||
|
||||
func (w *worldsHandler) 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.Item(it.NBTData, &s)
|
||||
}
|
|
@ -0,0 +1,308 @@
|
|||
package worlds
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/draw"
|
||||
"math"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/bedrock-tool/bedrocktool/locale"
|
||||
"github.com/bedrock-tool/bedrocktool/ui/messages"
|
||||
"github.com/bedrock-tool/bedrocktool/utils"
|
||||
"github.com/go-gl/mathgl/mgl32"
|
||||
"golang.design/x/lockfree"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
const ViewMapID = 0x424242
|
||||
|
||||
// MapItemPacket tells the client that it has a map with id 0x424242 in the offhand
|
||||
var MapItemPacket = packet.InventoryContent{
|
||||
WindowID: 119,
|
||||
Content: []protocol.ItemInstance{
|
||||
{
|
||||
StackNetworkID: 1, // random if auth inv
|
||||
Stack: protocol.ItemStack{
|
||||
ItemType: protocol.ItemType{
|
||||
NetworkID: 420, // overwritten in onconnect
|
||||
MetadataValue: 0,
|
||||
},
|
||||
BlockRuntimeID: 0,
|
||||
Count: 1,
|
||||
NBTData: map[string]interface{}{
|
||||
"map_name_index": int64(1),
|
||||
"map_uuid": int64(ViewMapID),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func imin(a, b int32) int32 {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func imax(a, b int32) int32 {
|
||||
if a > b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func (m *MapUI) GetBounds() (min, max protocol.ChunkPos) {
|
||||
if len(m.renderedChunks) == 0 {
|
||||
return
|
||||
}
|
||||
min = protocol.ChunkPos{math.MaxInt32, math.MaxInt32}
|
||||
for chunk := range m.renderedChunks {
|
||||
min[0] = imin(min[0], chunk[0])
|
||||
min[1] = imin(min[1], chunk[1])
|
||||
max[0] = imax(max[0], chunk[0])
|
||||
max[1] = imax(max[1], chunk[1])
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type RenderElem struct {
|
||||
pos protocol.ChunkPos
|
||||
ch *chunk.Chunk
|
||||
}
|
||||
|
||||
type MapUI struct {
|
||||
img *image.RGBA // rendered image
|
||||
zoomLevel int // pixels per chunk
|
||||
renderQueue *lockfree.Queue
|
||||
renderedChunks map[protocol.ChunkPos]*image.RGBA // prerendered chunks
|
||||
needRedraw bool // when the map has updated this is true
|
||||
showOnGui bool
|
||||
l sync.RWMutex
|
||||
|
||||
ticker *time.Ticker
|
||||
w *worldsHandler
|
||||
}
|
||||
|
||||
func NewMapUI(w *worldsHandler) *MapUI {
|
||||
m := &MapUI{
|
||||
img: image.NewRGBA(image.Rect(0, 0, 128, 128)),
|
||||
zoomLevel: 16,
|
||||
renderQueue: lockfree.NewQueue(),
|
||||
renderedChunks: make(map[protocol.ChunkPos]*image.RGBA),
|
||||
needRedraw: true,
|
||||
w: w,
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *MapUI) Start() {
|
||||
r := m.w.gui.Message(messages.CanShowImages{})
|
||||
if r.Ok {
|
||||
m.showOnGui = true
|
||||
}
|
||||
|
||||
// init map
|
||||
err := m.w.proxy.ClientWritePacket(&packet.ClientBoundMapItemData{
|
||||
MapID: ViewMapID,
|
||||
Scale: 4,
|
||||
MapsIncludedIn: []int64{ViewMapID},
|
||||
UpdateFlags: packet.MapUpdateFlagInitialisation,
|
||||
})
|
||||
if err != nil {
|
||||
logrus.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
m.ticker = time.NewTicker(33 * time.Millisecond)
|
||||
go func() {
|
||||
for range m.ticker.C {
|
||||
if m.needRedraw {
|
||||
m.needRedraw = false
|
||||
m.Redraw()
|
||||
|
||||
if err := m.w.proxy.ClientWritePacket(&packet.ClientBoundMapItemData{
|
||||
MapID: ViewMapID,
|
||||
Scale: 4,
|
||||
Width: 128,
|
||||
Height: 128,
|
||||
Pixels: utils.Img2rgba(m.img),
|
||||
UpdateFlags: packet.MapUpdateFlagTexture,
|
||||
}); err != nil {
|
||||
logrus.Error(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
go func() { // send map item
|
||||
t := time.NewTicker(1 * time.Second)
|
||||
for range t.C {
|
||||
if m.w.ctx.Err() != nil {
|
||||
return
|
||||
}
|
||||
err := m.w.proxy.ClientWritePacket(&MapItemPacket)
|
||||
if err != nil {
|
||||
logrus.Error(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (m *MapUI) Stop() {
|
||||
if m.ticker != nil {
|
||||
m.ticker.Stop()
|
||||
}
|
||||
}
|
||||
|
||||
// Reset resets the map to inital state
|
||||
func (m *MapUI) Reset() {
|
||||
m.l.Lock()
|
||||
m.renderedChunks = make(map[protocol.ChunkPos]*image.RGBA)
|
||||
m.l.Unlock()
|
||||
m.SchedRedraw()
|
||||
}
|
||||
|
||||
// ChangeZoom adds to the zoom value and goes around to 32 once it hits 128
|
||||
func (m *MapUI) ChangeZoom() {
|
||||
m.zoomLevel /= 2
|
||||
if m.zoomLevel == 0 {
|
||||
m.zoomLevel = 16
|
||||
}
|
||||
m.SchedRedraw()
|
||||
}
|
||||
|
||||
// SchedRedraw tells the map to redraw the next time its sent
|
||||
func (m *MapUI) SchedRedraw() {
|
||||
m.needRedraw = true
|
||||
}
|
||||
|
||||
// Redraw draws chunk images to the map image
|
||||
func (m *MapUI) Redraw() {
|
||||
m.l.Lock()
|
||||
updatedChunks := make([]protocol.ChunkPos, 0, m.renderQueue.Length())
|
||||
for {
|
||||
r, ok := m.renderQueue.Dequeue().(*RenderElem)
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
if r.ch != nil {
|
||||
m.renderedChunks[r.pos] = utils.Chunk2Img(r.ch)
|
||||
}
|
||||
updatedChunks = append(updatedChunks, r.pos)
|
||||
}
|
||||
m.l.Unlock()
|
||||
|
||||
middle := protocol.ChunkPos{
|
||||
int32(m.w.serverState.PlayerPos.Position.X()),
|
||||
int32(m.w.serverState.PlayerPos.Position.Z()),
|
||||
}
|
||||
|
||||
chunksPerLine := float64(128 / m.zoomLevel)
|
||||
pxPerBlock := 128 / chunksPerLine / 16 // how many pixels per block
|
||||
pxSizeChunk := int(math.Floor(pxPerBlock * 16))
|
||||
|
||||
for i := 0; i < len(m.img.Pix); i++ { // clear canvas
|
||||
m.img.Pix[i] = 0
|
||||
}
|
||||
|
||||
m.l.RLock()
|
||||
for _ch := range m.renderedChunks {
|
||||
relativeMiddleX := float64(_ch.X()*16 - middle.X())
|
||||
relativeMiddleZ := float64(_ch.Z()*16 - middle.Z())
|
||||
px := image.Point{ // bottom left corner of the chunk on the map
|
||||
X: int(math.Floor(relativeMiddleX*pxPerBlock)) + 64,
|
||||
Y: int(math.Floor(relativeMiddleZ*pxPerBlock)) + 64,
|
||||
}
|
||||
|
||||
if !m.img.Rect.Intersect(image.Rect(px.X, px.Y, px.X+pxSizeChunk, px.Y+pxSizeChunk)).Empty() {
|
||||
utils.DrawImgScaledPos(m.img, m.renderedChunks[_ch], px, pxSizeChunk)
|
||||
}
|
||||
}
|
||||
if m.showOnGui {
|
||||
min, max := m.GetBounds()
|
||||
m.w.gui.Message(messages.UpdateMap{
|
||||
ChunkCount: len(m.renderedChunks),
|
||||
Rotation: m.w.serverState.PlayerPos.Yaw,
|
||||
UpdatedTiles: updatedChunks,
|
||||
Tiles: m.renderedChunks,
|
||||
BoundsMin: min,
|
||||
BoundsMax: max,
|
||||
})
|
||||
}
|
||||
m.l.RUnlock()
|
||||
}
|
||||
|
||||
func (m *MapUI) ToImage() *image.RGBA {
|
||||
// get the chunk coord bounds
|
||||
min, max := m.GetBounds()
|
||||
chunksX := int(max[0] - min[0] + 1) // how many chunk lengths is x coordinate
|
||||
chunksY := int(max[1] - min[1] + 1)
|
||||
|
||||
img := image.NewRGBA(image.Rect(0, 0, chunksX*16, chunksY*16))
|
||||
|
||||
m.l.RLock()
|
||||
for pos, tile := range m.renderedChunks {
|
||||
px := image.Pt(
|
||||
int((pos.X()-min.X())*16),
|
||||
int((pos.Z()-min.Z())*16),
|
||||
)
|
||||
draw.Draw(img, image.Rect(
|
||||
px.X, px.Y,
|
||||
px.X+16, px.Y+16,
|
||||
), tile, image.Point{}, draw.Src)
|
||||
}
|
||||
m.l.RUnlock()
|
||||
return img
|
||||
}
|
||||
|
||||
func (m *MapUI) SetChunk(pos world.ChunkPos, ch *chunk.Chunk, complete bool) {
|
||||
m.renderQueue.Enqueue(&RenderElem{(protocol.ChunkPos)(pos), ch})
|
||||
m.SchedRedraw()
|
||||
}
|
||||
|
||||
func (w *worldsHandler) 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 *worldsHandler) SetPlayerPos(Position mgl32.Vec3, Pitch, Yaw, HeadYaw float32) {
|
||||
last := w.serverState.PlayerPos
|
||||
current := TPlayerPos{
|
||||
Position: Position,
|
||||
Pitch: Pitch,
|
||||
Yaw: Yaw,
|
||||
HeadYaw: HeadYaw,
|
||||
}
|
||||
w.serverState.PlayerPos = current
|
||||
|
||||
if int(last.Position.X()) != int(current.Position.X()) || int(last.Position.Z()) != int(current.Position.Z()) {
|
||||
w.mapUI.SchedRedraw()
|
||||
}
|
||||
}
|
||||
|
||||
func (w *worldsHandler) 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
|
||||
}
|
|
@ -0,0 +1,202 @@
|
|||
package worlds
|
||||
|
||||
import (
|
||||
"github.com/bedrock-tool/bedrocktool/utils/nbtconv"
|
||||
"github.com/df-mc/dragonfly/server/item/inventory"
|
||||
)
|
||||
|
||||
func (w *worldsHandler) playerData() (ret map[string]any) {
|
||||
ret = map[string]any{
|
||||
"format_version": "1.12.0",
|
||||
"identifier": "minecraft:player",
|
||||
}
|
||||
|
||||
if len(w.serverState.playerInventory) > 0 {
|
||||
inv := inventory.New(len(w.serverState.playerInventory), nil)
|
||||
for i, ii := range w.serverState.playerInventory {
|
||||
inv.SetItem(i, stackToItem(ii.Stack))
|
||||
}
|
||||
ret["Inventory"] = nbtconv.InvToNBT(inv)
|
||||
}
|
||||
|
||||
ret["abilities"] = map[string]any{
|
||||
"doorsandswitches": true,
|
||||
"op": true,
|
||||
"opencontainers": true,
|
||||
"teleport": true,
|
||||
"attackmobs": true,
|
||||
"instabuild": true,
|
||||
"permissionsLevel": int32(3),
|
||||
"flying": false,
|
||||
"lightning": false,
|
||||
"playerPermissionsLevel": int32(2),
|
||||
"attackplayers": true,
|
||||
"build": true,
|
||||
"flySpeed": float32(0.05),
|
||||
"invulnerable": true,
|
||||
"mayfly": true,
|
||||
"mine": true,
|
||||
"walkSpeed": float32(0.1),
|
||||
}
|
||||
|
||||
type attribute struct {
|
||||
Name string
|
||||
Base float32
|
||||
Current float32
|
||||
DefaultMax float32
|
||||
DefaultMin float32
|
||||
Max float32
|
||||
Min float32
|
||||
}
|
||||
|
||||
ret["Attributes"] = []attribute{
|
||||
{
|
||||
Base: 0,
|
||||
Current: 0,
|
||||
DefaultMax: 1024,
|
||||
DefaultMin: -1024,
|
||||
Max: 1024,
|
||||
Min: -1024,
|
||||
Name: "minecraft:luck",
|
||||
},
|
||||
{
|
||||
Base: 20,
|
||||
Current: 20,
|
||||
DefaultMax: 20,
|
||||
DefaultMin: 0,
|
||||
Max: 20,
|
||||
Min: 0,
|
||||
Name: "minecraft:health",
|
||||
},
|
||||
{
|
||||
Base: 0,
|
||||
Current: 0,
|
||||
DefaultMax: 16,
|
||||
DefaultMin: 0,
|
||||
Max: 16,
|
||||
Min: 0,
|
||||
Name: "minecraft:absorption",
|
||||
},
|
||||
{
|
||||
Base: 0,
|
||||
Current: 0,
|
||||
DefaultMax: 1,
|
||||
DefaultMin: 0,
|
||||
Max: 1,
|
||||
Min: 0,
|
||||
Name: "minecraft:knockback_resistance",
|
||||
},
|
||||
{
|
||||
Base: 0.1,
|
||||
Current: 0.1,
|
||||
DefaultMax: 3.4028235e+38,
|
||||
DefaultMin: 0,
|
||||
Max: 3.4028235e+38,
|
||||
Min: 0,
|
||||
Name: "minecraft:movement",
|
||||
},
|
||||
{
|
||||
Base: 0.02,
|
||||
Current: 0.02,
|
||||
DefaultMax: 3.4028235e+38,
|
||||
DefaultMin: 0,
|
||||
Max: 3.4028235e+38,
|
||||
Min: 0,
|
||||
Name: "minecraft:underwater_movement",
|
||||
},
|
||||
{
|
||||
Base: 0.02,
|
||||
Current: 0.02,
|
||||
DefaultMax: 3.4028235e+38,
|
||||
DefaultMin: 0,
|
||||
Max: 3.4028235e+38,
|
||||
Min: 0,
|
||||
Name: "minecraft:lava_movement",
|
||||
},
|
||||
{
|
||||
Base: 16,
|
||||
Current: 16,
|
||||
DefaultMax: 2048,
|
||||
DefaultMin: 0,
|
||||
Max: 2048,
|
||||
Min: 0,
|
||||
Name: "minecraft:follow_range",
|
||||
},
|
||||
{
|
||||
Base: 1,
|
||||
Current: 1,
|
||||
DefaultMax: 1,
|
||||
DefaultMin: 1,
|
||||
Max: 1,
|
||||
Min: 1,
|
||||
Name: "minecraft:attack_damage",
|
||||
},
|
||||
{
|
||||
Base: 20,
|
||||
Current: 20,
|
||||
DefaultMax: 20,
|
||||
DefaultMin: 0,
|
||||
Max: 20,
|
||||
Min: 0,
|
||||
Name: "minecraft:player.hunger",
|
||||
},
|
||||
{
|
||||
Base: 0,
|
||||
Current: 0,
|
||||
DefaultMax: 20,
|
||||
DefaultMin: 0,
|
||||
Max: 20,
|
||||
Min: 0,
|
||||
Name: "minecraft:player.exhaustion",
|
||||
},
|
||||
{
|
||||
Base: 5,
|
||||
Current: 5,
|
||||
DefaultMax: 20,
|
||||
DefaultMin: 0,
|
||||
Max: 20,
|
||||
Min: 0,
|
||||
Name: "minecraft:player.saturation",
|
||||
},
|
||||
{
|
||||
Base: 0,
|
||||
Current: 0,
|
||||
DefaultMax: 24791,
|
||||
DefaultMin: 0,
|
||||
Max: 24791,
|
||||
Min: 0,
|
||||
Name: "minecraft:player.level",
|
||||
},
|
||||
{
|
||||
Base: 0,
|
||||
Current: 0,
|
||||
DefaultMax: 1,
|
||||
DefaultMin: 0,
|
||||
Max: 1,
|
||||
Min: 0,
|
||||
Name: "minecraft:player.experience",
|
||||
},
|
||||
}
|
||||
|
||||
ret["Tags"] = []string{}
|
||||
ret["OnGround"] = true
|
||||
|
||||
spawn := w.serverState.PlayerPos.Position
|
||||
|
||||
ret["SpawnX"] = int32(spawn.X())
|
||||
ret["SpawnY"] = int32(spawn.Y())
|
||||
ret["SpawnZ"] = int32(spawn.Z())
|
||||
|
||||
ret["Pos"] = []float32{
|
||||
float32(spawn.X()),
|
||||
float32(spawn.Y()),
|
||||
float32(spawn.Z()),
|
||||
}
|
||||
|
||||
ret["Rotation"] = []float32{
|
||||
w.serverState.PlayerPos.Pitch,
|
||||
w.serverState.PlayerPos.Yaw,
|
||||
}
|
||||
|
||||
return
|
||||
}
|
|
@ -0,0 +1,628 @@
|
|||
package worlds
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
"image/png"
|
||||
"math/rand"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"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/behaviourpack"
|
||||
"github.com/flytam/filenamify"
|
||||
|
||||
"github.com/df-mc/dragonfly/server/block/cube"
|
||||
"github.com/df-mc/dragonfly/server/world"
|
||||
"github.com/df-mc/dragonfly/server/world/chunk"
|
||||
"github.com/df-mc/dragonfly/server/world/mcdb"
|
||||
"github.com/df-mc/goleveldb/leveldb/opt"
|
||||
"github.com/go-gl/mathgl/mgl32"
|
||||
"github.com/repeale/fp-go"
|
||||
"github.com/sandertv/gophertunnel/minecraft"
|
||||
"github.com/sandertv/gophertunnel/minecraft/protocol"
|
||||
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type TPlayerPos struct {
|
||||
Position mgl32.Vec3
|
||||
Pitch float32
|
||||
Yaw float32
|
||||
HeadYaw float32
|
||||
}
|
||||
|
||||
// the state used for drawing and saving
|
||||
|
||||
type WorldSettings struct {
|
||||
// settings
|
||||
VoidGen bool
|
||||
WithPacks bool
|
||||
SaveImage bool
|
||||
SaveEntities bool
|
||||
SaveInventories bool
|
||||
BlockUpdates bool
|
||||
}
|
||||
|
||||
type worldState struct {
|
||||
dimension world.Dimension
|
||||
chunks map[world.ChunkPos]*chunk.Chunk
|
||||
blockNBTs map[cube.Pos]map[string]any
|
||||
entities map[uint64]*entityState
|
||||
openItemContainers map[byte]*itemContainer
|
||||
Name string
|
||||
}
|
||||
|
||||
type serverState struct {
|
||||
ChunkRadius int
|
||||
ispre118 bool
|
||||
worldCounter int
|
||||
|
||||
playerInventory []protocol.ItemInstance
|
||||
PlayerPos TPlayerPos
|
||||
|
||||
Name string
|
||||
}
|
||||
|
||||
type worldsHandler struct {
|
||||
ctx context.Context
|
||||
wg sync.WaitGroup
|
||||
proxy *utils.ProxyContext
|
||||
mapUI *MapUI
|
||||
gui utils.UI
|
||||
bp *behaviourpack.BehaviourPack
|
||||
|
||||
worldState worldState
|
||||
serverState serverState
|
||||
settings WorldSettings
|
||||
}
|
||||
|
||||
func NewWorldsHandler(ctx context.Context, ui utils.UI, settings WorldSettings) *utils.ProxyHandler {
|
||||
w := &worldsHandler{
|
||||
ctx: ctx,
|
||||
gui: ui,
|
||||
|
||||
serverState: serverState{
|
||||
ispre118: false,
|
||||
worldCounter: 0,
|
||||
ChunkRadius: 0,
|
||||
PlayerPos: TPlayerPos{},
|
||||
},
|
||||
|
||||
settings: settings,
|
||||
}
|
||||
w.mapUI = NewMapUI(w)
|
||||
w.Reset()
|
||||
|
||||
return &utils.ProxyHandler{
|
||||
Name: "Worlds",
|
||||
ProxyRef: func(pc *utils.ProxyContext) {
|
||||
w.proxy = pc
|
||||
|
||||
w.proxy.AddCommand(utils.IngameCommand{
|
||||
Exec: func(cmdline []string) bool {
|
||||
return w.setWorldName(strings.Join(cmdline, " "), false)
|
||||
},
|
||||
Cmd: protocol.Command{
|
||||
Name: "setname",
|
||||
Description: locale.Loc("setname_desc", nil),
|
||||
Overloads: []protocol.CommandOverload{{
|
||||
Parameters: []protocol.CommandParameter{{
|
||||
Name: "name",
|
||||
Type: protocol.CommandArgTypeString,
|
||||
Optional: false,
|
||||
}},
|
||||
}},
|
||||
},
|
||||
})
|
||||
|
||||
w.proxy.AddCommand(utils.IngameCommand{
|
||||
Exec: func(cmdline []string) bool {
|
||||
return w.setVoidGen(!w.settings.VoidGen, false)
|
||||
},
|
||||
Cmd: protocol.Command{
|
||||
Name: "void",
|
||||
Description: locale.Loc("void_desc", nil),
|
||||
},
|
||||
})
|
||||
},
|
||||
AddressAndName: func(address, hostname string) error {
|
||||
w.bp = behaviourpack.New(hostname)
|
||||
w.serverState.Name = hostname
|
||||
return nil
|
||||
},
|
||||
OnClientConnect: func(conn minecraft.IConn) {
|
||||
w.gui.Message(messages.SetUIState(messages.UIStateConnecting))
|
||||
},
|
||||
GameDataModifier: func(gd *minecraft.GameData) {
|
||||
gd.ClientSideGeneration = false
|
||||
},
|
||||
ConnectCB: w.OnConnect,
|
||||
PacketCB: func(pk packet.Packet, toServer bool, timeReceived time.Time) (packet.Packet, error) {
|
||||
forward := true
|
||||
|
||||
if toServer {
|
||||
// from client
|
||||
pk = w.processItemPacketsClient(pk, &forward)
|
||||
pk = w.processMapPacketsClient(pk, &forward)
|
||||
} else {
|
||||
// from server
|
||||
switch pk := pk.(type) {
|
||||
case *packet.ChunkRadiusUpdated:
|
||||
w.serverState.ChunkRadius = int(pk.ChunkRadius)
|
||||
pk.ChunkRadius = 80
|
||||
}
|
||||
pk = w.processItemPacketsServer(pk)
|
||||
pk = w.ProcessChunkPackets(pk)
|
||||
pk = w.ProcessEntityPackets(pk)
|
||||
}
|
||||
|
||||
if !forward {
|
||||
return nil, nil
|
||||
}
|
||||
return pk, nil
|
||||
},
|
||||
OnEnd: func() {
|
||||
w.SaveAndReset()
|
||||
w.wg.Wait()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (w *worldsHandler) setVoidGen(val bool, fromUI bool) bool {
|
||||
w.settings.VoidGen = val
|
||||
var s = locale.Loc("void_generator_false", nil)
|
||||
if w.settings.VoidGen {
|
||||
s = locale.Loc("void_generator_true", nil)
|
||||
}
|
||||
w.proxy.SendMessage(s)
|
||||
|
||||
if !fromUI {
|
||||
w.gui.Message(messages.SetVoidGen{
|
||||
Value: w.settings.VoidGen,
|
||||
})
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (w *worldsHandler) setWorldName(val string, fromUI bool) bool {
|
||||
w.worldState.Name = val
|
||||
w.proxy.SendMessage(locale.Loc("worldname_set", locale.Strmap{"Name": w.worldState.Name}))
|
||||
|
||||
if !fromUI {
|
||||
w.gui.Message(messages.SetWorldName{
|
||||
WorldName: w.worldState.Name,
|
||||
})
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (w *worldsHandler) currentName() string {
|
||||
worldName := "world"
|
||||
if w.serverState.worldCounter > 0 {
|
||||
worldName = fmt.Sprintf("world-%d", w.serverState.worldCounter)
|
||||
}
|
||||
return worldName
|
||||
}
|
||||
|
||||
func (w *worldsHandler) Reset() {
|
||||
w.worldState = worldState{
|
||||
dimension: w.worldState.dimension,
|
||||
chunks: make(map[world.ChunkPos]*chunk.Chunk),
|
||||
blockNBTs: make(map[cube.Pos]map[string]any),
|
||||
entities: make(map[uint64]*entityState),
|
||||
openItemContainers: make(map[byte]*itemContainer),
|
||||
Name: w.currentName(),
|
||||
}
|
||||
w.mapUI.Reset()
|
||||
}
|
||||
|
||||
func (w *worldState) cullChunks() {
|
||||
keys := make([]world.ChunkPos, 0, len(w.chunks))
|
||||
for cp := range w.chunks {
|
||||
keys = append(keys, cp)
|
||||
}
|
||||
for _, cp := range fp.Filter(func(cp world.ChunkPos) bool {
|
||||
return !fp.Some(func(sc *chunk.SubChunk) bool {
|
||||
return !sc.Empty()
|
||||
})(w.chunks[cp].Sub())
|
||||
})(keys) {
|
||||
delete(w.chunks, cp)
|
||||
}
|
||||
}
|
||||
|
||||
type dummyBlock struct {
|
||||
id string
|
||||
nbt map[string]any
|
||||
}
|
||||
|
||||
func (d *dummyBlock) EncodeBlock() (string, map[string]any) {
|
||||
return d.id, d.nbt
|
||||
}
|
||||
|
||||
func (d *dummyBlock) Hash() uint64 {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (d *dummyBlock) Model() world.BlockModel {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *dummyBlock) Color() color.RGBA {
|
||||
return color.RGBA{0, 0, 0, 0}
|
||||
}
|
||||
|
||||
func (d *dummyBlock) DecodeNBT(data map[string]any) any {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *dummyBlock) EncodeNBT() map[string]any {
|
||||
return d.nbt
|
||||
}
|
||||
|
||||
func (w *worldState) Save(folder string) (*mcdb.DB, error) {
|
||||
provider, err := mcdb.Config{
|
||||
Log: logrus.StandardLogger(),
|
||||
Compression: opt.DefaultCompression,
|
||||
}.Open(folder)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
chunkBlockNBT := make(map[world.ChunkPos]map[cube.Pos]world.Block)
|
||||
for bp, blockNBT := range w.blockNBTs { // 3d to 2d
|
||||
cp := world.ChunkPos{int32(bp.X()) >> 4, int32(bp.Z()) >> 4}
|
||||
m, ok := chunkBlockNBT[cp]
|
||||
if !ok {
|
||||
m = make(map[cube.Pos]world.Block)
|
||||
chunkBlockNBT[cp] = m
|
||||
}
|
||||
id := blockNBT["id"].(string)
|
||||
m[bp] = &dummyBlock{id, blockNBT}
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
|
||||
// save chunk data
|
||||
for cp, c := range w.chunks {
|
||||
column := &world.Column{
|
||||
Chunk: c,
|
||||
BlockEntities: chunkBlockNBT[cp],
|
||||
Entities: chunkEntities[cp],
|
||||
}
|
||||
err = provider.StoreColumn(cp, w.dimension, column)
|
||||
if err != nil {
|
||||
logrus.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
return provider, err
|
||||
}
|
||||
|
||||
func (w *worldsHandler) SaveAndReset() {
|
||||
w.worldState.cullChunks()
|
||||
if len(w.worldState.chunks) == 0 {
|
||||
w.Reset()
|
||||
return
|
||||
}
|
||||
|
||||
worldStateCopy := w.worldState
|
||||
playerData := w.playerData()
|
||||
playerPos := w.serverState.PlayerPos.Position
|
||||
spawnPos := cube.Pos{int(playerPos.X()), int(playerPos.Y()), int(playerPos.Z())}
|
||||
|
||||
var img image.Image
|
||||
if w.settings.SaveImage {
|
||||
img = w.mapUI.ToImage()
|
||||
}
|
||||
|
||||
folder := fmt.Sprintf("worlds/%s/%s", w.serverState.Name, worldStateCopy.Name)
|
||||
filename := folder + ".mcworld"
|
||||
|
||||
w.serverState.worldCounter += 1
|
||||
w.Reset()
|
||||
w.wg.Add(1)
|
||||
go func() {
|
||||
defer w.wg.Done()
|
||||
logrus.Infof(locale.Loc("saving_world", locale.Strmap{"Name": worldStateCopy.Name, "Count": len(worldStateCopy.chunks)}))
|
||||
w.gui.Message(messages.SavingWorld{
|
||||
World: &messages.SavedWorld{
|
||||
Name: worldStateCopy.Name,
|
||||
Path: filename,
|
||||
Chunks: len(worldStateCopy.chunks),
|
||||
},
|
||||
})
|
||||
|
||||
// open world
|
||||
os.RemoveAll(folder)
|
||||
os.MkdirAll(folder, 0o777)
|
||||
provider, err := worldStateCopy.Save(folder)
|
||||
if err != nil {
|
||||
logrus.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
err = provider.SaveLocalPlayerData(playerData)
|
||||
if err != nil {
|
||||
logrus.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
// write metadata
|
||||
s := provider.Settings()
|
||||
s.Spawn = spawnPos
|
||||
s.Name = worldStateCopy.Name
|
||||
|
||||
// set gamerules
|
||||
ld := provider.LevelDat()
|
||||
gd := w.proxy.Server.GameData()
|
||||
ld.RandomSeed = int64(gd.WorldSeed)
|
||||
for _, gr := range gd.GameRules {
|
||||
switch gr.Name {
|
||||
case "commandblockoutput":
|
||||
ld.CommandBlockOutput = gr.Value.(bool)
|
||||
case "maxcommandchainlength":
|
||||
ld.MaxCommandChainLength = int32(gr.Value.(uint32))
|
||||
case "commandblocksenabled":
|
||||
ld.CommandsEnabled = gr.Value.(bool)
|
||||
case "dodaylightcycle":
|
||||
ld.DoDayLightCycle = gr.Value.(bool)
|
||||
case "doentitydrops":
|
||||
ld.DoEntityDrops = gr.Value.(bool)
|
||||
case "dofiretick":
|
||||
ld.DoFireTick = gr.Value.(bool)
|
||||
case "domobloot":
|
||||
ld.DoMobLoot = gr.Value.(bool)
|
||||
case "domobspawning":
|
||||
ld.DoMobSpawning = gr.Value.(bool)
|
||||
case "dotiledrops":
|
||||
ld.DoTileDrops = gr.Value.(bool)
|
||||
case "doweathercycle":
|
||||
ld.DoWeatherCycle = gr.Value.(bool)
|
||||
case "drowningdamage":
|
||||
ld.DrowningDamage = gr.Value.(bool)
|
||||
case "doinsomnia":
|
||||
ld.DoInsomnia = gr.Value.(bool)
|
||||
case "falldamage":
|
||||
ld.FallDamage = gr.Value.(bool)
|
||||
case "firedamage":
|
||||
ld.FireDamage = gr.Value.(bool)
|
||||
case "keepinventory":
|
||||
ld.KeepInventory = gr.Value.(bool)
|
||||
case "mobgriefing":
|
||||
ld.MobGriefing = gr.Value.(bool)
|
||||
case "pvp":
|
||||
ld.PVP = gr.Value.(bool)
|
||||
case "showcoordinates":
|
||||
ld.ShowCoordinates = gr.Value.(bool)
|
||||
case "naturalregeneration":
|
||||
ld.NaturalRegeneration = gr.Value.(bool)
|
||||
case "tntexplodes":
|
||||
ld.TNTExplodes = gr.Value.(bool)
|
||||
case "sendcommandfeedback":
|
||||
ld.SendCommandFeedback = gr.Value.(bool)
|
||||
case "randomtickspeed":
|
||||
ld.RandomTickSpeed = int32(gr.Value.(uint32))
|
||||
case "doimmediaterespawn":
|
||||
ld.DoImmediateRespawn = gr.Value.(bool)
|
||||
case "showdeathmessages":
|
||||
ld.ShowDeathMessages = gr.Value.(bool)
|
||||
case "functioncommandlimit":
|
||||
ld.FunctionCommandLimit = int32(gr.Value.(uint32))
|
||||
case "spawnradius":
|
||||
ld.SpawnRadius = int32(gr.Value.(uint32))
|
||||
case "showtags":
|
||||
ld.ShowTags = gr.Value.(bool)
|
||||
case "freezedamage":
|
||||
ld.FreezeDamage = gr.Value.(bool)
|
||||
case "respawnblocksexplode":
|
||||
ld.RespawnBlocksExplode = gr.Value.(bool)
|
||||
case "showbordereffect":
|
||||
ld.ShowBorderEffect = gr.Value.(bool)
|
||||
// todo
|
||||
default:
|
||||
logrus.Warnf(locale.Loc("unknown_gamerule", locale.Strmap{"Name": gr.Name}))
|
||||
}
|
||||
}
|
||||
|
||||
// void world
|
||||
if w.settings.VoidGen {
|
||||
ld.FlatWorldLayers = `{"biome_id":1,"block_layers":[{"block_data":0,"block_id":0,"count":1},{"block_data":0,"block_id":0,"count":2},{"block_data":0,"block_id":0,"count":1}],"encoding_version":3,"structure_options":null}`
|
||||
ld.Generator = 2
|
||||
}
|
||||
|
||||
ld.RandomTickSpeed = 0
|
||||
s.CurrentTick = 0
|
||||
|
||||
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)
|
||||
err = provider.Close()
|
||||
if err != nil {
|
||||
logrus.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
if w.settings.SaveImage {
|
||||
f, _ := os.Create(folder + ".png")
|
||||
png.Encode(f, img)
|
||||
f.Close()
|
||||
}
|
||||
|
||||
w.AddPacks(folder)
|
||||
|
||||
// zip it
|
||||
err = utils.ZipFolder(filename, folder)
|
||||
if err != nil {
|
||||
logrus.Error(err)
|
||||
return
|
||||
}
|
||||
logrus.Info(locale.Loc("saved", locale.Strmap{"Name": filename}))
|
||||
//os.RemoveAll(folder)
|
||||
w.gui.Message(messages.SetUIState(messages.UIStateMain))
|
||||
}()
|
||||
}
|
||||
|
||||
func (w *worldsHandler) AddPacks(folder string) {
|
||||
type dep struct {
|
||||
PackID string `json:"pack_id"`
|
||||
Version [3]int `json:"version"`
|
||||
}
|
||||
addPacksJSON := func(name string, deps []dep) {
|
||||
f, err := os.Create(path.Join(folder, name))
|
||||
if err != nil {
|
||||
logrus.Error(err)
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
if err := json.NewEncoder(f).Encode(deps); err != nil {
|
||||
logrus.Error(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// save behaviourpack
|
||||
if w.bp.HasContent() {
|
||||
name := strings.ReplaceAll(w.serverState.Name, "./", "")
|
||||
name = strings.ReplaceAll(name, "/", "-")
|
||||
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.settings.WithPacks = true
|
||||
}
|
||||
|
||||
// add resource packs
|
||||
if w.settings.WithPacks {
|
||||
packs, err := utils.GetPacks(w.proxy.Server)
|
||||
if err != nil {
|
||||
logrus.Error(err)
|
||||
} else {
|
||||
packNames := make(map[string]int)
|
||||
for _, pack := range packs {
|
||||
packNames[pack.Name()] += 1
|
||||
}
|
||||
|
||||
var rdeps []dep
|
||||
for _, pack := range packs {
|
||||
if pack.Encrypted() && !pack.CanDecrypt() {
|
||||
logrus.Warnf("Cant add %s, it is encrypted", pack.Name())
|
||||
continue
|
||||
}
|
||||
logrus.Infof(locale.Loc("adding_pack", locale.Strmap{"Name": pack.Name()}))
|
||||
|
||||
packName := pack.Name()
|
||||
if packNames[packName] > 1 {
|
||||
packName += "_" + pack.UUID()
|
||||
}
|
||||
packName, _ = filenamify.FilenamifyV2(packName)
|
||||
packFolder := path.Join(folder, "resource_packs", packName)
|
||||
os.MkdirAll(packFolder, 0o755)
|
||||
utils.UnpackZip(pack, int64(pack.Len()), packFolder)
|
||||
|
||||
rdeps = append(rdeps, dep{
|
||||
PackID: pack.Manifest().Header.UUID,
|
||||
Version: pack.Manifest().Header.Version,
|
||||
})
|
||||
}
|
||||
if len(rdeps) > 0 {
|
||||
addPacksJSON("world_resource_packs.json", rdeps)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (w *worldsHandler) OnConnect(err error) bool {
|
||||
w.gui.Message(messages.SetWorldName{
|
||||
WorldName: w.worldState.Name,
|
||||
})
|
||||
w.gui.Message(messages.SetUIState(messages.UIStateMain))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
gd := w.proxy.Server.GameData()
|
||||
w.serverState.ChunkRadius = int(gd.ChunkRadius)
|
||||
w.proxy.ClientWritePacket(&packet.ChunkRadiusUpdated{
|
||||
ChunkRadius: 80,
|
||||
})
|
||||
|
||||
world.InsertCustomItems(gd.Items)
|
||||
for _, ie := range gd.Items {
|
||||
w.bp.AddItem(ie)
|
||||
}
|
||||
|
||||
mapItemID, _ := world.ItemRidByName("minecraft:filled_map")
|
||||
MapItemPacket.Content[0].Stack.ItemType.NetworkID = mapItemID
|
||||
if gd.ServerAuthoritativeInventory {
|
||||
MapItemPacket.Content[0].StackNetworkID = 0xffff + rand.Int31n(0xfff)
|
||||
}
|
||||
|
||||
if len(gd.CustomBlocks) > 0 {
|
||||
logrus.Info(locale.Loc("using_customblocks", nil))
|
||||
for _, be := range gd.CustomBlocks {
|
||||
w.bp.AddBlock(be)
|
||||
}
|
||||
// telling the chunk code what custom blocks there are so it can generate offsets
|
||||
world.InsertCustomBlocks(gd.CustomBlocks)
|
||||
}
|
||||
|
||||
{ // check game version
|
||||
gv := strings.Split(gd.BaseGameVersion, ".")
|
||||
var err error
|
||||
if len(gv) > 1 {
|
||||
var ver int
|
||||
ver, err = strconv.Atoi(gv[1])
|
||||
w.serverState.ispre118 = ver < 18
|
||||
}
|
||||
if err != nil || len(gv) <= 1 {
|
||||
logrus.Info(locale.Loc("guessing_version", nil))
|
||||
}
|
||||
|
||||
dimensionID := gd.Dimension
|
||||
if w.serverState.ispre118 {
|
||||
logrus.Info(locale.Loc("using_under_118", nil))
|
||||
if dimensionID == 0 {
|
||||
dimensionID += 10
|
||||
}
|
||||
}
|
||||
w.worldState.dimension, _ = world.DimensionByID(int(dimensionID))
|
||||
}
|
||||
|
||||
w.proxy.SendMessage(locale.Loc("use_setname", nil))
|
||||
w.mapUI.Start()
|
||||
return true
|
||||
}
|
|
@ -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,101 @@
|
|||
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,
|
||||
)))
|
||||
|
||||
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{})
|
||||
}
|
|
@ -1,87 +1,38 @@
|
|||
package subcommands
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"flag"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/bedrock-tool/bedrocktool/locale"
|
||||
"github.com/bedrock-tool/bedrocktool/utils"
|
||||
|
||||
"github.com/google/subcommands"
|
||||
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func init() {
|
||||
utils.RegisterCommand(&CaptureCMD{})
|
||||
}
|
||||
|
||||
var dump_lock sync.Mutex
|
||||
|
||||
func dump_packet(f io.WriteCloser, toServer bool, payload []byte) {
|
||||
dump_lock.Lock()
|
||||
defer dump_lock.Unlock()
|
||||
f.Write([]byte{0xAA, 0xAA, 0xAA, 0xAA})
|
||||
packet_size := uint32(len(payload))
|
||||
binary.Write(f, binary.LittleEndian, packet_size)
|
||||
binary.Write(f, binary.LittleEndian, toServer)
|
||||
_, err := f.Write(payload)
|
||||
if err != nil {
|
||||
logrus.Error(err)
|
||||
}
|
||||
f.Write([]byte{0xBB, 0xBB, 0xBB, 0xBB})
|
||||
}
|
||||
|
||||
type CaptureCMD struct {
|
||||
server_address string
|
||||
ServerAddress string
|
||||
}
|
||||
|
||||
func (*CaptureCMD) Name() string { return "capture" }
|
||||
func (*CaptureCMD) Synopsis() string { return "capture packets in a pcap file" }
|
||||
|
||||
func (p *CaptureCMD) SetFlags(f *flag.FlagSet) {
|
||||
f.StringVar(&p.server_address, "address", "", "remote server address")
|
||||
func (*CaptureCMD) Synopsis() string { return locale.Loc("capture_synopsis", nil) }
|
||||
func (c *CaptureCMD) SetFlags(f *flag.FlagSet) {
|
||||
f.StringVar(&c.ServerAddress, "address", "", "remote server address")
|
||||
}
|
||||
|
||||
func (c *CaptureCMD) Usage() string {
|
||||
return c.Name() + ": " + c.Synopsis() + "\n" + utils.SERVER_ADDRESS_HELP
|
||||
}
|
||||
|
||||
func (c *CaptureCMD) Execute(ctx context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
|
||||
address, hostname, err := utils.ServerInput(ctx, c.server_address)
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
return 1
|
||||
}
|
||||
|
||||
fio, err := os.Create(hostname + "-" + time.Now().Format("2006-01-02_15-04-05") + ".pcap2")
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
return 1
|
||||
}
|
||||
defer fio.Close()
|
||||
|
||||
proxy := utils.NewProxy(logrus.StandardLogger())
|
||||
proxy.PacketFunc = func(header packet.Header, payload []byte, src, dst net.Addr) {
|
||||
from_client := src.String() == proxy.Client.LocalAddr().String()
|
||||
|
||||
buf := bytes.NewBuffer(nil)
|
||||
header.Write(buf)
|
||||
buf.Write(payload)
|
||||
dump_packet(fio, from_client, buf.Bytes())
|
||||
}
|
||||
|
||||
err = proxy.Run(ctx, address)
|
||||
time.Sleep(2 * time.Second)
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
func (c *CaptureCMD) Execute(ctx context.Context, ui utils.UI) error {
|
||||
address, hostname, err := utils.ServerInput(ctx, c.ServerAddress)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
proxy, err := utils.NewProxy()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
proxy.AlwaysGetPacks = true
|
||||
utils.Options.Capture = true
|
||||
return proxy.Run(ctx, address, hostname)
|
||||
}
|
||||
|
|
|
@ -3,71 +3,36 @@ package subcommands
|
|||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/bedrock-tool/bedrocktool/handlers"
|
||||
"github.com/bedrock-tool/bedrocktool/locale"
|
||||
"github.com/bedrock-tool/bedrocktool/utils"
|
||||
|
||||
"github.com/google/subcommands"
|
||||
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type ChatLogCMD struct {
|
||||
Address string
|
||||
verbose bool
|
||||
ServerAddress string
|
||||
Verbose bool
|
||||
}
|
||||
|
||||
func (*ChatLogCMD) Name() string { return "chat-log" }
|
||||
func (*ChatLogCMD) Synopsis() string { return "logs chat to a file" }
|
||||
|
||||
func (*ChatLogCMD) Synopsis() string { return locale.Loc("chat_log_synopsis", nil) }
|
||||
func (c *ChatLogCMD) SetFlags(f *flag.FlagSet) {
|
||||
f.StringVar(&c.Address, "address", "", "remote server address")
|
||||
f.BoolVar(&c.verbose, "v", false, "verbose")
|
||||
f.StringVar(&c.ServerAddress, "address", "", "remote server address")
|
||||
f.BoolVar(&c.Verbose, "v", false, "verbose")
|
||||
}
|
||||
|
||||
func (c *ChatLogCMD) Usage() string {
|
||||
return c.Name() + ": " + c.Synopsis() + "\n" + utils.SERVER_ADDRESS_HELP
|
||||
}
|
||||
|
||||
func (c *ChatLogCMD) Execute(ctx context.Context, flags *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
|
||||
address, hostname, err := utils.ServerInput(ctx, c.Address)
|
||||
func (c *ChatLogCMD) Execute(ctx context.Context, ui utils.UI) error {
|
||||
address, hostname, err := utils.ServerInput(ctx, c.ServerAddress)
|
||||
if err != nil {
|
||||
logrus.Error(err)
|
||||
return 1
|
||||
return err
|
||||
}
|
||||
|
||||
filename := fmt.Sprintf("%s_%s_chat.log", hostname, time.Now().Format("2006-01-02_15-04-05_Z07"))
|
||||
f, err := os.Create(filename)
|
||||
proxy, err := utils.NewProxy()
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
proxy := utils.NewProxy(logrus.StandardLogger())
|
||||
proxy.PacketCB = func(pk packet.Packet, proxy *utils.ProxyContext, toServer bool) (packet.Packet, error) {
|
||||
if text, ok := pk.(*packet.Text); ok {
|
||||
logLine := text.Message
|
||||
if c.verbose {
|
||||
logLine += fmt.Sprintf(" (TextType: %d | XUID: %s | PlatformChatID: %s)", text.TextType, text.XUID, text.PlatformChatID)
|
||||
}
|
||||
f.WriteString(fmt.Sprintf("[%s] ", time.Now().Format(time.RFC3339)))
|
||||
logrus.Info(logLine)
|
||||
if toServer {
|
||||
f.WriteString("SENT: ")
|
||||
}
|
||||
f.WriteString(logLine + "\n")
|
||||
}
|
||||
return pk, nil
|
||||
}
|
||||
|
||||
if err := proxy.Run(ctx, address); err != nil {
|
||||
logrus.Error(err)
|
||||
return 1
|
||||
}
|
||||
|
||||
return 0
|
||||
proxy.AddHandler(handlers.NewChatLogger())
|
||||
return proxy.Run(ctx, address, hostname)
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
|
|
@ -3,61 +3,36 @@ package subcommands
|
|||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"strings"
|
||||
|
||||
seconduser "github.com/bedrock-tool/bedrocktool/handlers/second-user"
|
||||
"github.com/bedrock-tool/bedrocktool/locale"
|
||||
"github.com/bedrock-tool/bedrocktool/utils"
|
||||
|
||||
"github.com/google/subcommands"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type DebugProxyCMD struct {
|
||||
Address string
|
||||
filter string
|
||||
ServerAddress string
|
||||
}
|
||||
|
||||
func (*DebugProxyCMD) Name() string { return "debug-proxy" }
|
||||
func (*DebugProxyCMD) Synopsis() string { return "verbose debug packets" }
|
||||
|
||||
func (*DebugProxyCMD) Synopsis() string { return locale.Loc("debug_proxy_synopsis", nil) }
|
||||
func (c *DebugProxyCMD) SetFlags(f *flag.FlagSet) {
|
||||
f.StringVar(&c.Address, "address", "", "remote server address")
|
||||
f.StringVar(&c.filter, "filter", "", "packets to not show")
|
||||
f.StringVar(&c.ServerAddress, "address", "", locale.Loc("remote_address", nil))
|
||||
}
|
||||
|
||||
func (c *DebugProxyCMD) Usage() string {
|
||||
return c.Name() + ": " + c.Synopsis() + "\n" + utils.SERVER_ADDRESS_HELP
|
||||
}
|
||||
|
||||
func (c *DebugProxyCMD) Execute(ctx context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
|
||||
address, _, err := utils.ServerInput(ctx, c.Address)
|
||||
func (c *DebugProxyCMD) Execute(ctx context.Context, ui utils.UI) error {
|
||||
address, hostname, err := utils.ServerInput(ctx, c.ServerAddress)
|
||||
if err != nil {
|
||||
logrus.Error(err)
|
||||
return 1
|
||||
return err
|
||||
}
|
||||
|
||||
utils.G_debug = true
|
||||
utils.Options.Debug = true
|
||||
|
||||
filters := strings.Split(c.filter, ",")
|
||||
if len(filters) > 0 {
|
||||
for _, v := range filters {
|
||||
if len(v) == 0 {
|
||||
continue
|
||||
}
|
||||
if string(v[0]) == "*" {
|
||||
v = v[1:]
|
||||
}
|
||||
v = strings.TrimPrefix(v, "packet.")
|
||||
v = "packet." + v
|
||||
utils.ExtraVerbose = append(utils.ExtraVerbose, v)
|
||||
}
|
||||
proxy, err := utils.NewProxy()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
proxy := utils.NewProxy(logrus.StandardLogger())
|
||||
if err := proxy.Run(ctx, address); err != nil {
|
||||
logrus.Error(err)
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
proxy.AddHandler(seconduser.NewSecondUser())
|
||||
return proxy.Run(ctx, address, hostname)
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
//go:build false
|
||||
|
||||
package subcommands
|
||||
|
||||
import (
|
||||
|
@ -7,8 +9,10 @@ import (
|
|||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/bedrock-tool/bedrocktool/locale"
|
||||
"github.com/bedrock-tool/bedrocktool/utils"
|
||||
|
||||
"github.com/df-mc/dragonfly/server/entity"
|
||||
"github.com/df-mc/dragonfly/server/world/mcdb"
|
||||
"github.com/df-mc/goleveldb/leveldb/opt"
|
||||
"github.com/google/subcommands"
|
||||
|
@ -22,7 +26,7 @@ type MergeCMD struct {
|
|||
}
|
||||
|
||||
func (*MergeCMD) Name() string { return "merge" }
|
||||
func (*MergeCMD) Synopsis() string { return "merge 2 or more worlds" }
|
||||
func (*MergeCMD) Synopsis() string { return locale.Loc("merge_synopsis", nil) }
|
||||
|
||||
func (c *MergeCMD) SetFlags(f *flag.FlagSet) {
|
||||
f.BoolVar(&c.legacy, "legacy", false, "if the worlds are before 1.18")
|
||||
|
@ -34,91 +38,91 @@ func (c *MergeCMD) Usage() string {
|
|||
|
||||
func (c *MergeCMD) Execute(ctx context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
|
||||
if f.NArg() == 0 {
|
||||
logrus.Error("you need to specify 1 or more worlds")
|
||||
logrus.Error(locale.Loc("need_to_specify_multiple_worlds", nil))
|
||||
return 1
|
||||
}
|
||||
c.worlds = f.Args()
|
||||
out_name := c.worlds[0] + "-merged"
|
||||
outName := c.worlds[0] + "-merged"
|
||||
|
||||
prov_out, err := mcdb.New(logrus.StandardLogger(), out_name, opt.DefaultCompression)
|
||||
provOut, err := mcdb.New(logrus.StandardLogger(), outName, opt.DefaultCompression)
|
||||
if err != nil {
|
||||
logrus.Errorf("failed to open output %s", err)
|
||||
logrus.Errorf(locale.Loc("failed_to_open_output", locale.Strmap{"Err": err}))
|
||||
return 1
|
||||
}
|
||||
|
||||
for i, world_name := range c.worlds {
|
||||
for i, worldName := range c.worlds {
|
||||
first := i == 0
|
||||
logrus.Infof("Adding %s", world_name)
|
||||
s, err := os.Stat(world_name)
|
||||
logrus.Infof(locale.Loc("adding_world", locale.Strmap{"World": worldName}))
|
||||
s, err := os.Stat(worldName)
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
logrus.Fatalf("%s not found", world_name)
|
||||
logrus.Fatalf(locale.Loc("not_found", locale.Strmap{"Name": worldName}), worldName)
|
||||
}
|
||||
if !s.IsDir() { // if its a zip temporarily unpack it to read it
|
||||
f, _ := os.Open(world_name)
|
||||
world_name += "_unpack"
|
||||
utils.UnpackZip(f, s.Size(), world_name)
|
||||
f, _ := os.Open(worldName)
|
||||
worldName += "_unpack"
|
||||
utils.UnpackZip(f, s.Size(), worldName)
|
||||
}
|
||||
// merge it into the state
|
||||
err = c.merge_worlds(prov_out, world_name, first)
|
||||
err = c.mergeWorlds(provOut, worldName, first)
|
||||
if err != nil {
|
||||
logrus.Errorf("%s %s", world_name, err)
|
||||
logrus.Errorf("%s %s", worldName, err)
|
||||
return 1
|
||||
}
|
||||
if !s.IsDir() { // remove temp folder again
|
||||
os.RemoveAll(world_name)
|
||||
os.RemoveAll(worldName)
|
||||
}
|
||||
}
|
||||
|
||||
if err = prov_out.Close(); err != nil {
|
||||
if err = provOut.Close(); err != nil {
|
||||
logrus.Error(err)
|
||||
return 1
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
if err := utils.ZipFolder(out_name+".mcworld", out_name); err != nil {
|
||||
if err := utils.ZipFolder(outName+".mcworld", outName); err != nil {
|
||||
logrus.Infof("zipping: %s", err)
|
||||
return 1
|
||||
}
|
||||
|
||||
os.RemoveAll(out_name)
|
||||
os.RemoveAll(outName)
|
||||
return 0
|
||||
}
|
||||
|
||||
func (c *MergeCMD) merge_worlds(prov_out *mcdb.Provider, folder string, first bool) error {
|
||||
prov_in, err := mcdb.New(logrus.StandardLogger(), folder, opt.DefaultCompression)
|
||||
func (c *MergeCMD) mergeWorlds(provOut *mcdb.Provider, folder string, first bool) error {
|
||||
provIn, err := mcdb.New(logrus.StandardLogger(), folder, opt.DefaultCompression)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
count := 0
|
||||
existing := prov_out.Chunks(c.legacy)
|
||||
new := prov_in.Chunks(c.legacy)
|
||||
existing := provOut.Chunks(c.legacy)
|
||||
new := provIn.Chunks(c.legacy)
|
||||
for i := range new {
|
||||
if _, ok := existing[i]; !ok {
|
||||
d := i.D
|
||||
// chunks
|
||||
ch, _, err := prov_in.LoadChunk(i.P, d)
|
||||
ch, _, err := provIn.LoadChunk(i.P, d)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := prov_out.SaveChunk(i.P, ch, i.D); err != nil {
|
||||
if err := provOut.SaveChunk(i.P, ch, i.D); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// blockNBT
|
||||
n, err := prov_in.LoadBlockNBT(i.P, i.D)
|
||||
n, err := provIn.LoadBlockNBT(i.P, i.D)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := prov_out.SaveBlockNBT(i.P, n, i.D); err != nil {
|
||||
if err := provOut.SaveBlockNBT(i.P, n, i.D); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// entities
|
||||
entities, err := prov_in.LoadEntities(i.P, i.D)
|
||||
entities, err := provIn.LoadEntities(i.P, i.D, entity.DefaultRegistry)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := prov_out.SaveEntities(i.P, entities, i.D); err != nil {
|
||||
if err := provOut.SaveEntities(i.P, entities, i.D); err != nil {
|
||||
return err
|
||||
}
|
||||
count += 1
|
||||
|
@ -127,9 +131,9 @@ func (c *MergeCMD) merge_worlds(prov_out *mcdb.Provider, folder string, first bo
|
|||
|
||||
if first {
|
||||
logrus.Debug("Applying Settings and level.dat")
|
||||
prov_out.SaveSettings(prov_in.Settings())
|
||||
out_ld := prov_out.LevelDat()
|
||||
copier.Copy(out_ld, prov_in.LevelDat())
|
||||
provOut.SaveSettings(provIn.Settings())
|
||||
outLd := provOut.LevelDat()
|
||||
copier.Copy(outLd, provIn.LevelDat())
|
||||
}
|
||||
logrus.Infof("Added: %d", count)
|
||||
return nil
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
package subcommands
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
|
||||
"github.com/bedrock-tool/bedrocktool/utils"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type RealmAddressCMD struct {
|
||||
realm string
|
||||
}
|
||||
|
||||
func (*RealmAddressCMD) Name() string { return "realm-address" }
|
||||
func (*RealmAddressCMD) Synopsis() string { return "gets realms address" }
|
||||
func (c *RealmAddressCMD) SetFlags(f *flag.FlagSet) {
|
||||
f.StringVar(&c.realm, "realm", "", "realm name <name:id> or just name")
|
||||
}
|
||||
|
||||
func (c *RealmAddressCMD) Execute(ctx context.Context, ui utils.UI) error {
|
||||
address, _, err := utils.ServerInput(ctx, "realm:"+c.realm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logrus.Infof("Address: %s", address)
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
utils.RegisterCommand(&RealmAddressCMD{})
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
package subcommands
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
|
||||
"github.com/bedrock-tool/bedrocktool/locale"
|
||||
"github.com/bedrock-tool/bedrocktool/utils"
|
||||
)
|
||||
|
||||
type RealmListCMD struct{}
|
||||
|
||||
func (*RealmListCMD) Name() string { return "list-realms" }
|
||||
func (*RealmListCMD) Synopsis() string { return locale.Loc("list_realms_synopsis", nil) }
|
||||
func (c *RealmListCMD) SetFlags(f *flag.FlagSet) {}
|
||||
func (c *RealmListCMD) Execute(ctx context.Context, ui utils.UI) error {
|
||||
realms, err := utils.GetRealmsAPI().Realms(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, realm := range realms {
|
||||
fmt.Println(locale.Loc("realm_list_line", locale.Strmap{"Name": realm.Name, "Id": realm.ID}))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
utils.RegisterCommand(&RealmListCMD{})
|
||||
}
|
|
@ -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{})
|
||||
}
|
|
@ -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,305 +1,59 @@
|
|||
package skins
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/png"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/bedrock-tool/bedrocktool/handlers"
|
||||
"github.com/bedrock-tool/bedrocktool/locale"
|
||||
"github.com/bedrock-tool/bedrocktool/ui/messages"
|
||||
"github.com/bedrock-tool/bedrocktool/utils"
|
||||
|
||||
"github.com/flytam/filenamify"
|
||||
"github.com/google/subcommands"
|
||||
"github.com/sandertv/gophertunnel/minecraft"
|
||||
"github.com/sandertv/gophertunnel/minecraft/protocol"
|
||||
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type Skin struct {
|
||||
protocol.Skin
|
||||
}
|
||||
|
||||
type SkinMeta struct {
|
||||
SkinID string
|
||||
PlayFabID string
|
||||
PremiumSkin bool
|
||||
PersonaSkin bool
|
||||
CapeID string
|
||||
SkinColour string
|
||||
ArmSize string
|
||||
Trusted bool
|
||||
PersonaPieces []protocol.PersonaPiece
|
||||
}
|
||||
|
||||
// WriteGeometry writes the geometry json for the skin to output_path
|
||||
func (skin *Skin) WriteGeometry(output_path string) error {
|
||||
f, err := os.Create(output_path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write Geometry %s: %s", output_path, err)
|
||||
}
|
||||
defer f.Close()
|
||||
io.Copy(f, bytes.NewReader(skin.SkinGeometry))
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteCape writes the cape as a png at output_path
|
||||
func (skin *Skin) WriteCape(output_path string) error {
|
||||
f, err := os.Create(output_path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write Cape %s: %s", output_path, err)
|
||||
}
|
||||
defer f.Close()
|
||||
cape_tex := image.NewRGBA(image.Rect(0, 0, int(skin.CapeImageWidth), int(skin.CapeImageHeight)))
|
||||
cape_tex.Pix = skin.CapeData
|
||||
|
||||
if err := png.Encode(f, cape_tex); err != nil {
|
||||
return fmt.Errorf("error writing skin: %s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteAnimations writes skin animations to the folder
|
||||
func (skin *Skin) WriteAnimations(output_path string) error {
|
||||
logrus.Warnf("%s has animations (unimplemented)", output_path)
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteTexture writes the main texture for this skin to a file
|
||||
func (skin *Skin) WriteTexture(output_path string) error {
|
||||
f, err := os.Create(output_path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error writing Texture: %s", err)
|
||||
}
|
||||
defer f.Close()
|
||||
skin_tex := image.NewRGBA(image.Rect(0, 0, int(skin.SkinImageWidth), int(skin.SkinImageHeight)))
|
||||
skin_tex.Pix = skin.SkinData
|
||||
|
||||
if err := png.Encode(f, skin_tex); err != nil {
|
||||
return fmt.Errorf("error writing Texture: %s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (skin *Skin) WriteTint(output_path string) error {
|
||||
f, err := os.Create(output_path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write Tint %s: %s", output_path, err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
err = json.NewEncoder(f).Encode(skin.PieceTintColours)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write Tint %s: %s", output_path, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (skin *Skin) WriteMeta(output_path string) error {
|
||||
f, err := os.Create(output_path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write Tint %s: %s", output_path, err)
|
||||
}
|
||||
defer f.Close()
|
||||
d, err := json.MarshalIndent(SkinMeta{
|
||||
skin.SkinID,
|
||||
skin.PlayFabID,
|
||||
skin.PremiumSkin,
|
||||
skin.PersonaSkin,
|
||||
skin.CapeID,
|
||||
skin.SkinColour,
|
||||
skin.ArmSize,
|
||||
skin.Trusted,
|
||||
skin.PersonaPieces,
|
||||
}, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f.Write(d)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (skin *Skin) Complex() bool {
|
||||
have_geometry, have_cape, have_animations, have_tint := len(skin.SkinGeometry) > 0, len(skin.CapeData) > 0, len(skin.Animations) > 0, len(skin.PieceTintColours) > 0
|
||||
return have_geometry || have_cape || have_animations || have_tint
|
||||
}
|
||||
|
||||
// Write writes all data for this skin to a folder
|
||||
func (skin *Skin) Write(output_path, name string) error {
|
||||
name, _ = filenamify.FilenamifyV2(name)
|
||||
skin_dir := path.Join(output_path, name)
|
||||
|
||||
have_geometry, have_cape, have_animations, have_tint := len(skin.SkinGeometry) > 0, len(skin.CapeData) > 0, len(skin.Animations) > 0, len(skin.PieceTintColours) > 0
|
||||
os.MkdirAll(skin_dir, 0o755)
|
||||
if have_geometry {
|
||||
if err := skin.WriteGeometry(path.Join(skin_dir, "geometry.json")); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if have_cape {
|
||||
if err := skin.WriteCape(path.Join(skin_dir, "cape.png")); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if have_animations {
|
||||
if err := skin.WriteAnimations(skin_dir); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if have_tint {
|
||||
if err := skin.WriteTint(path.Join(skin_dir, "tint.json")); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := skin.WriteMeta(path.Join(skin_dir, "metadata.json")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return skin.WriteTexture(skin_dir + "/skin.png")
|
||||
}
|
||||
|
||||
// puts the skin at output_path if the filter matches it
|
||||
// internally converts the struct so it can use the extra methods
|
||||
func write_skin(output_path, name string, skin *protocol.Skin) {
|
||||
logrus.Infof("Writing skin for %s\n", name)
|
||||
_skin := &Skin{*skin}
|
||||
if err := _skin.Write(output_path, name); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error writing skin: %s\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
skin_players = make(map[string]string)
|
||||
skin_player_counts = make(map[string]int)
|
||||
)
|
||||
|
||||
func popup_skin_saved(conn *minecraft.Conn, name string) {
|
||||
if conn != nil {
|
||||
(&utils.ProxyContext{Client: conn}).SendPopup(fmt.Sprintf("%s Skin was Saved", name))
|
||||
}
|
||||
}
|
||||
|
||||
func skin_meta_get_skinid(path string) string {
|
||||
cont, err := os.ReadFile(fmt.Sprintf("%s/metadata.json", path))
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
var meta SkinMeta
|
||||
if err := json.Unmarshal(cont, &meta); err != nil {
|
||||
return ""
|
||||
}
|
||||
return meta.SkinID
|
||||
}
|
||||
|
||||
func save_player_skin(conn *minecraft.Conn, out_path, player_name string, skin *protocol.Skin) {
|
||||
count := skin_player_counts[player_name]
|
||||
if count > 0 {
|
||||
meta_id := skin_meta_get_skinid(fmt.Sprintf("%s/%s_%d", out_path, player_name, count-1))
|
||||
if meta_id == skin.SkinID {
|
||||
return // skin same as before
|
||||
}
|
||||
}
|
||||
|
||||
skin_player_counts[player_name]++
|
||||
count++
|
||||
write_skin(out_path, fmt.Sprintf("%s_%d", player_name, count), skin)
|
||||
popup_skin_saved(conn, player_name)
|
||||
}
|
||||
|
||||
func process_packet_skins(conn *minecraft.Conn, out_path string, pk packet.Packet, filter string, only_if_geom bool) {
|
||||
switch _pk := pk.(type) {
|
||||
case *packet.PlayerSkin:
|
||||
player_name := skin_players[_pk.UUID.String()]
|
||||
if player_name == "" {
|
||||
player_name = _pk.UUID.String()
|
||||
}
|
||||
if !strings.HasPrefix(player_name, filter) {
|
||||
return
|
||||
}
|
||||
if only_if_geom && len(_pk.Skin.SkinGeometry) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
save_player_skin(conn, out_path, player_name, &_pk.Skin)
|
||||
case *packet.PlayerList:
|
||||
if _pk.ActionType == 1 { // remove
|
||||
return
|
||||
}
|
||||
for _, player := range _pk.Entries {
|
||||
player_name := utils.CleanupName(player.Username)
|
||||
if player_name == "" {
|
||||
player_name = player.UUID.String()
|
||||
}
|
||||
if !strings.HasPrefix(player_name, filter) {
|
||||
return
|
||||
}
|
||||
if only_if_geom && len(player.Skin.SkinGeometry) == 0 {
|
||||
return
|
||||
}
|
||||
skin_players[player.UUID.String()] = player_name
|
||||
save_player_skin(conn, out_path, player_name, &player.Skin)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type SkinCMD struct {
|
||||
server_address string
|
||||
filter string
|
||||
ServerAddress string
|
||||
Filter string
|
||||
NoProxy bool
|
||||
}
|
||||
|
||||
func (*SkinCMD) Name() string { return "skins" }
|
||||
func (*SkinCMD) Synopsis() string { return "download all skins from players on a server" }
|
||||
func (*SkinCMD) Synopsis() string { return locale.Loc("skins_synopsis", nil) }
|
||||
|
||||
func (c *SkinCMD) SetFlags(f *flag.FlagSet) {
|
||||
f.StringVar(&c.server_address, "address", "", "remote server address")
|
||||
f.StringVar(&c.filter, "filter", "", "player name filter prefix")
|
||||
f.StringVar(&c.ServerAddress, "address", "", locale.Loc("remote_address", nil))
|
||||
f.StringVar(&c.Filter, "filter", "", locale.Loc("name_prefix", nil))
|
||||
f.BoolVar(&c.NoProxy, "no-proxy", false, "use headless version")
|
||||
}
|
||||
|
||||
func (c *SkinCMD) Usage() string {
|
||||
return c.Name() + ": " + c.Synopsis() + "\n"
|
||||
}
|
||||
|
||||
func (c *SkinCMD) Execute(ctx context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
|
||||
address, hostname, err := utils.ServerInput(ctx, c.server_address)
|
||||
func (c *SkinCMD) Execute(ctx context.Context, ui utils.UI) error {
|
||||
address, hostname, err := utils.ServerInput(ctx, c.ServerAddress)
|
||||
if err != nil {
|
||||
fmt.Fprint(os.Stderr, err)
|
||||
return 1
|
||||
return err
|
||||
}
|
||||
|
||||
serverConn, err := utils.ConnectServer(ctx, address, nil, false, nil)
|
||||
if err != nil {
|
||||
fmt.Fprint(os.Stderr, err)
|
||||
return 1
|
||||
}
|
||||
defer serverConn.Close()
|
||||
|
||||
out_path := fmt.Sprintf("skins/%s", hostname)
|
||||
|
||||
if err := serverConn.DoSpawnContext(ctx); err != nil {
|
||||
fmt.Fprint(os.Stderr, err)
|
||||
return 1
|
||||
}
|
||||
|
||||
logrus.Info("Connected")
|
||||
logrus.Info("Press ctrl+c to exit")
|
||||
|
||||
os.MkdirAll(out_path, 0o755)
|
||||
|
||||
for {
|
||||
pk, err := serverConn.ReadPacket()
|
||||
if err != nil {
|
||||
return 1
|
||||
}
|
||||
process_packet_skins(nil, out_path, pk, c.filter, false)
|
||||
proxy, _ := utils.NewProxy()
|
||||
proxy.WithClient = !c.NoProxy
|
||||
proxy.AddHandler(handlers.NewSkinSaver(func(sa handlers.SkinAdd) {
|
||||
ui.Message(messages.NewSkin{
|
||||
PlayerName: sa.PlayerName,
|
||||
Skin: sa.Skin,
|
||||
})
|
||||
}))
|
||||
proxy.AddHandler(&utils.ProxyHandler{
|
||||
Name: "Skin CMD",
|
||||
OnClientConnect: func(conn minecraft.IConn) {
|
||||
ui.Message(messages.SetUIState(messages.UIStateConnecting))
|
||||
},
|
||||
})
|
||||
|
||||
if proxy.WithClient {
|
||||
ui.Message(messages.SetUIState(messages.UIStateConnect))
|
||||
} else {
|
||||
ui.Message(messages.SetUIState(messages.UIStateConnecting))
|
||||
}
|
||||
err = proxy.Run(ctx, address, hostname)
|
||||
return err
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
|
|
@ -1,45 +1,38 @@
|
|||
// Package subcommands ...
|
||||
package subcommands
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
|
||||
"github.com/bedrock-tool/bedrocktool/locale"
|
||||
"github.com/bedrock-tool/bedrocktool/utils"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/google/subcommands"
|
||||
)
|
||||
|
||||
type UpdateCMD struct{}
|
||||
|
||||
func (*UpdateCMD) Name() string { return "update" }
|
||||
func (*UpdateCMD) Synopsis() string { return "self updates to latest version" }
|
||||
|
||||
func (*UpdateCMD) Name() string { return "update" }
|
||||
func (*UpdateCMD) Synopsis() string { return locale.Loc("update_synopsis", nil) }
|
||||
func (c *UpdateCMD) SetFlags(f *flag.FlagSet) {}
|
||||
|
||||
func (c *UpdateCMD) Usage() string {
|
||||
return c.Name() + ": " + c.Synopsis() + "\n"
|
||||
}
|
||||
|
||||
func (c *UpdateCMD) Execute(ctx context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
|
||||
func (c *UpdateCMD) Execute(ctx context.Context, ui utils.UI) error {
|
||||
newVersion, err := utils.Updater.UpdateAvailable()
|
||||
if err != nil {
|
||||
logrus.Error(err)
|
||||
return 1
|
||||
return err
|
||||
}
|
||||
if newVersion == "" {
|
||||
logrus.Info("No Updates available.")
|
||||
return 0
|
||||
logrus.Info(locale.Loc("no_update", nil))
|
||||
return nil
|
||||
}
|
||||
logrus.Infof("Updating to %s", newVersion)
|
||||
logrus.Infof(locale.Loc("updating", locale.Strmap{"Version": newVersion}))
|
||||
|
||||
if err := utils.Updater.Update(); err != nil {
|
||||
logrus.Error(err)
|
||||
return 1
|
||||
return err
|
||||
}
|
||||
|
||||
logrus.Infof("Updated!")
|
||||
return 0
|
||||
logrus.Infof(locale.Loc("updated", nil))
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
|
Binary file not shown.
Binary file not shown.
Before Width: | Height: | Size: 513 B |
|
@ -1,99 +0,0 @@
|
|||
package world
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
|
||||
"github.com/bedrock-tool/bedrocktool/utils"
|
||||
|
||||
"github.com/df-mc/dragonfly/server/block"
|
||||
"github.com/df-mc/dragonfly/server/block/cube"
|
||||
"github.com/df-mc/dragonfly/server/world"
|
||||
"github.com/df-mc/dragonfly/server/world/chunk"
|
||||
)
|
||||
|
||||
func blockColorAt(c *chunk.Chunk, x uint8, y int16, z uint8) (blockColor color.RGBA) {
|
||||
blockColor = color.RGBA{255, 0, 255, 255}
|
||||
rid := c.Block(x, y, z, 0)
|
||||
if rid == 0 && y == 0 { // void
|
||||
blockColor = color.RGBA{0, 0, 0, 255}
|
||||
} else {
|
||||
b, found := world.BlockByRuntimeID(rid)
|
||||
if found {
|
||||
if _, ok := b.(block.Water); ok {
|
||||
y2 := c.HeightMap().At(x, z)
|
||||
depth := y - y2
|
||||
if depth > 0 {
|
||||
blockColor = blockColorAt(c, x, y2, z)
|
||||
}
|
||||
|
||||
bw := (&block.Water{}).Color()
|
||||
bw.A = uint8(utils.Clamp(int(150+depth*7), 255))
|
||||
blockColor = utils.BlendColors(blockColor, bw)
|
||||
blockColor.R -= uint8(depth * 2)
|
||||
blockColor.G -= uint8(depth * 2)
|
||||
blockColor.B -= uint8(depth * 2)
|
||||
} else {
|
||||
blockColor = b.Color()
|
||||
}
|
||||
}
|
||||
/*
|
||||
if blockColor.R == 0 || blockColor.R == 255 && blockColor.B == 255 {
|
||||
name, nbt := b.EncodeBlock()
|
||||
fmt.Printf("unknown color %d %s %s %s\n", rid, reflect.TypeOf(b), name, nbt)
|
||||
b.Color()
|
||||
}
|
||||
*/
|
||||
}
|
||||
return blockColor
|
||||
}
|
||||
|
||||
func chunkGetColorAt(c *chunk.Chunk, x uint8, y int16, z uint8) color.RGBA {
|
||||
p := cube.Pos{int(x), int(y), int(z)}
|
||||
have_up := false
|
||||
p.Side(cube.FaceUp).Neighbours(func(neighbour cube.Pos) {
|
||||
if neighbour.X() < 0 || neighbour.X() >= 16 || neighbour.Z() < 0 || neighbour.Z() >= 16 || neighbour.Y() > c.Range().Max() {
|
||||
return
|
||||
}
|
||||
if !have_up {
|
||||
block_rid := c.Block(uint8(neighbour[0]), int16(neighbour[1]), uint8(neighbour[2]), 0)
|
||||
if block_rid > 0 {
|
||||
b, found := world.BlockByRuntimeID(block_rid)
|
||||
if found {
|
||||
if _, ok := b.(block.Air); !ok {
|
||||
have_up = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}, cube.Range{int(y + 1), int(y + 1)})
|
||||
|
||||
col := blockColorAt(c, x, y, z)
|
||||
|
||||
if have_up {
|
||||
if col.R > 10 {
|
||||
col.R -= 10
|
||||
}
|
||||
if col.G > 10 {
|
||||
col.G -= 10
|
||||
}
|
||||
if col.B > 10 {
|
||||
col.B -= 10
|
||||
}
|
||||
}
|
||||
return col
|
||||
}
|
||||
|
||||
func Chunk2Img(c *chunk.Chunk) *image.RGBA {
|
||||
img := image.NewRGBA(image.Rect(0, 0, 16, 16))
|
||||
hm := c.HeightMapWithWater()
|
||||
|
||||
for x := uint8(0); x < 16; x++ {
|
||||
for z := uint8(0); z < 16; z++ {
|
||||
height := hm.At(x, z)
|
||||
col := chunkGetColorAt(c, x, height, z)
|
||||
img.SetRGBA(int(x), int(z), col)
|
||||
}
|
||||
}
|
||||
return img
|
||||
}
|
|
@ -1,237 +0,0 @@
|
|||
package world
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"image"
|
||||
"image/draw"
|
||||
"math"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/bedrock-tool/bedrocktool/utils"
|
||||
"golang.design/x/lockfree"
|
||||
|
||||
"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"
|
||||
"golang.org/x/image/bmp"
|
||||
)
|
||||
|
||||
const VIEW_MAP_ID = 0x424242
|
||||
|
||||
// packet to tell the client that it has a map with id 0x424242 in the offhand
|
||||
var MAP_ITEM_PACKET packet.InventoryContent = packet.InventoryContent{
|
||||
WindowID: 119,
|
||||
Content: []protocol.ItemInstance{
|
||||
{
|
||||
StackNetworkID: 1,
|
||||
Stack: protocol.ItemStack{
|
||||
ItemType: protocol.ItemType{
|
||||
NetworkID: 420,
|
||||
MetadataValue: 0,
|
||||
},
|
||||
BlockRuntimeID: 0,
|
||||
Count: 1,
|
||||
NBTData: map[string]interface{}{
|
||||
"map_uuid": int64(VIEW_MAP_ID),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func (m *MapUI) get_bounds() (min, max protocol.ChunkPos) {
|
||||
// get the chunk coord bounds
|
||||
for _ch := range m.renderedChunks {
|
||||
if _ch.X() < min.X() {
|
||||
min[0] = _ch.X()
|
||||
}
|
||||
if _ch.Z() < min.Z() {
|
||||
min[1] = _ch.Z()
|
||||
}
|
||||
if _ch.X() > max.X() {
|
||||
max[0] = _ch.X()
|
||||
}
|
||||
if _ch.Z() > max.Z() {
|
||||
max[1] = _ch.Z()
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type RenderElem struct {
|
||||
pos protocol.ChunkPos
|
||||
ch *chunk.Chunk
|
||||
}
|
||||
|
||||
type MapUI struct {
|
||||
img *image.RGBA // rendered image
|
||||
zoomLevel int // pixels per chunk
|
||||
renderQueue *lockfree.Queue
|
||||
renderedChunks map[protocol.ChunkPos]*image.RGBA // prerendered chunks
|
||||
needRedraw bool // when the map has updated this is true
|
||||
|
||||
ticker *time.Ticker
|
||||
w *WorldState
|
||||
}
|
||||
|
||||
func NewMapUI(w *WorldState) *MapUI {
|
||||
m := &MapUI{
|
||||
img: image.NewRGBA(image.Rect(0, 0, 128, 128)),
|
||||
zoomLevel: 16,
|
||||
renderQueue: lockfree.NewQueue(),
|
||||
renderedChunks: make(map[protocol.ChunkPos]*image.RGBA),
|
||||
needRedraw: true,
|
||||
w: w,
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *MapUI) Start() {
|
||||
m.ticker = time.NewTicker(33 * time.Millisecond)
|
||||
go func() {
|
||||
for range m.ticker.C {
|
||||
if m.needRedraw {
|
||||
m.needRedraw = false
|
||||
m.Redraw()
|
||||
|
||||
if m.w.proxy.Client != nil {
|
||||
if err := m.w.proxy.Client.WritePacket(&packet.ClientBoundMapItemData{
|
||||
MapID: VIEW_MAP_ID,
|
||||
Width: 128,
|
||||
Height: 128,
|
||||
Pixels: utils.Img2rgba(m.img),
|
||||
UpdateFlags: 2,
|
||||
}); err != nil {
|
||||
logrus.Error(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (m *MapUI) Stop() {
|
||||
if m.ticker != nil {
|
||||
m.ticker.Stop()
|
||||
}
|
||||
}
|
||||
|
||||
// Reset resets the map to inital state
|
||||
func (m *MapUI) Reset() {
|
||||
m.renderedChunks = make(map[protocol.ChunkPos]*image.RGBA)
|
||||
m.SchedRedraw()
|
||||
}
|
||||
|
||||
// ChangeZoom adds to the zoom value and goes around to 32 once it hits 128
|
||||
func (m *MapUI) ChangeZoom() {
|
||||
m.zoomLevel /= 2
|
||||
if m.zoomLevel == 0 {
|
||||
m.zoomLevel = 16
|
||||
}
|
||||
m.SchedRedraw()
|
||||
}
|
||||
|
||||
// SchedRedraw tells the map to redraw the next time its sent
|
||||
func (m *MapUI) SchedRedraw() {
|
||||
m.needRedraw = true
|
||||
}
|
||||
|
||||
// draw_img_scaled_pos draws src onto dst at bottom_left, scaled to size
|
||||
func draw_img_scaled_pos(dst *image.RGBA, src *image.RGBA, bottom_left image.Point, size_scaled int) {
|
||||
sbx := src.Bounds().Dx()
|
||||
ratio := int(float64(sbx) / float64(size_scaled))
|
||||
|
||||
for x_out := bottom_left.X; x_out < bottom_left.X+size_scaled; x_out++ {
|
||||
for y_out := bottom_left.Y; y_out < bottom_left.Y+size_scaled; y_out++ {
|
||||
x_in := (x_out - bottom_left.X) * ratio
|
||||
y_in := (y_out - bottom_left.Y) * ratio
|
||||
c := src.At(x_in, y_in)
|
||||
dst.Set(x_out, y_out, c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// draw chunk images to the map image
|
||||
func (m *MapUI) Redraw() {
|
||||
for {
|
||||
r, ok := m.renderQueue.Dequeue().(*RenderElem)
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
if r.ch != nil {
|
||||
m.renderedChunks[r.pos] = Chunk2Img(r.ch)
|
||||
} else {
|
||||
m.renderedChunks[r.pos] = black_16x16
|
||||
}
|
||||
}
|
||||
|
||||
middle := protocol.ChunkPos{
|
||||
int32(m.w.PlayerPos.Position.X()),
|
||||
int32(m.w.PlayerPos.Position.Z()),
|
||||
}
|
||||
|
||||
// total_width := 32 * math.Ceil(float64(chunks_x)/32)
|
||||
chunks_per_line := float64(128 / m.zoomLevel)
|
||||
px_per_block := 128 / chunks_per_line / 16 // how many pixels per block
|
||||
sz_chunk := int(math.Floor(px_per_block * 16))
|
||||
|
||||
for i := 0; i < len(m.img.Pix); i++ { // clear canvas
|
||||
m.img.Pix[i] = 0
|
||||
}
|
||||
|
||||
for _ch := range m.renderedChunks {
|
||||
relative_middle_x := float64(_ch.X()*16 - middle.X())
|
||||
relative_middle_z := float64(_ch.Z()*16 - middle.Z())
|
||||
px_pos := image.Point{ // bottom left corner of the chunk on the map
|
||||
X: int(math.Floor(relative_middle_x*px_per_block)) + 64,
|
||||
Y: int(math.Floor(relative_middle_z*px_per_block)) + 64,
|
||||
}
|
||||
|
||||
if !m.img.Rect.Intersect(image.Rect(px_pos.X, px_pos.Y, px_pos.X+sz_chunk, px_pos.Y+sz_chunk)).Empty() {
|
||||
draw_img_scaled_pos(m.img, m.renderedChunks[_ch], px_pos, sz_chunk)
|
||||
}
|
||||
}
|
||||
|
||||
draw_full := false
|
||||
|
||||
if draw_full {
|
||||
img2 := m.ToImage()
|
||||
buf := bytes.NewBuffer(nil)
|
||||
bmp.Encode(buf, img2)
|
||||
os.WriteFile("test.bmp", buf.Bytes(), 0o777)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *MapUI) ToImage() *image.RGBA {
|
||||
// get the chunk coord bounds
|
||||
min, max := m.get_bounds()
|
||||
chunks_x := int(max[0] - min[0] + 1) // how many chunk lengths is x coordinate
|
||||
chunks_y := int(max[1] - min[1] + 1)
|
||||
|
||||
img2 := image.NewRGBA(image.Rect(0, 0, chunks_x*16, chunks_y*16))
|
||||
|
||||
middle_block_x := chunks_x / 2 * 16
|
||||
middle_block_y := chunks_y / 2 * 16
|
||||
|
||||
for pos := range m.renderedChunks {
|
||||
px_pos := image.Point{
|
||||
X: int(pos.X()*16) - middle_block_x + img2.Rect.Dx(),
|
||||
Y: int(pos.Z()*16) - middle_block_y + img2.Rect.Dy(),
|
||||
}
|
||||
draw.Draw(img2, image.Rect(
|
||||
px_pos.X,
|
||||
px_pos.Y,
|
||||
px_pos.X+16,
|
||||
px_pos.Y+16,
|
||||
), m.renderedChunks[pos], image.Point{}, draw.Src)
|
||||
}
|
||||
return img2
|
||||
}
|
||||
|
||||
func (m *MapUI) SetChunk(pos protocol.ChunkPos, ch *chunk.Chunk) {
|
||||
m.renderQueue.Enqueue(&RenderElem{pos, ch})
|
||||
m.SchedRedraw()
|
||||
}
|
|
@ -1,675 +1,65 @@
|
|||
package world
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/draw"
|
||||
"image/png"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/bedrock-tool/bedrocktool/handlers/worlds"
|
||||
"github.com/bedrock-tool/bedrocktool/locale"
|
||||
"github.com/bedrock-tool/bedrocktool/ui/messages"
|
||||
"github.com/bedrock-tool/bedrocktool/utils"
|
||||
"github.com/bedrock-tool/bedrocktool/utils/nbtconv"
|
||||
|
||||
"github.com/df-mc/dragonfly/server/block"
|
||||
"github.com/df-mc/dragonfly/server/block/cube"
|
||||
"github.com/df-mc/dragonfly/server/item"
|
||||
"github.com/df-mc/dragonfly/server/item/inventory"
|
||||
"github.com/df-mc/dragonfly/server/world"
|
||||
"github.com/df-mc/dragonfly/server/world/chunk"
|
||||
"github.com/df-mc/dragonfly/server/world/mcdb"
|
||||
"github.com/df-mc/goleveldb/leveldb/opt"
|
||||
"github.com/go-gl/mathgl/mgl32"
|
||||
"github.com/google/subcommands"
|
||||
"github.com/sandertv/gophertunnel/minecraft/protocol"
|
||||
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
|
||||
"github.com/sandertv/gophertunnel/minecraft/resource"
|
||||
"github.com/sirupsen/logrus"
|
||||
//_ "github.com/df-mc/dragonfly/server/block" // to load blocks
|
||||
//_ "net/http/pprof"
|
||||
)
|
||||
|
||||
type TPlayerPos struct {
|
||||
Position mgl32.Vec3
|
||||
Pitch float32
|
||||
Yaw float32
|
||||
HeadYaw float32
|
||||
}
|
||||
|
||||
type itemContainer struct {
|
||||
OpenPacket *packet.ContainerOpen
|
||||
Content *packet.InventoryContent
|
||||
}
|
||||
|
||||
// the state used for drawing and saving
|
||||
|
||||
type WorldState struct {
|
||||
ctx context.Context
|
||||
ispre118 bool
|
||||
voidgen bool
|
||||
chunks map[protocol.ChunkPos]*chunk.Chunk
|
||||
blockNBT map[protocol.SubChunkPos][]map[string]any
|
||||
openItemContainers map[byte]*itemContainer
|
||||
airRid uint32
|
||||
|
||||
Dim world.Dimension
|
||||
WorldName string
|
||||
ServerName string
|
||||
worldCounter int
|
||||
packs map[string]*resource.Pack
|
||||
|
||||
withPacks bool
|
||||
saveImage bool
|
||||
experimentInventory bool
|
||||
|
||||
PlayerPos TPlayerPos
|
||||
proxy *utils.ProxyContext
|
||||
|
||||
// ui
|
||||
ui *MapUI
|
||||
}
|
||||
|
||||
func NewWorldState() *WorldState {
|
||||
w := &WorldState{
|
||||
chunks: make(map[protocol.ChunkPos]*chunk.Chunk),
|
||||
blockNBT: make(map[protocol.SubChunkPos][]map[string]any),
|
||||
openItemContainers: make(map[byte]*itemContainer),
|
||||
Dim: nil,
|
||||
WorldName: "world",
|
||||
PlayerPos: TPlayerPos{},
|
||||
airRid: 6692,
|
||||
}
|
||||
w.ui = NewMapUI(w)
|
||||
return w
|
||||
}
|
||||
|
||||
var dimension_ids = map[uint8]world.Dimension{
|
||||
0: world.Overworld,
|
||||
1: world.Nether,
|
||||
2: world.End,
|
||||
// < 1.18
|
||||
10: world.Overworld_legacy,
|
||||
11: world.Nether,
|
||||
12: world.End,
|
||||
}
|
||||
|
||||
var (
|
||||
black_16x16 = image.NewRGBA(image.Rect(0, 0, 16, 16))
|
||||
Offset_table [24]protocol.SubChunkOffset
|
||||
)
|
||||
|
||||
func init() {
|
||||
for i := range Offset_table {
|
||||
Offset_table[i] = protocol.SubChunkOffset{0, int8(i), 0}
|
||||
}
|
||||
draw.Draw(black_16x16, image.Rect(0, 0, 16, 16), image.Black, image.Point{}, draw.Src)
|
||||
utils.RegisterCommand(&WorldCMD{})
|
||||
}
|
||||
|
||||
type WorldCMD struct {
|
||||
Address string
|
||||
packs bool
|
||||
enableVoid bool
|
||||
saveImage bool
|
||||
experimentInventory bool
|
||||
ServerAddress string
|
||||
Packs bool
|
||||
EnableVoid bool
|
||||
SaveEntities bool
|
||||
SaveInventories bool
|
||||
SaveImage bool
|
||||
}
|
||||
|
||||
func (*WorldCMD) Name() string { return "worlds" }
|
||||
func (*WorldCMD) Synopsis() string { return "download a world from a server" }
|
||||
func (*WorldCMD) Synopsis() string { return locale.Loc("world_synopsis", nil) }
|
||||
|
||||
func (p *WorldCMD) SetFlags(f *flag.FlagSet) {
|
||||
f.StringVar(&p.Address, "address", "", "remote server address")
|
||||
f.BoolVar(&p.packs, "packs", false, "save resourcepacks to the worlds")
|
||||
f.BoolVar(&p.enableVoid, "void", true, "if false, saves with default flat generator")
|
||||
f.BoolVar(&p.saveImage, "image", false, "saves an png of the map at the end")
|
||||
f.BoolVar(&p.experimentInventory, "inv", false, "enable experimental block inventory saving")
|
||||
func (c *WorldCMD) SetFlags(f *flag.FlagSet) {
|
||||
f.StringVar(&c.ServerAddress, "address", "", locale.Loc("remote_address", nil))
|
||||
f.BoolVar(&c.Packs, "packs", false, locale.Loc("save_packs_with_world", nil))
|
||||
f.BoolVar(&c.EnableVoid, "void", true, locale.Loc("enable_void", nil))
|
||||
f.BoolVar(&c.SaveImage, "image", false, locale.Loc("save_image", nil))
|
||||
f.BoolVar(&c.SaveEntities, "save-entities", true, "Save Entities")
|
||||
f.BoolVar(&c.SaveInventories, "save-inventories", true, "Save Inventories")
|
||||
}
|
||||
|
||||
func (c *WorldCMD) Usage() string {
|
||||
return c.Name() + ": " + c.Synopsis() + "\n" + utils.SERVER_ADDRESS_HELP
|
||||
}
|
||||
|
||||
func (c *WorldCMD) Execute(ctx context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
|
||||
/*
|
||||
go func() {
|
||||
http.ListenAndServe(":8000", nil)
|
||||
}()
|
||||
*/
|
||||
|
||||
server_address, hostname, err := utils.ServerInput(ctx, c.Address)
|
||||
func (c *WorldCMD) Execute(ctx context.Context, ui utils.UI) error {
|
||||
serverAddress, hostname, err := ui.ServerInput(ctx, c.ServerAddress)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
return 1
|
||||
return err
|
||||
}
|
||||
|
||||
w := NewWorldState()
|
||||
w.voidgen = c.enableVoid
|
||||
w.ServerName = hostname
|
||||
w.withPacks = c.packs
|
||||
w.saveImage = c.saveImage
|
||||
w.experimentInventory = c.experimentInventory
|
||||
w.ctx = ctx
|
||||
|
||||
proxy := utils.NewProxy(logrus.StandardLogger())
|
||||
proxy.ConnectCB = w.OnConnect
|
||||
proxy.PacketCB = func(pk packet.Packet, proxy *utils.ProxyContext, toServer bool) (packet.Packet, error) {
|
||||
var forward bool
|
||||
if toServer {
|
||||
pk, forward = w.ProcessPacketClient(pk)
|
||||
} else {
|
||||
pk, forward = w.ProcessPacketServer(pk)
|
||||
}
|
||||
if !forward {
|
||||
return nil, nil
|
||||
}
|
||||
return pk, nil
|
||||
}
|
||||
|
||||
err = proxy.Run(ctx, server_address)
|
||||
proxy, err := utils.NewProxy()
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
}
|
||||
w.SaveAndReset()
|
||||
return 0
|
||||
}
|
||||
|
||||
func (w *WorldState) setnameCommand(cmdline []string) bool {
|
||||
w.WorldName = strings.Join(cmdline, " ")
|
||||
w.proxy.SendMessage(fmt.Sprintf("worldName is now: %s", w.WorldName))
|
||||
return true
|
||||
}
|
||||
|
||||
func (w *WorldState) toggleVoid(cmdline []string) bool {
|
||||
w.voidgen = !w.voidgen
|
||||
w.proxy.SendMessage(fmt.Sprintf("using void generator: %t", w.voidgen))
|
||||
return true
|
||||
}
|
||||
|
||||
func (w *WorldState) ProcessLevelChunk(pk *packet.LevelChunk) {
|
||||
_, exists := w.chunks[pk.Position]
|
||||
if exists {
|
||||
return
|
||||
return err
|
||||
}
|
||||
|
||||
ch, blockNBTs, err := chunk.NetworkDecode(w.airRid, pk.RawPayload, int(pk.SubChunkCount), w.Dim.Range(), w.ispre118)
|
||||
proxy.AlwaysGetPacks = true
|
||||
proxy.AddHandler(worlds.NewWorldsHandler(ctx, ui, worlds.WorldSettings{
|
||||
VoidGen: c.EnableVoid,
|
||||
WithPacks: c.Packs,
|
||||
SaveEntities: c.SaveEntities,
|
||||
SaveInventories: c.SaveInventories,
|
||||
SaveImage: c.SaveImage,
|
||||
}))
|
||||
|
||||
ui.Message(messages.SetUIState(messages.UIStateConnect))
|
||||
err = proxy.Run(ctx, serverAddress, hostname)
|
||||
if err != nil {
|
||||
logrus.Error(err)
|
||||
return
|
||||
}
|
||||
if blockNBTs != nil {
|
||||
w.blockNBT[protocol.SubChunkPos{
|
||||
pk.Position.X(), 0, pk.Position.Z(),
|
||||
}] = blockNBTs
|
||||
}
|
||||
|
||||
w.chunks[pk.Position] = ch
|
||||
|
||||
if pk.SubChunkRequestMode == protocol.SubChunkRequestModeLegacy {
|
||||
w.ui.SetChunk(pk.Position, ch)
|
||||
} else {
|
||||
w.ui.SetChunk(pk.Position, nil)
|
||||
// request all the subchunks
|
||||
|
||||
max := w.Dim.Range().Height() / 16
|
||||
if pk.SubChunkRequestMode == protocol.SubChunkRequestModeLimited {
|
||||
max = int(pk.HighestSubChunk)
|
||||
}
|
||||
|
||||
w.proxy.Server.WritePacket(&packet.SubChunkRequest{
|
||||
Dimension: int32(w.Dim.EncodeDimension()),
|
||||
Position: protocol.SubChunkPos{
|
||||
pk.Position.X(), 0, pk.Position.Z(),
|
||||
},
|
||||
Offsets: Offset_table[:max],
|
||||
})
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
last := w.PlayerPos
|
||||
w.PlayerPos = TPlayerPos{
|
||||
Position: Position,
|
||||
Pitch: Pitch,
|
||||
Yaw: Yaw,
|
||||
HeadYaw: HeadYaw,
|
||||
}
|
||||
|
||||
if int(last.Position.X()) != int(w.PlayerPos.Position.X()) || int(last.Position.Z()) != int(w.PlayerPos.Position.Z()) {
|
||||
w.ui.SchedRedraw()
|
||||
}
|
||||
}
|
||||
|
||||
func (w *WorldState) Reset() {
|
||||
w.chunks = make(map[protocol.ChunkPos]*chunk.Chunk)
|
||||
w.WorldName = fmt.Sprintf("world-%d", w.worldCounter)
|
||||
w.ui.Reset()
|
||||
}
|
||||
|
||||
// writes the world to a folder, resets all the chunks
|
||||
func (w *WorldState) SaveAndReset() {
|
||||
logrus.Infof("Saving world %s %d chunks", w.WorldName, len(w.chunks))
|
||||
|
||||
// open world
|
||||
folder := path.Join("worlds", fmt.Sprintf("%s/%s", w.ServerName, w.WorldName))
|
||||
os.RemoveAll(folder)
|
||||
os.MkdirAll(folder, 0o777)
|
||||
|
||||
provider, err := mcdb.New(logrus.StandardLogger(), folder, opt.DefaultCompression)
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
// save chunk data
|
||||
for cp, c := range w.chunks {
|
||||
provider.SaveChunk((world.ChunkPos)(cp), c, w.Dim)
|
||||
}
|
||||
|
||||
// save block nbt data
|
||||
blockNBT := make(map[protocol.ChunkPos][]map[string]any)
|
||||
for scp, v := range w.blockNBT { // 3d to 2d
|
||||
cp := protocol.ChunkPos{scp.X(), scp.Z()}
|
||||
blockNBT[cp] = append(blockNBT[cp], v...)
|
||||
}
|
||||
for cp, v := range blockNBT {
|
||||
err = provider.SaveBlockNBT((world.ChunkPos)(cp), v, w.Dim)
|
||||
if err != nil {
|
||||
logrus.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
// write metadata
|
||||
s := provider.Settings()
|
||||
player := w.proxy.Server.GameData().PlayerPosition
|
||||
s.Spawn = cube.Pos{
|
||||
int(player.X()),
|
||||
int(player.Y()),
|
||||
int(player.Z()),
|
||||
}
|
||||
s.Name = w.WorldName
|
||||
|
||||
// set gamerules
|
||||
ld := provider.LevelDat()
|
||||
gd := w.proxy.Server.GameData()
|
||||
for _, gr := range gd.GameRules {
|
||||
switch gr.Name {
|
||||
case "commandblockoutput":
|
||||
ld.CommandBlockOutput = gr.Value.(bool)
|
||||
case "maxcommandchainlength":
|
||||
ld.MaxCommandChainLength = int32(gr.Value.(uint32))
|
||||
case "commandblocksenabled":
|
||||
ld.CommandsEnabled = gr.Value.(bool)
|
||||
case "dodaylightcycle":
|
||||
ld.DoDayLightCycle = gr.Value.(bool)
|
||||
case "doentitydrops":
|
||||
ld.DoEntityDrops = gr.Value.(bool)
|
||||
case "dofiretick":
|
||||
ld.DoFireTick = gr.Value.(bool)
|
||||
case "domobloot":
|
||||
ld.DoMobLoot = gr.Value.(bool)
|
||||
case "domobspawning":
|
||||
ld.DoMobSpawning = gr.Value.(bool)
|
||||
case "dotiledrops":
|
||||
ld.DoTileDrops = gr.Value.(bool)
|
||||
case "doweathercycle":
|
||||
ld.DoWeatherCycle = gr.Value.(bool)
|
||||
case "drowningdamage":
|
||||
ld.DrowningDamage = gr.Value.(bool)
|
||||
case "doinsomnia":
|
||||
ld.DoInsomnia = gr.Value.(bool)
|
||||
case "falldamage":
|
||||
ld.FallDamage = gr.Value.(bool)
|
||||
case "firedamage":
|
||||
ld.FireDamage = gr.Value.(bool)
|
||||
case "keepinventory":
|
||||
ld.KeepInventory = gr.Value.(bool)
|
||||
case "mobgriefing":
|
||||
ld.MobGriefing = gr.Value.(bool)
|
||||
case "pvp":
|
||||
ld.PVP = gr.Value.(bool)
|
||||
case "showcoordinates":
|
||||
ld.ShowCoordinates = gr.Value.(bool)
|
||||
case "naturalregeneration":
|
||||
ld.NaturalRegeneration = gr.Value.(bool)
|
||||
case "tntexplodes":
|
||||
ld.TNTExplodes = gr.Value.(bool)
|
||||
case "sendcommandfeedback":
|
||||
ld.SendCommandFeedback = gr.Value.(bool)
|
||||
case "randomtickspeed":
|
||||
ld.RandomTickSpeed = int32(gr.Value.(uint32))
|
||||
case "doimmediaterespawn":
|
||||
ld.DoImmediateRespawn = gr.Value.(bool)
|
||||
case "showdeathmessages":
|
||||
ld.ShowDeathMessages = gr.Value.(bool)
|
||||
case "functioncommandlimit":
|
||||
ld.FunctionCommandLimit = int32(gr.Value.(uint32))
|
||||
case "spawnradius":
|
||||
ld.SpawnRadius = int32(gr.Value.(uint32))
|
||||
case "showtags":
|
||||
ld.ShowTags = gr.Value.(bool)
|
||||
case "freezedamage":
|
||||
ld.FreezeDamage = gr.Value.(bool)
|
||||
case "respawnblocksexplode":
|
||||
ld.RespawnBlocksExplode = gr.Value.(bool)
|
||||
case "showbordereffect":
|
||||
ld.ShowBorderEffect = gr.Value.(bool)
|
||||
// todo
|
||||
default:
|
||||
logrus.Warnf("unknown gamerule: %s\n", gr.Name)
|
||||
}
|
||||
}
|
||||
|
||||
ld.RandomSeed = int64(gd.WorldSeed)
|
||||
|
||||
// void world
|
||||
if w.voidgen {
|
||||
ld.FlatWorldLayers = `{"biome_id":1,"block_layers":[{"block_data":0,"block_id":0,"count":1},{"block_data":0,"block_id":0,"count":2},{"block_data":0,"block_id":0,"count":1}],"encoding_version":3,"structure_options":null}`
|
||||
ld.Generator = 2
|
||||
}
|
||||
|
||||
provider.SaveSettings(s)
|
||||
provider.Close()
|
||||
w.worldCounter += 1
|
||||
|
||||
for k, p := range w.packs {
|
||||
logrus.Infof("Adding resource pack: %s\n", k)
|
||||
pack_folder := path.Join(folder, "resource_packs", k)
|
||||
os.MkdirAll(pack_folder, 0o755)
|
||||
data := make([]byte, p.Len())
|
||||
p.ReadAt(data, 0)
|
||||
utils.UnpackZip(bytes.NewReader(data), int64(len(data)), pack_folder)
|
||||
}
|
||||
|
||||
if w.saveImage {
|
||||
f, _ := os.Create(folder + ".png")
|
||||
png.Encode(f, w.ui.ToImage())
|
||||
f.Close()
|
||||
}
|
||||
|
||||
// zip it
|
||||
filename := folder + ".mcworld"
|
||||
|
||||
if err := utils.ZipFolder(filename, folder); err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
logrus.Infof("Saved: %s\n", filename)
|
||||
os.RemoveAll(folder)
|
||||
w.Reset()
|
||||
}
|
||||
|
||||
func (w *WorldState) OnConnect(proxy *utils.ProxyContext) {
|
||||
w.proxy = proxy
|
||||
gd := w.proxy.Server.GameData()
|
||||
|
||||
if len(gd.CustomBlocks) > 0 {
|
||||
logrus.Info("Using Custom Blocks")
|
||||
/*
|
||||
for _, be := range gd.CustomBlocks {
|
||||
b := block.ServerCustomBlock(be)
|
||||
world.RegisterBlock(b)
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
if w.withPacks {
|
||||
go func() {
|
||||
w.packs, _ = utils.GetPacks(w.proxy.Server)
|
||||
}()
|
||||
}
|
||||
|
||||
{ // check game version
|
||||
gv := strings.Split(gd.BaseGameVersion, ".")
|
||||
var err error
|
||||
if len(gv) > 1 {
|
||||
var ver int
|
||||
ver, err = strconv.Atoi(gv[1])
|
||||
w.ispre118 = ver < 18
|
||||
}
|
||||
if err != nil || len(gv) <= 1 {
|
||||
logrus.Info("couldnt determine game version, assuming > 1.18")
|
||||
}
|
||||
|
||||
dim_id := gd.Dimension
|
||||
if w.ispre118 {
|
||||
logrus.Info("using legacy (< 1.18)")
|
||||
dim_id += 10
|
||||
}
|
||||
w.Dim = dimension_ids[uint8(dim_id)]
|
||||
}
|
||||
|
||||
w.proxy.SendMessage("use /setname <worldname>\nto set the world name")
|
||||
|
||||
w.ui.Start()
|
||||
go func() { // send map item
|
||||
select {
|
||||
case <-w.ctx.Done():
|
||||
return
|
||||
default:
|
||||
for {
|
||||
time.Sleep(1 * time.Second)
|
||||
if w.proxy.Client != nil {
|
||||
err := w.proxy.Client.WritePacket(&MAP_ITEM_PACKET)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
proxy.AddCommand(utils.IngameCommand{
|
||||
Exec: w.setnameCommand,
|
||||
Cmd: protocol.Command{
|
||||
Name: "setname",
|
||||
Description: "set user defined name for this world",
|
||||
Overloads: []protocol.CommandOverload{
|
||||
{
|
||||
Parameters: []protocol.CommandParameter{
|
||||
{
|
||||
Name: "name",
|
||||
Type: protocol.CommandArgTypeString,
|
||||
Optional: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
proxy.AddCommand(utils.IngameCommand{
|
||||
Exec: w.toggleVoid,
|
||||
Cmd: protocol.Command{
|
||||
Name: "void",
|
||||
Description: "toggle if void generator should be used",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
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
|
||||
ui.Message(messages.SetUIState(messages.UIStateFinished))
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -0,0 +1,142 @@
|
|||
//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/packs"
|
||||
"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.Register("packs", packs.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(data interface{}) messages.MessageResponse {
|
||||
r := g.router.Handler(data)
|
||||
if r.Ok || r.Data != nil {
|
||||
return r
|
||||
}
|
||||
|
||||
r = messages.MessageResponse{
|
||||
Ok: false,
|
||||
Data: nil,
|
||||
}
|
||||
|
||||
switch data.(type) {
|
||||
case messages.CanShowImages:
|
||||
r.Ok = true
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func init() {
|
||||
utils.MakeGui = func() utils.UI {
|
||||
return &GUI{}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,274 @@
|
|||
package packs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
"sort"
|
||||
"sync"
|
||||
|
||||
"gioui.org/f32"
|
||||
"gioui.org/layout"
|
||||
"gioui.org/op"
|
||||
"gioui.org/op/paint"
|
||||
"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/messages"
|
||||
"github.com/bedrock-tool/bedrocktool/utils"
|
||||
)
|
||||
|
||||
type (
|
||||
C = layout.Context
|
||||
D = layout.Dimensions
|
||||
)
|
||||
|
||||
type Page struct {
|
||||
*pages.Router
|
||||
|
||||
State messages.UIState
|
||||
packsList widget.List
|
||||
packShowButtons map[string]*widget.Clickable
|
||||
l sync.Mutex
|
||||
Packs map[string]*packEntry
|
||||
}
|
||||
|
||||
type packEntry struct {
|
||||
IsFinished bool
|
||||
UUID string
|
||||
|
||||
HasIcon bool
|
||||
Icon paint.ImageOp
|
||||
|
||||
Size uint64
|
||||
Loaded uint64
|
||||
Name string
|
||||
Path string
|
||||
Err error
|
||||
}
|
||||
|
||||
func New(router *pages.Router) *Page {
|
||||
return &Page{
|
||||
Router: router,
|
||||
packsList: widget.List{
|
||||
List: layout.List{
|
||||
Axis: layout.Vertical,
|
||||
},
|
||||
},
|
||||
Packs: make(map[string]*packEntry),
|
||||
packShowButtons: make(map[string]*widget.Clickable),
|
||||
}
|
||||
}
|
||||
|
||||
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: "Pack Download",
|
||||
//Icon: icon.OtherIcon,
|
||||
}
|
||||
}
|
||||
func drawPackIcon(gtx C, hasImage bool, imageOp paint.ImageOp, bounds image.Point) D {
|
||||
return layout.Inset{
|
||||
Top: unit.Dp(5),
|
||||
Bottom: unit.Dp(5),
|
||||
Right: unit.Dp(5),
|
||||
Left: unit.Dp(5),
|
||||
}.Layout(gtx, func(gtx C) D {
|
||||
if hasImage {
|
||||
imageOp.Add(gtx.Ops)
|
||||
s := imageOp.Size()
|
||||
p := f32.Pt(float32(s.X), float32(s.Y))
|
||||
p.X = 1 / (p.X / float32(bounds.X))
|
||||
p.Y = 1 / (p.Y / float32(bounds.Y))
|
||||
defer op.Affine(f32.Affine2D{}.Scale(f32.Pt(0, 0), p)).Push(gtx.Ops).Pop()
|
||||
paint.PaintOp{}.Add(gtx.Ops)
|
||||
}
|
||||
return D{Size: bounds}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func MulAlpha(c color.NRGBA, alpha uint8) color.NRGBA {
|
||||
c.A = uint8(uint32(c.A) * uint32(alpha) / 0xFF)
|
||||
return c
|
||||
}
|
||||
|
||||
func drawPackEntry(gtx C, th *material.Theme, entry *packEntry, button *widget.Clickable) D {
|
||||
var size = ""
|
||||
var colorSize = th.Palette.Fg
|
||||
if entry.IsFinished {
|
||||
size = utils.SizeofFmt(float32(entry.Size))
|
||||
} else {
|
||||
size = fmt.Sprintf("%s / %s %.02f%%",
|
||||
utils.SizeofFmt(float32(entry.Loaded)),
|
||||
utils.SizeofFmt(float32(entry.Size)),
|
||||
float32(entry.Loaded)/float32(entry.Size)*100,
|
||||
)
|
||||
colorSize = color.NRGBA{0x00, 0xc9, 0xc9, 0xff}
|
||||
}
|
||||
|
||||
return layout.UniformInset(5).Layout(gtx, func(gtx C) D {
|
||||
fn := func(gtx C) D {
|
||||
return layout.Flex{Axis: layout.Horizontal}.Layout(gtx,
|
||||
layout.Rigid(func(gtx C) D {
|
||||
return drawPackIcon(gtx, entry.HasIcon, entry.Icon, image.Pt(50, 50))
|
||||
}),
|
||||
layout.Flexed(1, func(gtx C) D {
|
||||
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
||||
layout.Rigid(material.Label(th, th.TextSize, entry.Name).Layout),
|
||||
layout.Rigid(material.LabelStyle{
|
||||
Text: size,
|
||||
Color: colorSize,
|
||||
SelectionColor: MulAlpha(th.Palette.ContrastBg, 0x60),
|
||||
TextSize: th.TextSize,
|
||||
Shaper: th.Shaper,
|
||||
}.Layout),
|
||||
layout.Rigid(func(gtx C) D {
|
||||
if entry.Err != nil {
|
||||
return material.LabelStyle{
|
||||
Color: color.NRGBA{0xbb, 0x00, 0x00, 0xff},
|
||||
Text: entry.Err.Error(),
|
||||
}.Layout(gtx)
|
||||
}
|
||||
return D{}
|
||||
}),
|
||||
)
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
if entry.Path != "" {
|
||||
return material.ButtonLayoutStyle{
|
||||
Background: MulAlpha(th.Palette.Bg, 0x60),
|
||||
Button: button,
|
||||
CornerRadius: 3,
|
||||
}.Layout(gtx, fn)
|
||||
} else {
|
||||
return fn(gtx)
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
func (p *Page) layoutFinished(gtx C, th *material.Theme) D {
|
||||
for uuid, button := range p.packShowButtons {
|
||||
if button.Clicked() {
|
||||
pack := p.Packs[uuid]
|
||||
if pack.IsFinished {
|
||||
utils.ShowFile(pack.Path)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return layout.Center.Layout(gtx, func(gtx C) D {
|
||||
return layout.Flex{
|
||||
Axis: layout.Vertical,
|
||||
}.Layout(gtx,
|
||||
layout.Rigid(material.Label(th, 20, "Downloaded Packs").Layout),
|
||||
layout.Flexed(1, func(gtx C) D {
|
||||
p.l.Lock()
|
||||
defer p.l.Unlock()
|
||||
|
||||
keys := make([]string, 0, len(p.Packs))
|
||||
for k := range p.Packs {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
return material.List(th, &p.packsList).Layout(gtx, len(keys), func(gtx C, index int) D {
|
||||
entry := p.Packs[keys[index]]
|
||||
button := p.packShowButtons[keys[index]]
|
||||
return drawPackEntry(gtx, th, entry, button)
|
||||
})
|
||||
}),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
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),
|
||||
}
|
||||
|
||||
return margin.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||
switch p.State {
|
||||
case messages.UIStateConnecting:
|
||||
return layout.Center.Layout(gtx, material.Label(th, 100, "Connecting").Layout)
|
||||
case messages.UIStateMain:
|
||||
return p.layoutFinished(gtx, th)
|
||||
}
|
||||
return layout.Dimensions{}
|
||||
})
|
||||
}
|
||||
|
||||
func (p *Page) Handler(data interface{}) messages.MessageResponse {
|
||||
r := messages.MessageResponse{
|
||||
Ok: false,
|
||||
Data: nil,
|
||||
}
|
||||
|
||||
switch m := data.(type) {
|
||||
case messages.SetUIState:
|
||||
p.State = m
|
||||
p.Router.Invalidate()
|
||||
r.Ok = true
|
||||
|
||||
case messages.InitialPacksInfo:
|
||||
p.State = messages.UIStateMain
|
||||
p.l.Lock()
|
||||
for _, dp := range m.Packs {
|
||||
e := &packEntry{
|
||||
IsFinished: false,
|
||||
UUID: dp.UUID,
|
||||
Name: dp.SubPackName + " v" + dp.Version,
|
||||
Size: dp.Size,
|
||||
}
|
||||
p.Packs[e.UUID] = e
|
||||
p.packShowButtons[e.UUID] = &widget.Clickable{}
|
||||
}
|
||||
p.l.Unlock()
|
||||
p.Router.Invalidate()
|
||||
|
||||
case messages.PackDownloadProgress:
|
||||
p.l.Lock()
|
||||
e := p.Packs[m.UUID]
|
||||
e.Loaded += m.LoadedAdd
|
||||
if e.Loaded == e.Size {
|
||||
e.IsFinished = true
|
||||
}
|
||||
p.l.Unlock()
|
||||
p.Router.Invalidate()
|
||||
|
||||
case messages.FinishedDownloadingPacks:
|
||||
p.l.Lock()
|
||||
for _, dp := range m.Packs {
|
||||
e := p.Packs[dp.UUID]
|
||||
if dp.Icon != nil {
|
||||
e.Icon = paint.NewImageOpFilter(dp.Icon, paint.FilterNearest)
|
||||
e.HasIcon = true
|
||||
}
|
||||
e.Err = dp.Err
|
||||
e.IsFinished = true
|
||||
e.Path = dp.Path
|
||||
}
|
||||
p.l.Unlock()
|
||||
p.Router.Invalidate()
|
||||
r.Ok = true
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
|
@ -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(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(data any) messages.MessageResponse
|
||||
}
|
||||
|
||||
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(data interface{}) messages.MessageResponse {
|
||||
page, ok := r.pages[r.current]
|
||||
if ok {
|
||||
return page.Handler(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,181 @@
|
|||
package settings
|
||||
|
||||
import (
|
||||
"image"
|
||||
"sort"
|
||||
|
||||
"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{},
|
||||
}
|
||||
|
||||
cmdNames := []string{}
|
||||
for k := range utils.ValidCMDs {
|
||||
cmdNames = append(cmdNames, k)
|
||||
}
|
||||
sort.Strings(cmdNames)
|
||||
|
||||
p.cmdMenu.items = make(map[string]*widget.Clickable, len(utils.ValidCMDs))
|
||||
options := make([]func(layout.Context) layout.Dimensions, 0, len(utils.ValidCMDs))
|
||||
for _, name := range cmdNames {
|
||||
item := &widget.Clickable{}
|
||||
p.cmdMenu.items[name] = item
|
||||
options = append(options, component.MenuItem(router.Theme, item, name).Layout)
|
||||
}
|
||||
|
||||
p.cmdMenu.state = &component.MenuState{
|
||||
OptionList: layout.List{},
|
||||
Options: options,
|
||||
}
|
||||
|
||||
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()
|
||||
|
||||
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(any) messages.MessageResponse {
|
||||
return messages.MessageResponse{
|
||||
Ok: false,
|
||||
Data: nil,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,117 @@
|
|||
package skins
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"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/messages"
|
||||
)
|
||||
|
||||
type (
|
||||
C = layout.Context
|
||||
D = layout.Dimensions
|
||||
)
|
||||
|
||||
type Page struct {
|
||||
*pages.Router
|
||||
|
||||
State messages.UIState
|
||||
SkinsList widget.List
|
||||
l sync.Mutex
|
||||
Skins []messages.NewSkin
|
||||
}
|
||||
|
||||
func New(router *pages.Router) *Page {
|
||||
return &Page{
|
||||
Router: router,
|
||||
SkinsList: widget.List{
|
||||
List: layout.List{
|
||||
Axis: layout.Vertical,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
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),
|
||||
layout.Flexed(1, func(gtx C) D {
|
||||
p.l.Lock()
|
||||
defer p.l.Unlock()
|
||||
return material.List(th, &p.SkinsList).Layout(gtx, len(p.Skins), func(gtx C, index int) D {
|
||||
entry := p.Skins[len(p.Skins)-index-1]
|
||||
return layout.UniformInset(25).Layout(gtx, func(gtx C) D {
|
||||
return layout.Flex{Axis: layout.Horizontal}.Layout(gtx,
|
||||
layout.Rigid(material.Label(th, th.TextSize, entry.PlayerName).Layout),
|
||||
)
|
||||
})
|
||||
})
|
||||
}),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
return layout.Flex{}.Layout(gtx)
|
||||
}
|
||||
|
||||
func (p *Page) Handler(data interface{}) messages.MessageResponse {
|
||||
r := messages.MessageResponse{
|
||||
Ok: false,
|
||||
Data: nil,
|
||||
}
|
||||
|
||||
switch m := data.(type) {
|
||||
case messages.SetUIState:
|
||||
p.State = m
|
||||
p.Router.Invalidate()
|
||||
r.Ok = true
|
||||
case messages.NewSkin:
|
||||
p.l.Lock()
|
||||
p.Skins = append(p.Skins, m)
|
||||
p.l.Unlock()
|
||||
p.Router.Invalidate()
|
||||
r.Ok = true
|
||||
}
|
||||
return r
|
||||
}
|
|
@ -0,0 +1,131 @@
|
|||
package worlds
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/draw"
|
||||
"math"
|
||||
|
||||
"gioui.org/f32"
|
||||
"gioui.org/io/pointer"
|
||||
"gioui.org/layout"
|
||||
"gioui.org/op"
|
||||
"gioui.org/op/clip"
|
||||
"gioui.org/op/paint"
|
||||
"github.com/bedrock-tool/bedrocktool/ui/messages"
|
||||
"github.com/sandertv/gophertunnel/minecraft/protocol"
|
||||
)
|
||||
|
||||
type Map struct {
|
||||
click f32.Point
|
||||
imageOp paint.ImageOp
|
||||
|
||||
scaleFactor float32
|
||||
center f32.Point
|
||||
transform f32.Affine2D
|
||||
grabbed bool
|
||||
cursor image.Point
|
||||
|
||||
MapImage *image.RGBA
|
||||
BoundsMin protocol.ChunkPos
|
||||
BoundsMax protocol.ChunkPos
|
||||
}
|
||||
|
||||
func (m *Map) HandlePointerEvent(e pointer.Event) {
|
||||
switch e.Type {
|
||||
case pointer.Press:
|
||||
m.click = e.Position
|
||||
m.grabbed = true
|
||||
case pointer.Drag:
|
||||
m.transform = m.transform.Offset(e.Position.Sub(m.click))
|
||||
m.click = e.Position
|
||||
case pointer.Release:
|
||||
m.grabbed = false
|
||||
case pointer.Scroll:
|
||||
scaleFactor := -float32(math.Pow(1.01, float64(e.Scroll.Y)))
|
||||
m.transform = m.transform.Scale(e.Position.Sub(m.center), f32.Pt(scaleFactor, scaleFactor))
|
||||
m.scaleFactor *= scaleFactor
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Map) Layout(gtx layout.Context) layout.Dimensions {
|
||||
m.center = f32.Pt(float32(gtx.Constraints.Max.X), float32(gtx.Constraints.Max.Y)).Div(2)
|
||||
|
||||
for _, e := range gtx.Events(m) {
|
||||
if e, ok := e.(pointer.Event); ok {
|
||||
m.HandlePointerEvent(e)
|
||||
}
|
||||
}
|
||||
|
||||
if m.MapImage != nil {
|
||||
// Calculate the size of the widget based on the size of the image and the current scale factor.
|
||||
dx := float32(m.MapImage.Bounds().Dx())
|
||||
dy := float32(m.MapImage.Bounds().Dy())
|
||||
size := f32.Pt(dx*m.scaleFactor, dy*m.scaleFactor)
|
||||
|
||||
// Draw the image at the correct position and scale.
|
||||
defer clip.Rect{Max: gtx.Constraints.Max}.Push(gtx.Ops).Pop()
|
||||
op.Affine(m.transform.Offset(m.center.Sub(size.Div(2)))).Add(gtx.Ops)
|
||||
m.imageOp.Add(gtx.Ops)
|
||||
paint.PaintOp{}.Add(gtx.Ops)
|
||||
if m.cursor.In(image.Rectangle(gtx.Constraints)) {
|
||||
if m.grabbed {
|
||||
pointer.CursorGrabbing.Add(gtx.Ops)
|
||||
} else {
|
||||
pointer.CursorGrab.Add(gtx.Ops)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size := gtx.Constraints.Max
|
||||
pointer.InputOp{
|
||||
Tag: m,
|
||||
Grab: m.grabbed,
|
||||
Types: pointer.Scroll | pointer.Drag | pointer.Press | pointer.Release,
|
||||
ScrollBounds: image.Rect(-size.X, -size.Y, size.X, size.Y),
|
||||
}.Add(gtx.Ops)
|
||||
|
||||
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[1])*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.UpdateMap) {
|
||||
if m.MapImage == nil {
|
||||
m.scaleFactor = 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.NewImageOpFilter(m.MapImage, paint.FilterNearest)
|
||||
}
|
|
@ -0,0 +1,157 @@
|
|||
package worlds
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"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/messages"
|
||||
)
|
||||
|
||||
type (
|
||||
C = layout.Context
|
||||
D = layout.Dimensions
|
||||
)
|
||||
|
||||
type Page struct {
|
||||
*pages.Router
|
||||
|
||||
worldMap *Map
|
||||
State messages.UIState
|
||||
chunkCount int
|
||||
voidGen bool
|
||||
worldName string
|
||||
|
||||
worldsList widget.List
|
||||
worlds []*messages.SavedWorld
|
||||
l sync.Mutex
|
||||
}
|
||||
|
||||
func New(router *pages.Router) *Page {
|
||||
return &Page{
|
||||
Router: router,
|
||||
worldMap: &Map{},
|
||||
worldsList: widget.List{
|
||||
List: layout.List{
|
||||
Axis: layout.Vertical,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
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 displayWorldEntry(gtx C, th *material.Theme, entry *messages.SavedWorld) D {
|
||||
return layout.UniformInset(5).Layout(gtx, func(gtx C) D {
|
||||
return layout.Flex{Axis: layout.Horizontal}.Layout(gtx,
|
||||
layout.Rigid(func(gtx C) D {
|
||||
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
||||
layout.Rigid(material.Label(th, th.TextSize, entry.Name).Layout),
|
||||
layout.Rigid(material.Label(th, th.TextSize, fmt.Sprintf("%d Chunks", entry.Chunks)).Layout),
|
||||
)
|
||||
}),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
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),
|
||||
}
|
||||
|
||||
margin.Layout(gtx, func(gtx C) D {
|
||||
switch p.State {
|
||||
case messages.UIStateConnect:
|
||||
// display login page
|
||||
return layout.Center.Layout(gtx, material.Label(th, 100, "connect Client").Layout)
|
||||
case messages.UIStateConnecting:
|
||||
return layout.Center.Layout(gtx, material.Label(th, 100, "Connecting").Layout)
|
||||
case messages.UIStateMain:
|
||||
// show the main ui
|
||||
return layout.Flex{
|
||||
Axis: layout.Vertical,
|
||||
}.Layout(gtx,
|
||||
//layout.Rigid(material.Label(th, th.TextSize, p.worldName).Layout),
|
||||
layout.Flexed(1, func(gtx C) D {
|
||||
return layout.Center.Layout(gtx, p.worldMap.Layout)
|
||||
}),
|
||||
)
|
||||
case messages.UIStateFinished:
|
||||
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
||||
layout.Rigid(func(gtx C) D {
|
||||
return layout.UniformInset(15).
|
||||
Layout(gtx, material.Label(th, 20, "Worlds Saved").Layout)
|
||||
}),
|
||||
layout.Flexed(1, func(gtx C) D {
|
||||
p.l.Lock()
|
||||
defer p.l.Unlock()
|
||||
return material.List(th, &p.worldsList).Layout(gtx, len(p.worlds), func(gtx C, index int) D {
|
||||
entry := p.worlds[len(p.worlds)-index-1]
|
||||
return displayWorldEntry(gtx, th, entry)
|
||||
})
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
return D{}
|
||||
})
|
||||
|
||||
return layout.Dimensions{}
|
||||
}
|
||||
|
||||
func (u *Page) Handler(data any) messages.MessageResponse {
|
||||
r := messages.MessageResponse{
|
||||
Ok: false,
|
||||
Data: nil,
|
||||
}
|
||||
|
||||
switch m := data.(type) {
|
||||
case messages.SetUIState:
|
||||
u.State = m
|
||||
u.Router.Invalidate()
|
||||
r.Ok = true
|
||||
case messages.UpdateMap:
|
||||
u.chunkCount = m.ChunkCount
|
||||
u.worldMap.Update(&m)
|
||||
u.Router.Invalidate()
|
||||
r.Ok = true
|
||||
case messages.SetVoidGen:
|
||||
u.voidGen = m.Value
|
||||
u.Router.Invalidate()
|
||||
r.Ok = true
|
||||
case messages.SetWorldName:
|
||||
u.worldName = m.WorldName
|
||||
u.Router.Invalidate()
|
||||
r.Ok = true
|
||||
case messages.SavingWorld:
|
||||
u.l.Lock()
|
||||
u.worlds = append(u.worlds, m.World)
|
||||
u.l.Unlock()
|
||||
u.Router.Invalidate()
|
||||
r.Ok = true
|
||||
}
|
||||
return r
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
package settings
|
||||
|
||||
import (
|
||||
"gioui.org/layout"
|
||||
"gioui.org/widget"
|
||||
"gioui.org/widget/material"
|
||||
"github.com/bedrock-tool/bedrocktool/subcommands"
|
||||
"github.com/bedrock-tool/bedrocktool/utils"
|
||||
)
|
||||
|
||||
type packsSettings struct {
|
||||
packs *subcommands.ResourcePackCMD
|
||||
|
||||
serverAddress widget.Editor
|
||||
}
|
||||
|
||||
func (s *packsSettings) Init() {
|
||||
s.packs = utils.ValidCMDs["packs"].(*subcommands.ResourcePackCMD)
|
||||
s.serverAddress.SingleLine = true
|
||||
}
|
||||
|
||||
func (s *packsSettings) Apply() {
|
||||
s.packs.ServerAddress = s.serverAddress.Text()
|
||||
}
|
||||
|
||||
func (s *packsSettings) Layout(gtx layout.Context, th *material.Theme) layout.Dimensions {
|
||||
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
||||
layout.Rigid(material.Editor(th, &s.serverAddress, "Server Address").Layout),
|
||||
)
|
||||
}
|
||||
|
||||
func init() {
|
||||
Settings["packs"] = &packsSettings{}
|
||||
}
|
|
@ -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,49 @@
|
|||
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
|
||||
PacketCapture widget.Bool
|
||||
serverAddress widget.Editor
|
||||
}
|
||||
|
||||
func (s *worldSettings) Init() {
|
||||
s.worlds = utils.ValidCMDs["worlds"].(*world.WorldCMD)
|
||||
s.serverAddress.SingleLine = true
|
||||
s.voidGen.Value = true
|
||||
s.PacketCapture.Value = false
|
||||
}
|
||||
|
||||
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()
|
||||
s.worlds.SaveEntities = true
|
||||
s.worlds.SaveInventories = true
|
||||
}
|
||||
|
||||
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.CheckBox(th, &s.PacketCapture, "packet capture").Layout),
|
||||
layout.Rigid(material.Editor(th, &s.serverAddress, "server Address").Layout),
|
||||
)
|
||||
}
|
||||
|
||||
func init() {
|
||||
Settings["worlds"] = &worldSettings{}
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
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
|
||||
UIStateFinished
|
||||
)
|
||||
|
||||
type HandlerFunc = func(data interface{}) MessageResponse
|
||||
|
||||
//
|
||||
|
||||
type SetUIState = UIState
|
||||
|
||||
//
|
||||
|
||||
type SetVoidGen struct {
|
||||
Value bool
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
type SetWorldName struct {
|
||||
WorldName string
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
type Init struct {
|
||||
Handler HandlerFunc
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
type UpdateMap struct {
|
||||
ChunkCount int
|
||||
Rotation float32
|
||||
UpdatedTiles []protocol.ChunkPos
|
||||
Tiles map[protocol.ChunkPos]*image.RGBA
|
||||
BoundsMin protocol.ChunkPos
|
||||
BoundsMax protocol.ChunkPos
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
type NewSkin struct {
|
||||
PlayerName string
|
||||
Skin *protocol.Skin
|
||||
}
|
||||
|
||||
type SavingWorld struct {
|
||||
World *SavedWorld
|
||||
}
|
||||
|
||||
type SavedWorld struct {
|
||||
Name string
|
||||
Path string
|
||||
Chunks int
|
||||
Image image.Image
|
||||
}
|
||||
|
||||
type CanShowImages struct{}
|
||||
|
||||
type InitialPacksInfo struct {
|
||||
Packs []protocol.TexturePackInfo
|
||||
}
|
||||
|
||||
type PackDownloadProgress struct {
|
||||
UUID string
|
||||
LoadedAdd uint64
|
||||
}
|
||||
|
||||
type DownloadedPack struct {
|
||||
UUID string
|
||||
Name string
|
||||
Path string
|
||||
Size int
|
||||
Icon image.Image
|
||||
Err error
|
||||
}
|
||||
|
||||
type FinishedDownloadingPacks struct {
|
||||
Packs []*DownloadedPack
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
package ui
|
|
@ -2,57 +2,64 @@ package utils
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/bedrock-tool/bedrocktool/locale"
|
||||
"github.com/sandertv/gophertunnel/minecraft/auth"
|
||||
"github.com/sandertv/gophertunnel/minecraft/realms"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
const TOKEN_FILE = "token.json"
|
||||
const TokenFile = "token.json"
|
||||
|
||||
var G_token_src oauth2.TokenSource
|
||||
var gTokenSrc oauth2.TokenSource
|
||||
|
||||
func GetTokenSource() oauth2.TokenSource {
|
||||
if G_token_src != nil {
|
||||
return G_token_src
|
||||
if gTokenSrc != nil {
|
||||
return gTokenSrc
|
||||
}
|
||||
token := get_token()
|
||||
G_token_src = auth.RefreshTokenSource(&token)
|
||||
new_token, err := G_token_src.Token()
|
||||
token := getToken()
|
||||
gTokenSrc = auth.RefreshTokenSource(&token)
|
||||
newToken, err := gTokenSrc.Token()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if !token.Valid() {
|
||||
logrus.Info("Refreshed token")
|
||||
write_token(new_token)
|
||||
logrus.Info(locale.Loc("refreshed_token", nil))
|
||||
writeToken(newToken)
|
||||
}
|
||||
|
||||
return G_token_src
|
||||
return gTokenSrc
|
||||
}
|
||||
|
||||
var G_realms_api *realms.Client
|
||||
var RealmsEnv string
|
||||
|
||||
func GetRealmsApi() *realms.Client {
|
||||
if G_realms_api == nil {
|
||||
G_realms_api = realms.NewClient(GetTokenSource())
|
||||
var gRealmsAPI *realms.Client
|
||||
|
||||
func GetRealmsAPI() *realms.Client {
|
||||
if gRealmsAPI == nil {
|
||||
if RealmsEnv != "" {
|
||||
realms.RealmsAPIBase = fmt.Sprintf("https://pocket-%s.realms.minecraft.net/", RealmsEnv)
|
||||
}
|
||||
gRealmsAPI = realms.NewClient(GetTokenSource())
|
||||
}
|
||||
return G_realms_api
|
||||
return gRealmsAPI
|
||||
}
|
||||
|
||||
func write_token(token *oauth2.Token) {
|
||||
func writeToken(token *oauth2.Token) {
|
||||
buf, err := json.Marshal(token)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
os.WriteFile(TOKEN_FILE, buf, 0o755)
|
||||
os.WriteFile(TokenFile, buf, 0o755)
|
||||
}
|
||||
|
||||
func get_token() oauth2.Token {
|
||||
func getToken() oauth2.Token {
|
||||
var token oauth2.Token
|
||||
if _, err := os.Stat(TOKEN_FILE); err == nil {
|
||||
f, err := os.Open(TOKEN_FILE)
|
||||
if _, err := os.Stat(TokenFile); err == nil {
|
||||
f, err := os.Open(TokenFile)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
@ -65,7 +72,7 @@ func get_token() oauth2.Token {
|
|||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
write_token(_token)
|
||||
writeToken(_token)
|
||||
token = *_token
|
||||
}
|
||||
return token
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
package behaviourpack
|
||||
|
||||
import (
|
||||
"github.com/df-mc/dragonfly/server/world"
|
||||
"github.com/sandertv/gophertunnel/minecraft/protocol"
|
||||
)
|
||||
|
||||
type blockBehaviour struct {
|
||||
FormatVersion string `json:"format_version"`
|
||||
MinecraftBlock world.MinecraftBlock `json:"minecraft:block"`
|
||||
}
|
||||
|
||||
func (bp *BehaviourPack) AddBlock(block protocol.BlockEntry) {
|
||||
ns, _ := ns_name_split(block.Name)
|
||||
if ns == "minecraft" {
|
||||
return
|
||||
}
|
||||
entry := blockBehaviour{
|
||||
FormatVersion: bp.formatVersion,
|
||||
MinecraftBlock: world.ParseBlock(block),
|
||||
}
|
||||
|
||||
bp.blocks = append(bp.blocks, entry)
|
||||
}
|
|
@ -0,0 +1,169 @@
|
|||
package behaviourpack
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/bedrock-tool/bedrocktool/utils"
|
||||
"github.com/repeale/fp-go"
|
||||
"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
|
||||
}
|
||||
|
||||
hasBlocksJson := false
|
||||
if bp.HasBlocks() {
|
||||
_, err = z.Open("blocks.json")
|
||||
if err == nil {
|
||||
hasBlocksJson = true
|
||||
}
|
||||
}
|
||||
|
||||
hasEntitiesFolder := false
|
||||
if bp.HasEntities() {
|
||||
hasEntitiesFolder = fp.Some(func(f *zip.File) bool {
|
||||
return f.Name == "entity" && f.FileInfo().IsDir()
|
||||
})(z.File)
|
||||
}
|
||||
|
||||
hasItemsFolder := false
|
||||
if bp.HasItems() {
|
||||
hasItemsFolder = fp.Some(func(f *zip.File) bool {
|
||||
return f.Name == "items" && f.FileInfo().IsDir()
|
||||
})(z.File)
|
||||
}
|
||||
|
||||
// has no assets needed
|
||||
if !(hasBlocksJson || hasEntitiesFolder || hasItemsFolder) {
|
||||
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) HasEntities() bool {
|
||||
return len(bp.entities) > 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 bp.HasBlocks() { // 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 bp.HasItems() { // 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 bp.HasEntities() { // entities
|
||||
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,96 @@
|
|||
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
|
||||
}
|
||||
|
||||
entry, ok := bp.entities[entity.Identifier]
|
||||
if !ok {
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
width, widthOk := entity.Meta[protocol.EntityDataKeyWidth].(float32)
|
||||
height, heightOk := entity.Meta[protocol.EntityDataKeyHeight].(float32)
|
||||
if widthOk || heightOk {
|
||||
entry.MinecraftEntity.Components["minecraft:collision_box"] = map[string]any{
|
||||
"width": width,
|
||||
"height": height,
|
||||
}
|
||||
}
|
||||
|
||||
if _, ok := entity.Meta[protocol.EntityDataKeyFlags]; ok {
|
||||
AlwaysShowName := entity.Meta.Flag(protocol.EntityDataKeyFlags, protocol.EntityDataFlagAlwaysShowName)
|
||||
if AlwaysShowName {
|
||||
entry.MinecraftEntity.Components["minecraft:nameable"] = map[string]any{
|
||||
"always_show": true,
|
||||
"allow_name_tag_renaming": false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
|
@ -0,0 +1,113 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
|
||||
"github.com/df-mc/dragonfly/server/block"
|
||||
"github.com/df-mc/dragonfly/server/block/cube"
|
||||
"github.com/df-mc/dragonfly/server/world"
|
||||
"github.com/df-mc/dragonfly/server/world/chunk"
|
||||
)
|
||||
|
||||
func isBlockLightblocking(b world.Block) bool {
|
||||
d, isDiffuser := b.(block.LightDiffuser)
|
||||
noDiffuse := isDiffuser && d.LightDiffusionLevel() == 0
|
||||
return !noDiffuse
|
||||
}
|
||||
|
||||
func blockColorAt(c *chunk.Chunk, x uint8, y int16, z uint8) (blockColor color.RGBA) {
|
||||
if y <= int16(c.Range().Min()) {
|
||||
return color.RGBA{0, 0, 0, 0}
|
||||
}
|
||||
rid := c.Block(x, y, z, 0)
|
||||
|
||||
blockColor = color.RGBA{255, 0, 255, 255}
|
||||
b, found := world.BlockByRuntimeID(rid)
|
||||
if !found {
|
||||
return blockColor
|
||||
}
|
||||
|
||||
if _, isWater := b.(block.Water); isWater {
|
||||
waterColor := block.Water{}.Color()
|
||||
|
||||
// get the first non water block at the position
|
||||
heightBlock := c.HeightMap().At(x, z)
|
||||
depth := y - heightBlock
|
||||
if depth > 0 {
|
||||
blockColor = blockColorAt(c, x, heightBlock, z)
|
||||
}
|
||||
|
||||
// blend that blocks color with water depending on depth
|
||||
waterColor.A = uint8(Clamp(int(150+depth*7), 255))
|
||||
blockColor = BlendColors(blockColor, waterColor)
|
||||
blockColor.R -= uint8(depth * 2)
|
||||
blockColor.G -= uint8(depth * 2)
|
||||
blockColor.B -= uint8(depth * 2)
|
||||
return blockColor
|
||||
} else {
|
||||
col := b.Color()
|
||||
if col.A != 255 {
|
||||
col = BlendColors(blockColorAt(c, x, y-1, z), col)
|
||||
}
|
||||
|
||||
/*
|
||||
a := color.RGBA{255, 0, 255, 255}
|
||||
if col == a {
|
||||
name, nbt := b.EncodeBlock()
|
||||
fmt.Printf("unknown color %d %s %s %s\n", rid, reflect.TypeOf(b), name, nbt)
|
||||
}
|
||||
*/
|
||||
|
||||
return col
|
||||
}
|
||||
}
|
||||
|
||||
func chunkGetColorAt(c *chunk.Chunk, x uint8, y int16, z uint8) color.RGBA {
|
||||
haveUp := false
|
||||
cube.Pos{int(x), int(y), int(z)}.
|
||||
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() || haveUp {
|
||||
return
|
||||
}
|
||||
blockRid := c.Block(uint8(neighbour[0]), int16(neighbour[1]), uint8(neighbour[2]), 0)
|
||||
if blockRid > 0 {
|
||||
b, found := world.BlockByRuntimeID(blockRid)
|
||||
if found {
|
||||
if isBlockLightblocking(b) {
|
||||
haveUp = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}, cube.Range{int(y + 1), int(y + 1)})
|
||||
|
||||
blockColor := blockColorAt(c, x, y, z)
|
||||
if haveUp && (x+z)%2 == 0 {
|
||||
if blockColor.R > 10 {
|
||||
blockColor.R -= 10
|
||||
}
|
||||
if blockColor.G > 10 {
|
||||
blockColor.G -= 10
|
||||
}
|
||||
if blockColor.B > 10 {
|
||||
blockColor.B -= 10
|
||||
}
|
||||
}
|
||||
return blockColor
|
||||
}
|
||||
|
||||
func Chunk2Img(c *chunk.Chunk) *image.RGBA {
|
||||
img := image.NewRGBA(image.Rect(0, 0, 16, 16))
|
||||
hm := c.HeightMapWithWater()
|
||||
|
||||
for x := uint8(0); x < 16; x++ {
|
||||
for z := uint8(0); z < 16; z++ {
|
||||
img.SetRGBA(
|
||||
int(x), int(z),
|
||||
chunkGetColorAt(c, x, hm.At(x, z), z),
|
||||
)
|
||||
}
|
||||
}
|
||||
return img
|
||||
}
|
|
@ -1,10 +1,42 @@
|
|||
package utils
|
||||
|
||||
import "github.com/google/subcommands"
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
|
||||
var ValidCMDs = make(map[string]string, 0)
|
||||
"github.com/google/subcommands"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func RegisterCommand(sub subcommands.Command) {
|
||||
subcommands.Register(sub, "")
|
||||
ValidCMDs[sub.Name()] = sub.Synopsis()
|
||||
var ValidCMDs = make(map[string]Command, 0)
|
||||
|
||||
type Command interface {
|
||||
Name() string
|
||||
Synopsis() string
|
||||
SetFlags(f *flag.FlagSet)
|
||||
Execute(ctx context.Context, ui UI) error
|
||||
}
|
||||
|
||||
type cmdWrap struct {
|
||||
subcommands.Command
|
||||
|
||||
cmd Command
|
||||
}
|
||||
|
||||
func (c *cmdWrap) Name() string { return c.cmd.Name() }
|
||||
func (c *cmdWrap) Synopsis() string { return c.cmd.Synopsis() }
|
||||
func (c *cmdWrap) SetFlags(f *flag.FlagSet) { c.cmd.SetFlags(f) }
|
||||
func (c *cmdWrap) Usage() string { return c.Name() + ": " + c.Synopsis() }
|
||||
func (c *cmdWrap) Execute(ctx context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
|
||||
err := c.cmd.Execute(ctx, CurrentUI)
|
||||
if err != nil {
|
||||
logrus.Error(err)
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func RegisterCommand(sub Command) {
|
||||
subcommands.Register(&cmdWrap{cmd: sub}, "")
|
||||
ValidCMDs[sub.Name()] = sub
|
||||
}
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
package crypt
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
_ "embed"
|
||||
"io"
|
||||
"os"
|
||||
"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(filename string) (io.WriteCloser, error) {
|
||||
w, err := os.Create(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wc, err := openpgp.Encrypt(w, recipients, nil, &openpgp.FileHints{
|
||||
IsBinary: true, FileName: filename, 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-----
|
20
utils/dns.go
20
utils/dns.go
|
@ -1,14 +1,17 @@
|
|||
//go:build false
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/bedrock-tool/bedrocktool/locale"
|
||||
"github.com/miekg/dns"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var override_dns = map[string]bool{
|
||||
var overrideDNS = map[string]bool{
|
||||
"geo.hivebedrock.network.": true,
|
||||
}
|
||||
|
||||
|
@ -23,15 +26,15 @@ func (d *DNSServer) answerQuery(remote net.Addr, req *dns.Msg) (reply *dns.Msg)
|
|||
case dns.TypeA:
|
||||
logrus.Infof("Query for %s", q.Name)
|
||||
|
||||
if override_dns[q.Name] {
|
||||
if overrideDNS[q.Name] {
|
||||
host, _, _ := net.SplitHostPort(remote.String())
|
||||
remote_ip := net.ParseIP(host)
|
||||
remoteIP := net.ParseIP(host)
|
||||
|
||||
addrs, _ := net.InterfaceAddrs()
|
||||
var ip string
|
||||
for _, addr := range addrs {
|
||||
if ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
|
||||
if ipnet.Contains(remote_ip) {
|
||||
if ipnet.Contains(remoteIP) {
|
||||
ip = ipnet.IP.String()
|
||||
}
|
||||
}
|
||||
|
@ -83,17 +86,20 @@ func (d *DNSServer) handler(w dns.ResponseWriter, req *dns.Msg) {
|
|||
}
|
||||
|
||||
func InitDNS() {
|
||||
if !Options.EnableDNS {
|
||||
return
|
||||
}
|
||||
d := DNSServer{}
|
||||
dns.HandleFunc(".", d.handler)
|
||||
|
||||
server := &dns.Server{Addr: ":53", Net: "udp"}
|
||||
go func() {
|
||||
logrus.Infof("Starting dns at %s:53\n", GetLocalIP())
|
||||
logrus.Infof(locale.Loc("starting_dns", locale.Strmap{"Ip": GetLocalIP()}))
|
||||
err := server.ListenAndServe()
|
||||
defer server.Shutdown()
|
||||
if err != nil {
|
||||
logrus.Warnf("Failed to start dns server: %s\n ", err.Error())
|
||||
logrus.Info("you may have to use bedrockconnect")
|
||||
logrus.Warnf(locale.Loc("failed_to_start_dns", locale.Strmap{"Err": err.Error()}))
|
||||
logrus.Info(locale.Loc("suggest_bedrockconnect", nil))
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
|
|
@ -0,0 +1,129 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
func DumpStruct(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()
|
||||
if typeName == "[]interface {}" {
|
||||
typeName = "[]any"
|
||||
}
|
||||
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 += DumpStruct(level+1, val.Interface(), false, false)
|
||||
s += "\n" + tBase + "}"
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
switch ii.Kind() {
|
||||
case 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() {
|
||||
s += fmt.Sprintf("%s\t%s: %s,\n", tBase, fieldType.Name, DumpStruct(level+1, ii.Field(i).Interface(), true, false))
|
||||
} else {
|
||||
s += tBase + " " + fieldType.Name + " (unexported)"
|
||||
}
|
||||
}
|
||||
s += tBase + "}"
|
||||
}
|
||||
case reflect.Slice:
|
||||
s += typeName + "{"
|
||||
|
||||
if ii.Len() > 1000 {
|
||||
s += "<slice too long>"
|
||||
} else if ii.Len() == 0 {
|
||||
} else {
|
||||
e := ii.Index(0)
|
||||
t := reflect.TypeOf(e.Interface())
|
||||
is_elem_struct := t.Kind() == reflect.Struct
|
||||
|
||||
if is_elem_struct {
|
||||
s += "\n"
|
||||
}
|
||||
for i := 0; i < ii.Len(); i++ {
|
||||
if is_elem_struct {
|
||||
s += tBase + "\t"
|
||||
}
|
||||
s += DumpStruct(level+1, ii.Index(i).Interface(), false, true) + ","
|
||||
if is_elem_struct {
|
||||
s += "\n"
|
||||
} else {
|
||||
if i != ii.Len()-1 {
|
||||
s += " "
|
||||
}
|
||||
}
|
||||
}
|
||||
if is_elem_struct {
|
||||
s += tBase
|
||||
}
|
||||
}
|
||||
s += "}"
|
||||
case reflect.Map:
|
||||
it := reflect.TypeOf(inputStruct)
|
||||
valType := it.Elem().String()
|
||||
if valType == "interface {}" {
|
||||
valType = "any"
|
||||
}
|
||||
keyType := it.Key().String()
|
||||
|
||||
s += fmt.Sprintf("map[%s]%s{", keyType, valType)
|
||||
if ii.Len() > 0 {
|
||||
s += "\n"
|
||||
}
|
||||
|
||||
iter := ii.MapRange()
|
||||
for iter.Next() {
|
||||
k := iter.Key()
|
||||
v := iter.Value()
|
||||
s += fmt.Sprintf("%s\t%#v: %s,\n", tBase, k.Interface(), DumpStruct(level+1, v.Interface(), true, false))
|
||||
}
|
||||
|
||||
if ii.Len() > 0 {
|
||||
s += tBase
|
||||
}
|
||||
s += "}"
|
||||
default:
|
||||
is_array := ii.Kind() == reflect.Array
|
||||
add_type := !isInList && !is_array && len(typeString) > 0
|
||||
if add_type {
|
||||
s += typeString + "("
|
||||
}
|
||||
s += fmt.Sprintf("%#v", ii.Interface())
|
||||
if add_type {
|
||||
s += ")"
|
||||
}
|
||||
}
|
||||
return s
|
||||
}
|
|
@ -0,0 +1,219 @@
|
|||
package encryptor
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"io/fs"
|
||||
"math/rand"
|
||||
"path/filepath"
|
||||
"testing/fstest"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type contentItem struct {
|
||||
Path string `json:"path"`
|
||||
Key string `json:"key"`
|
||||
}
|
||||
|
||||
type Content struct {
|
||||
Content []contentItem `json:"content"`
|
||||
}
|
||||
|
||||
var StaticKey = []byte("s5s5ejuDru4uchuF2drUFuthaspAbepE")
|
||||
|
||||
func GenerateKey() (out []byte) {
|
||||
out = make([]byte, 32)
|
||||
var vocab = []byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
|
||||
for i := 0; i < 32; i++ {
|
||||
out[i] = vocab[rand.Intn(len(vocab))]
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func encryptCfb(data []byte, key []byte) {
|
||||
b, _ := aes.NewCipher(key)
|
||||
s := cipher.NewCFBEncrypter(b, key[0:16])
|
||||
s.XORKeyStream(data, data)
|
||||
}
|
||||
|
||||
func canEncrypt(path string) bool {
|
||||
if path == "manifest.json" {
|
||||
return false
|
||||
}
|
||||
s := filepath.SplitList(path)
|
||||
if s[0] == "texts" {
|
||||
return false
|
||||
}
|
||||
if s[len(s)-1] == "contents.json" {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func enc(fsys fs.FS, fsyso fstest.MapFS, contentsJson *Content, dir string) error {
|
||||
// get all files in this folder
|
||||
matches, err := fs.Glob(fsys, dir+"**")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, path := range matches {
|
||||
// create output file
|
||||
ifo, err := fs.Stat(fsys, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fo := &fstest.MapFile{
|
||||
ModTime: ifo.ModTime(),
|
||||
Mode: ifo.Mode(),
|
||||
}
|
||||
fsyso[path] = fo
|
||||
|
||||
// recurse
|
||||
if ifo.IsDir() {
|
||||
return enc(fsys, fsyso, contentsJson, path+"/")
|
||||
}
|
||||
|
||||
// read data
|
||||
var data []byte
|
||||
data, err = fs.ReadFile(fsys, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// encrypt if needed
|
||||
if canEncrypt(path) {
|
||||
key := GenerateKey()
|
||||
it := contentItem{
|
||||
Path: path,
|
||||
Key: hex.EncodeToString(key),
|
||||
}
|
||||
contentsJson.Content = append(contentsJson.Content, it)
|
||||
encryptCfb(data, key)
|
||||
}
|
||||
|
||||
// write to output
|
||||
fo.Data = data
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Enc(fsys fs.FS, id *uuid.UUID, ContentKey []byte) (fs.FS, error) {
|
||||
var manifest map[string]any
|
||||
|
||||
// read the manifest
|
||||
f, err := fsys.Open("manifest.json")
|
||||
if err == nil {
|
||||
dec := json.NewDecoder(f)
|
||||
err = dec.Decode(&manifest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
header, ok := manifest["header"].(map[string]any)
|
||||
if !ok {
|
||||
return nil, errors.New("no header")
|
||||
}
|
||||
|
||||
// get id from manifest if not specified, else change it in the manifet
|
||||
if id == nil {
|
||||
idstr, ok := header["uuid"].(string)
|
||||
if !ok {
|
||||
return nil, errors.New("no id")
|
||||
}
|
||||
_id, err := uuid.Parse(idstr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
id = &_id
|
||||
} else {
|
||||
header["uuid"] = id.String()
|
||||
}
|
||||
} else {
|
||||
if id != nil {
|
||||
// create a manifest
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
fsyso := fstest.MapFS{}
|
||||
// encrypt
|
||||
var contentsJson Content
|
||||
err = enc(fsys, fsyso, &contentsJson, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// write new manifest
|
||||
manifestData, _ := json.MarshalIndent(manifest, "", "\t")
|
||||
fsyso["manifest.json"] = &fstest.MapFile{
|
||||
Data: manifestData,
|
||||
}
|
||||
|
||||
// write the contents.json encrypted
|
||||
contentsBuf := bytes.NewBuffer(nil)
|
||||
binary.Write(contentsBuf, binary.LittleEndian, uint32(0))
|
||||
binary.Write(contentsBuf, binary.LittleEndian, uint32(0x9bcfb9fc))
|
||||
binary.Write(contentsBuf, binary.LittleEndian, uint64(0))
|
||||
contentsBuf.WriteByte(byte(len(id.String())))
|
||||
contentsBuf.Write([]byte(id.String()))
|
||||
contentsBuf.Write(make([]byte, 0xff-contentsBuf.Len()))
|
||||
contentsData, _ := json.Marshal(&contentsJson)
|
||||
encryptCfb(contentsData, ContentKey)
|
||||
contentsBuf.Write(contentsData)
|
||||
fsyso["contents.json"] = &fstest.MapFile{
|
||||
Data: contentsBuf.Bytes(),
|
||||
Mode: 0775,
|
||||
ModTime: time.Now(),
|
||||
}
|
||||
|
||||
return fsyso, nil
|
||||
}
|
||||
|
||||
func fstozip(fsys fs.FS, zw *zip.Writer, dir string) error {
|
||||
// get files in this folder
|
||||
matches, err := fs.Glob(fsys, dir+"**")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, path := range matches {
|
||||
// if this path is a folder, recurse
|
||||
ifo, _ := fs.Stat(fsys, path)
|
||||
if ifo.IsDir() {
|
||||
return fstozip(fsys, zw, path+"/")
|
||||
}
|
||||
// copy the file to the zip
|
||||
w, err := zw.CreateHeader(&zip.FileHeader{
|
||||
Name: ifo.Name(),
|
||||
Modified: ifo.ModTime(),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data, err := fs.ReadFile(fsys, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
w.Write(data)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func FSToZip(fsys fs.FS, w io.Writer) error {
|
||||
zw := zip.NewWriter(w)
|
||||
err := fstozip(fsys, zw, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
zw.Close()
|
||||
return nil
|
||||
}
|
|
@ -3,15 +3,25 @@ package utils
|
|||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
"reflect"
|
||||
"image/png"
|
||||
"os"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func Img2rgba(img *image.RGBA) []color.RGBA {
|
||||
header := *(*reflect.SliceHeader)(unsafe.Pointer(&img.Pix))
|
||||
header.Len /= 4
|
||||
header.Cap /= 4
|
||||
return *(*[]color.RGBA)(unsafe.Pointer(&header))
|
||||
return unsafe.Slice((*color.RGBA)(unsafe.Pointer(unsafe.SliceData(img.Pix))), len(img.Pix)/4)
|
||||
}
|
||||
|
||||
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)), nil
|
||||
}
|
||||
|
||||
// LERP is a linear interpolation function
|
||||
|
@ -34,3 +44,17 @@ func BlendColors(c1, c2 color.RGBA) (ret color.RGBA) {
|
|||
ret.A = blendAlphaValue(c1.A, c2.A)
|
||||
return ret
|
||||
}
|
||||
|
||||
// DrawImgScaledPos draws src onto dst at bottomLeft, scaled to size
|
||||
func DrawImgScaledPos(dst *image.RGBA, src *image.RGBA, bottomLeft image.Point, sizeScaled int) {
|
||||
sbx := src.Bounds().Dx()
|
||||
ratio := int(float64(sbx) / float64(sizeScaled))
|
||||
|
||||
for xOut := bottomLeft.X; xOut < bottomLeft.X+sizeScaled; xOut++ {
|
||||
for yOut := bottomLeft.Y; yOut < bottomLeft.Y+sizeScaled; yOut++ {
|
||||
xIn := (xOut - bottomLeft.X) * ratio
|
||||
yIn := (yOut - bottomLeft.Y) * ratio
|
||||
dst.Set(xOut, yOut, src.At(xIn, yIn))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,10 +9,11 @@ import (
|
|||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/bedrock-tool/bedrocktool/locale"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func User_input(ctx context.Context, q string) (string, bool) {
|
||||
func UserInput(ctx context.Context, q string) (string, bool) {
|
||||
c := make(chan string)
|
||||
go func() {
|
||||
fmt.Print(q)
|
||||
|
@ -31,47 +32,65 @@ func User_input(ctx context.Context, q string) (string, bool) {
|
|||
}
|
||||
}
|
||||
|
||||
func server_url_to_name(server string) string {
|
||||
func serverGetHostname(server string) string {
|
||||
host, _, err := net.SplitHostPort(server)
|
||||
if err != nil {
|
||||
logrus.Fatalf("Invalid server: %s", err)
|
||||
logrus.Fatalf(locale.Loc("invalid_server", locale.Strmap{"Err": err.Error()}))
|
||||
}
|
||||
return host
|
||||
}
|
||||
|
||||
func ServerInput(ctx context.Context, server string) (address, name string, err error) {
|
||||
if server == "" { // no arg provided, interactive input
|
||||
var (
|
||||
realmRegex = regexp.MustCompile("realm:(?P<Name>.*)(?::(?P<ID>.*))?")
|
||||
pcapRegex = regexp.MustCompile(`(?P<Filename>(?P<Name>.*)\.pcap2)(?:\?(?P<Args>.*))?`)
|
||||
)
|
||||
|
||||
func regexGetParams(r *regexp.Regexp, s string) (params map[string]string) {
|
||||
match := r.FindStringSubmatch(s)
|
||||
params = make(map[string]string)
|
||||
for i, name := range r.SubexpNames() {
|
||||
if i > 0 && i <= len(match) {
|
||||
params[name] = match[i]
|
||||
}
|
||||
}
|
||||
return params
|
||||
}
|
||||
|
||||
func ServerInput(ctx context.Context, server string) (string, string, error) {
|
||||
// no arg provided, interactive input
|
||||
if server == "" {
|
||||
var cancelled bool
|
||||
server, cancelled = User_input(ctx, "Enter Server: ")
|
||||
server, cancelled = UserInput(ctx, locale.Loc("enter_server", nil))
|
||||
if cancelled {
|
||||
return "", "", context.Canceled
|
||||
}
|
||||
}
|
||||
|
||||
if strings.HasPrefix(server, "realm:") { // for realms use api to get ip address
|
||||
realm_info := strings.Split(server, ":")
|
||||
id := ""
|
||||
if len(realm_info) == 3 {
|
||||
id = realm_info[2]
|
||||
}
|
||||
name, address, err = get_realm(context.Background(), GetRealmsApi(), realm_info[1], id)
|
||||
// realm
|
||||
if realmRegex.MatchString(server) {
|
||||
p := regexGetParams(realmRegex, server)
|
||||
name, address, err := getRealm(ctx, p["Name"], p["ID"])
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
name = CleanupName(name)
|
||||
} else if strings.HasSuffix(server, ".pcap") || strings.HasSuffix(server, ".pcap2") {
|
||||
s := strings.Split(server, ".")
|
||||
name = strings.Join(s[:len(s)-1], ".")
|
||||
address = server
|
||||
} else {
|
||||
// if an actual server address if given
|
||||
// add port if necessary
|
||||
address = server
|
||||
if len(strings.Split(address, ":")) == 1 {
|
||||
address += ":19132"
|
||||
}
|
||||
name = server_url_to_name(address)
|
||||
return address, CleanupName(name), nil
|
||||
}
|
||||
|
||||
return address, name, nil
|
||||
// old pcap format
|
||||
if match, _ := regexp.MatchString(`.*\.pcap$`, server); match {
|
||||
return "", "", fmt.Errorf(locale.Loc("not_supported_anymore", nil))
|
||||
}
|
||||
|
||||
// new pcap format
|
||||
|
||||
if pcapRegex.MatchString(server) {
|
||||
p := regexGetParams(pcapRegex, server)
|
||||
return "PCAP!" + p["Filename"], p["Name"], nil
|
||||
}
|
||||
|
||||
// normal server dns or ip
|
||||
if len(strings.Split(server, ":")) == 1 {
|
||||
server += ":19132"
|
||||
}
|
||||
return server, serverGetHostname(server), nil
|
||||
}
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
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(data interface{}) messages.MessageResponse
|
||||
ServerInput(context.Context, string) (string, string, error)
|
||||
}
|
||||
|
||||
type BaseUI struct {
|
||||
UI
|
||||
}
|
||||
|
||||
func (u *BaseUI) Message(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()
|
||||
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{}
|
||||
}
|
|
@ -8,11 +8,11 @@ import (
|
|||
func InvFromNBT(inv *inventory.Inventory, items []any) {
|
||||
for _, itemData := range items {
|
||||
data, _ := itemData.(map[string]any)
|
||||
it := ReadItem(data, nil)
|
||||
it := Item(data, nil)
|
||||
if it.Empty() {
|
||||
continue
|
||||
}
|
||||
_ = inv.SetItem(int(Map[byte](data, "Slot")), it)
|
||||
_ = inv.SetItem(int(Uint8(data, "Slot")), it)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,89 +0,0 @@
|
|||
package nbtconv
|
||||
|
||||
import (
|
||||
"github.com/df-mc/dragonfly/server/block/cube"
|
||||
"github.com/df-mc/dragonfly/server/item"
|
||||
"github.com/df-mc/dragonfly/server/world"
|
||||
"github.com/go-gl/mathgl/mgl64"
|
||||
)
|
||||
|
||||
// Map reads a value of the type T from the map passed. Map never panics. If the key was not found in the map
|
||||
// or if the value was of a different type, the default value of type T is returned.
|
||||
func Map[T any](m map[string]any, key string) T {
|
||||
v, _ := m[key].(T)
|
||||
return v
|
||||
}
|
||||
|
||||
// MapVec3 converts x, y and z values in an NBT map to an mgl64.Vec3.
|
||||
func MapVec3(x map[string]any, k string) mgl64.Vec3 {
|
||||
if i, ok := x[k].([]any); ok {
|
||||
if len(i) != 3 {
|
||||
return mgl64.Vec3{}
|
||||
}
|
||||
var v mgl64.Vec3
|
||||
for index, f := range i {
|
||||
f32, _ := f.(float32)
|
||||
v[index] = float64(f32)
|
||||
}
|
||||
return v
|
||||
} else if i, ok := x[k].([]float32); ok {
|
||||
if len(i) != 3 {
|
||||
return mgl64.Vec3{}
|
||||
}
|
||||
return mgl64.Vec3{float64(i[0]), float64(i[1]), float64(i[2])}
|
||||
}
|
||||
return mgl64.Vec3{}
|
||||
}
|
||||
|
||||
// Vec3ToFloat32Slice converts an mgl64.Vec3 to a []float32 with 3 elements.
|
||||
func Vec3ToFloat32Slice(x mgl64.Vec3) []float32 {
|
||||
return []float32{float32(x[0]), float32(x[1]), float32(x[2])}
|
||||
}
|
||||
|
||||
// MapPos converts x, y and z values in an NBT map to a cube.Pos.
|
||||
func MapPos(x map[string]any, k string) cube.Pos {
|
||||
if i, ok := x[k].([]any); ok {
|
||||
if len(i) != 3 {
|
||||
return cube.Pos{}
|
||||
}
|
||||
var v cube.Pos
|
||||
for index, f := range i {
|
||||
f32, _ := f.(int32)
|
||||
v[index] = int(f32)
|
||||
}
|
||||
return v
|
||||
} else if i, ok := x[k].([]int32); ok {
|
||||
if len(i) != 3 {
|
||||
return cube.Pos{}
|
||||
}
|
||||
return cube.Pos{int(i[0]), int(i[1]), int(i[2])}
|
||||
}
|
||||
return cube.Pos{}
|
||||
}
|
||||
|
||||
// PosToInt32Slice converts a cube.Pos to a []int32 with 3 elements.
|
||||
func PosToInt32Slice(x cube.Pos) []int32 {
|
||||
return []int32{int32(x[0]), int32(x[1]), int32(x[2])}
|
||||
}
|
||||
|
||||
// MapBlock converts a block's name and properties in a map obtained by decoding NBT to a world.Block.
|
||||
func MapBlock(x map[string]any, k string) world.Block {
|
||||
if m, ok := x[k].(map[string]any); ok {
|
||||
return ReadBlock(m)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MapItem converts an item's name, count, damage (and properties when it is a block) in a map obtained by decoding NBT
|
||||
// to a world.Item.
|
||||
func MapItem(x map[string]any, k string) item.Stack {
|
||||
if m, ok := x[k].(map[string]any); ok {
|
||||
s := readItemStack(m)
|
||||
readDamage(m, &s, true)
|
||||
readEnchantments(m, &s)
|
||||
readDisplay(m, &s)
|
||||
readDragonflyData(m, &s)
|
||||
return s
|
||||
}
|
||||
return item.Stack{}
|
||||
}
|
|
@ -3,78 +3,233 @@ package nbtconv
|
|||
import (
|
||||
"bytes"
|
||||
"encoding/gob"
|
||||
"github.com/df-mc/dragonfly/server/block/cube"
|
||||
"github.com/df-mc/dragonfly/server/item"
|
||||
"github.com/df-mc/dragonfly/server/world"
|
||||
"github.com/go-gl/mathgl/mgl64"
|
||||
"golang.org/x/exp/constraints"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ReadItem decodes the data of an item into an item stack.
|
||||
func ReadItem(data map[string]any, s *item.Stack) item.Stack {
|
||||
disk := s == nil
|
||||
// Bool reads a uint8 value from a map at key k and returns true if it equals 1.
|
||||
func Bool(m map[string]any, k string) bool {
|
||||
return Uint8(m, k) == 1
|
||||
}
|
||||
|
||||
// Uint8 reads a uint8 value from a map at key k.
|
||||
func Uint8(m map[string]any, k string) uint8 {
|
||||
v, _ := m[k].(uint8)
|
||||
return v
|
||||
}
|
||||
|
||||
// String reads a string value from a map at key k.
|
||||
func String(m map[string]any, k string) string {
|
||||
v, _ := m[k].(string)
|
||||
return v
|
||||
}
|
||||
|
||||
// Int16 reads an int16 value from a map at key k.
|
||||
func Int16(m map[string]any, k string) int16 {
|
||||
v, _ := m[k].(int16)
|
||||
return v
|
||||
}
|
||||
|
||||
// Int32 reads an int32 value from a map at key k.
|
||||
func Int32(m map[string]any, k string) int32 {
|
||||
v, _ := m[k].(int32)
|
||||
return v
|
||||
}
|
||||
|
||||
// Int64 reads an int16 value from a map at key k.
|
||||
func Int64(m map[string]any, k string) int64 {
|
||||
v, _ := m[k].(int64)
|
||||
return v
|
||||
}
|
||||
|
||||
// TickDuration reads a uint8/int16/in32 value from a map at key k and converts
|
||||
// it from ticks to a time.Duration.
|
||||
func TickDuration[T constraints.Integer](m map[string]any, k string) time.Duration {
|
||||
var v time.Duration
|
||||
switch any(*new(T)).(type) {
|
||||
case uint8:
|
||||
v = time.Duration(Uint8(m, k))
|
||||
case int16:
|
||||
v = time.Duration(Int16(m, k))
|
||||
case int32:
|
||||
v = time.Duration(Int32(m, k))
|
||||
default:
|
||||
panic("invalid tick duration value type")
|
||||
}
|
||||
return v * time.Millisecond * 50
|
||||
}
|
||||
|
||||
// Float32 reads a float32 value from a map at key k.
|
||||
func Float32(m map[string]any, k string) float32 {
|
||||
v, _ := m[k].(float32)
|
||||
return v
|
||||
}
|
||||
|
||||
// Float64 reads a float64 value from a map at key k.
|
||||
func Float64(m map[string]any, k string) float64 {
|
||||
v, _ := m[k].(float64)
|
||||
return v
|
||||
}
|
||||
|
||||
// Slice reads a []any value from a map at key k.
|
||||
func Slice(m map[string]any, k string) []any {
|
||||
v, _ := m[k].([]any)
|
||||
return v
|
||||
}
|
||||
|
||||
// Vec3 converts x, y and z values in an NBT map to an mgl64.Vec3.
|
||||
func Vec3(x map[string]any, k string) mgl64.Vec3 {
|
||||
if i, ok := x[k].([]any); ok {
|
||||
if len(i) != 3 {
|
||||
return mgl64.Vec3{}
|
||||
}
|
||||
var v mgl64.Vec3
|
||||
for index, f := range i {
|
||||
f32, _ := f.(float32)
|
||||
v[index] = float64(f32)
|
||||
}
|
||||
return v
|
||||
} else if i, ok := x[k].([]float32); ok {
|
||||
if len(i) != 3 {
|
||||
return mgl64.Vec3{}
|
||||
}
|
||||
return mgl64.Vec3{float64(i[0]), float64(i[1]), float64(i[2])}
|
||||
}
|
||||
return mgl64.Vec3{}
|
||||
}
|
||||
|
||||
// Vec3ToFloat32Slice converts an mgl64.Vec3 to a []float32 with 3 elements.
|
||||
func Vec3ToFloat32Slice(x mgl64.Vec3) []float32 {
|
||||
return []float32{float32(x[0]), float32(x[1]), float32(x[2])}
|
||||
}
|
||||
|
||||
// Pos converts x, y and z values in an NBT map to a cube.Pos.
|
||||
func Pos(x map[string]any, k string) cube.Pos {
|
||||
if i, ok := x[k].([]any); ok {
|
||||
if len(i) != 3 {
|
||||
return cube.Pos{}
|
||||
}
|
||||
var v cube.Pos
|
||||
for index, f := range i {
|
||||
f32, _ := f.(int32)
|
||||
v[index] = int(f32)
|
||||
}
|
||||
return v
|
||||
} else if i, ok := x[k].([]int32); ok {
|
||||
if len(i) != 3 {
|
||||
return cube.Pos{}
|
||||
}
|
||||
return cube.Pos{int(i[0]), int(i[1]), int(i[2])}
|
||||
}
|
||||
return cube.Pos{}
|
||||
}
|
||||
|
||||
// PosToInt32Slice converts a cube.Pos to a []int32 with 3 elements.
|
||||
func PosToInt32Slice(x cube.Pos) []int32 {
|
||||
return []int32{int32(x[0]), int32(x[1]), int32(x[2])}
|
||||
}
|
||||
|
||||
// MapItem converts an item's name, count, damage (and properties when it is a block) in a map obtained by decoding NBT
|
||||
// to a world.Item.
|
||||
func MapItem(x map[string]any, k string) item.Stack {
|
||||
if m, ok := x[k].(map[string]any); ok {
|
||||
tag, ok := m["tag"].(map[string]any)
|
||||
if !ok {
|
||||
tag = map[string]any{}
|
||||
}
|
||||
|
||||
s := readItemStack(m, tag)
|
||||
readDamage(tag, &s, true)
|
||||
readEnchantments(tag, &s)
|
||||
readDisplay(tag, &s)
|
||||
readDragonflyData(tag, &s)
|
||||
return s
|
||||
}
|
||||
return item.Stack{}
|
||||
}
|
||||
|
||||
// Item decodes the data of an item into an item stack.
|
||||
func Item(data map[string]any, s *item.Stack) item.Stack {
|
||||
disk, tag := s == nil, data
|
||||
if disk {
|
||||
a := readItemStack(data)
|
||||
t, ok := data["tag"].(map[string]any)
|
||||
if !ok {
|
||||
t = map[string]any{}
|
||||
}
|
||||
tag = t
|
||||
|
||||
a := readItemStack(data, tag)
|
||||
s = &a
|
||||
}
|
||||
readDamage(data, s, disk)
|
||||
readAnvilCost(data, s)
|
||||
readDisplay(data, s)
|
||||
readEnchantments(data, s)
|
||||
readDragonflyData(data, s)
|
||||
|
||||
readAnvilCost(tag, s)
|
||||
readDamage(tag, s, disk)
|
||||
readDisplay(tag, s)
|
||||
readDragonflyData(tag, s)
|
||||
readEnchantments(tag, s)
|
||||
return *s
|
||||
}
|
||||
|
||||
// ReadBlock decodes the data of a block into a world.Block.
|
||||
func ReadBlock(m map[string]any) world.Block {
|
||||
name, _ := m["name"].(string)
|
||||
properties, _ := m["states"].(map[string]any)
|
||||
b, _ := world.BlockByName(name, properties)
|
||||
return b
|
||||
// Block decodes the data of a block into a world.Block.
|
||||
func Block(m map[string]any, k string) world.Block {
|
||||
if mk, ok := m[k].(map[string]any); ok {
|
||||
name, _ := mk["name"].(string)
|
||||
properties, _ := mk["states"].(map[string]any)
|
||||
b, _ := world.BlockByName(name, properties)
|
||||
return b
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// readItemStack reads an item.Stack from the NBT in the map passed.
|
||||
func readItemStack(m map[string]any) item.Stack {
|
||||
func readItemStack(m, t map[string]any) item.Stack {
|
||||
var it world.Item
|
||||
if blockItem, ok := MapBlock(m, "Block").(world.Item); ok {
|
||||
if blockItem, ok := Block(m, "Block").(world.Item); ok {
|
||||
it = blockItem
|
||||
}
|
||||
if v, ok := world.ItemByName(Map[string](m, "Name"), Map[int16](m, "Damage")); ok {
|
||||
if v, ok := world.ItemByName(String(m, "Name"), Int16(m, "Damage")); ok {
|
||||
it = v
|
||||
}
|
||||
if it == nil {
|
||||
return item.Stack{}
|
||||
}
|
||||
if n, ok := it.(world.NBTer); ok {
|
||||
it = n.DecodeNBT(m).(world.Item)
|
||||
it = n.DecodeNBT(t).(world.Item)
|
||||
}
|
||||
return item.NewStack(it, int(Map[byte](m, "Count")))
|
||||
return item.NewStack(it, int(Uint8(m, "Count")))
|
||||
}
|
||||
|
||||
// readDamage reads the damage value stored in the NBT with the Damage tag and saves it to the item.Stack passed.
|
||||
func readDamage(m map[string]any, s *item.Stack, disk bool) {
|
||||
if disk {
|
||||
*s = s.Damage(int(Map[int16](m, "Damage")))
|
||||
*s = s.Damage(int(Int16(m, "Damage")))
|
||||
return
|
||||
}
|
||||
*s = s.Damage(int(Map[int32](m, "Damage")))
|
||||
*s = s.Damage(int(Int32(m, "Damage")))
|
||||
}
|
||||
|
||||
// readAnvilCost ...
|
||||
func readAnvilCost(m map[string]any, s *item.Stack) {
|
||||
*s = s.WithAnvilCost(int(Map[int32](m, "RepairCost")))
|
||||
*s = s.WithAnvilCost(int(Int32(m, "RepairCost")))
|
||||
}
|
||||
|
||||
// readEnchantments reads the enchantments stored in the ench tag of the NBT passed and stores it into an item.Stack.
|
||||
func readEnchantments(m map[string]any, s *item.Stack) {
|
||||
enchantments, ok := m["ench"].([]map[string]any)
|
||||
if !ok {
|
||||
for _, e := range Map[[]any](m, "ench") {
|
||||
for _, e := range Slice(m, "ench") {
|
||||
if v, ok := e.(map[string]any); ok {
|
||||
enchantments = append(enchantments, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, ench := range enchantments {
|
||||
if t, ok := item.EnchantmentByID(int(Map[int16](ench, "id"))); ok {
|
||||
*s = s.WithEnchantments(item.NewEnchantment(t, int(Map[int16](ench, "lvl"))))
|
||||
if t, ok := item.EnchantmentByID(int(Int16(ench, "id"))); ok {
|
||||
*s = s.WithEnchantments(item.NewEnchantment(t, int(Int16(ench, "lvl"))))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,21 +11,27 @@ import (
|
|||
|
||||
// WriteItem encodes an item stack into a map that can be encoded using NBT.
|
||||
func WriteItem(s item.Stack, disk bool) map[string]any {
|
||||
m := make(map[string]any)
|
||||
tag := make(map[string]any)
|
||||
if nbt, ok := s.Item().(world.NBTer); ok {
|
||||
for k, v := range nbt.EncodeNBT() {
|
||||
m[k] = v
|
||||
tag[k] = v
|
||||
}
|
||||
}
|
||||
writeAnvilCost(tag, s)
|
||||
writeDamage(tag, s, disk)
|
||||
writeDisplay(tag, s)
|
||||
writeDragonflyData(tag, s)
|
||||
writeEnchantments(tag, s)
|
||||
|
||||
data := make(map[string]any)
|
||||
if disk {
|
||||
writeItemStack(m, s)
|
||||
writeItemStack(data, tag, s)
|
||||
} else {
|
||||
for k, v := range tag {
|
||||
data[k] = v
|
||||
}
|
||||
}
|
||||
writeDamage(m, s, disk)
|
||||
writeAnvilCost(m, s)
|
||||
writeDisplay(m, s)
|
||||
writeEnchantments(m, s)
|
||||
writeDragonflyData(m, s)
|
||||
return m
|
||||
return data
|
||||
}
|
||||
|
||||
// WriteBlock encodes a world.Block into a map that can be encoded using NBT.
|
||||
|
@ -39,7 +45,7 @@ func WriteBlock(b world.Block) map[string]any {
|
|||
}
|
||||
|
||||
// writeItemStack writes the name, metadata value, count and NBT of an item to a map ready for NBT encoding.
|
||||
func writeItemStack(m map[string]any, s item.Stack) {
|
||||
func writeItemStack(m, t map[string]any, s item.Stack) {
|
||||
m["Name"], m["Damage"] = s.Item().EncodeItem()
|
||||
if b, ok := s.Item().(world.Block); ok {
|
||||
v := map[string]any{}
|
||||
|
@ -47,6 +53,9 @@ func writeItemStack(m map[string]any, s item.Stack) {
|
|||
m["Block"] = v
|
||||
}
|
||||
m["Count"] = byte(s.Count())
|
||||
if len(t) > 0 {
|
||||
m["tag"] = t
|
||||
}
|
||||
}
|
||||
|
||||
// writeBlock writes the name, properties and version of a block to a map ready for NBT encoding.
|
||||
|
|
19
utils/net.go
19
utils/net.go
|
@ -1,8 +1,12 @@
|
|||
package utils
|
||||
|
||||
import "net"
|
||||
import (
|
||||
"net"
|
||||
|
||||
var PrivateIPNetworks = []net.IPNet{
|
||||
"github.com/repeale/fp-go"
|
||||
)
|
||||
|
||||
var privateIPNetworks = []net.IPNet{
|
||||
{
|
||||
IP: net.ParseIP("10.0.0.0"),
|
||||
Mask: net.CIDRMask(8, 32),
|
||||
|
@ -17,14 +21,11 @@ var PrivateIPNetworks = []net.IPNet{
|
|||
},
|
||||
}
|
||||
|
||||
// check if ip is private
|
||||
// IPPrivate checks if ip is private
|
||||
func IPPrivate(ip net.IP) bool {
|
||||
for _, ipNet := range PrivateIPNetworks {
|
||||
if ipNet.Contains(ip) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
return fp.Some(func(ipNet net.IPNet) bool {
|
||||
return ipNet.Contains(ip)
|
||||
})(privateIPNetworks)
|
||||
}
|
||||
|
||||
// GetLocalIP returns the non loopback local IP of the host
|
||||
|
|
|
@ -1,93 +0,0 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net"
|
||||
"reflect"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/sandertv/gophertunnel/minecraft/protocol"
|
||||
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
var Pool = packet.NewPool()
|
||||
|
||||
var MutedPackets = []string{
|
||||
"packet.UpdateBlock",
|
||||
"packet.MoveActorAbsolute",
|
||||
"packet.SetActorMotion",
|
||||
"packet.SetTime",
|
||||
"packet.RemoveActor",
|
||||
"packet.AddActor",
|
||||
"packet.UpdateAttributes",
|
||||
"packet.Interact",
|
||||
"packet.LevelEvent",
|
||||
"packet.SetActorData",
|
||||
"packet.MoveActorDelta",
|
||||
"packet.MovePlayer",
|
||||
"packet.BlockActorData",
|
||||
"packet.PlayerAuthInput",
|
||||
"packet.LevelChunk",
|
||||
"packet.LevelSoundEvent",
|
||||
"packet.ActorEvent",
|
||||
"packet.NetworkChunkPublisherUpdate",
|
||||
"packet.UpdateSubChunkBlocks",
|
||||
"packet.SubChunk",
|
||||
"packet.SubChunkRequest",
|
||||
"packet.Animate",
|
||||
"packet.NetworkStackLatency",
|
||||
"packet.InventoryTransaction",
|
||||
"packet.PlaySound",
|
||||
}
|
||||
|
||||
var ExtraVerbose []string
|
||||
|
||||
func PacketLogger(header packet.Header, payload []byte, src, dst net.Addr) {
|
||||
var pk packet.Packet
|
||||
if pkFunc, ok := Pool[header.PacketID]; ok {
|
||||
pk = pkFunc()
|
||||
} else {
|
||||
pk = &packet.Unknown{PacketID: header.PacketID}
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if recoveredErr := recover(); recoveredErr != nil {
|
||||
logrus.Errorf("%T: %w", pk, recoveredErr.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
pk.Unmarshal(protocol.NewReader(bytes.NewBuffer(payload), 0))
|
||||
|
||||
pk_name := reflect.TypeOf(pk).String()[1:]
|
||||
if slices.Contains(MutedPackets, pk_name) {
|
||||
return
|
||||
}
|
||||
|
||||
switch pk := pk.(type) {
|
||||
case *packet.Disconnect:
|
||||
logrus.Infof("Disconnect: %s", pk.Message)
|
||||
}
|
||||
|
||||
dir_S2C := color.GreenString("S") + "->" + color.CyanString("C")
|
||||
dir_C2S := color.CyanString("C") + "->" + color.GreenString("S")
|
||||
var dir string = dir_S2C
|
||||
|
||||
if Client_addr != nil {
|
||||
if src == Client_addr {
|
||||
dir = dir_C2S
|
||||
}
|
||||
} else {
|
||||
src_addr, _, _ := net.SplitHostPort(src.String())
|
||||
if IPPrivate(net.ParseIP(src_addr)) {
|
||||
dir = dir_C2S
|
||||
}
|
||||
}
|
||||
|
||||
logrus.Debugf("%s 0x%02x, %s", dir, pk.ID(), pk_name)
|
||||
|
||||
if slices.Contains(ExtraVerbose, pk_name) {
|
||||
logrus.Debugf("%+v", pk)
|
||||
}
|
||||
}
|
546
utils/proxy.go
546
utils/proxy.go
|
@ -1,22 +1,32 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/bedrock-tool/bedrocktool/locale"
|
||||
"github.com/repeale/fp-go"
|
||||
"github.com/sandertv/gophertunnel/minecraft"
|
||||
"github.com/sandertv/gophertunnel/minecraft/protocol"
|
||||
"github.com/sandertv/gophertunnel/minecraft/protocol/login"
|
||||
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
|
||||
"github.com/sandertv/gophertunnel/minecraft/resource"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var G_disconnect_reason = "Connection lost"
|
||||
var DisconnectReason = "Connection lost"
|
||||
|
||||
/*
|
||||
type dummyProto struct {
|
||||
id int32
|
||||
ver string
|
||||
|
@ -32,60 +42,180 @@ func (p dummyProto) ConvertToLatest(pk packet.Packet, _ *minecraft.Conn) []packe
|
|||
func (p dummyProto) ConvertFromLatest(pk packet.Packet, _ *minecraft.Conn) []packet.Packet {
|
||||
return []packet.Packet{pk}
|
||||
}
|
||||
|
||||
type ProxyContext struct {
|
||||
Server *minecraft.Conn
|
||||
Client *minecraft.Conn
|
||||
Listener *minecraft.Listener
|
||||
commands map[string]IngameCommand
|
||||
|
||||
log *logrus.Logger
|
||||
|
||||
// called for every packet
|
||||
PacketFunc PacketFunc
|
||||
// called after game started
|
||||
ConnectCB ConnectCallback
|
||||
// called on every packet after login
|
||||
PacketCB PacketCallback
|
||||
}
|
||||
*/
|
||||
|
||||
type (
|
||||
PacketFunc func(header packet.Header, payload []byte, src, dst net.Addr)
|
||||
PacketCallback func(pk packet.Packet, proxy *ProxyContext, toServer bool) (packet.Packet, error)
|
||||
ConnectCallback func(proxy *ProxyContext)
|
||||
PacketFunc func(header packet.Header, payload []byte, src, dst net.Addr)
|
||||
IngameCommand struct {
|
||||
Exec func(cmdline []string) bool
|
||||
Cmd protocol.Command
|
||||
}
|
||||
)
|
||||
|
||||
func (p *ProxyContext) SendMessage(text string) {
|
||||
if p.Client != nil {
|
||||
p.Client.WritePacket(&packet.Text{
|
||||
TextType: packet.TextTypeSystem,
|
||||
Message: "§8[§bBedrocktool§8]§r " + text,
|
||||
})
|
||||
}
|
||||
type ProxyHandler struct {
|
||||
Name string
|
||||
ProxyRef func(pc *ProxyContext)
|
||||
//
|
||||
AddressAndName func(address, hostname string) error
|
||||
|
||||
// called to change game data
|
||||
GameDataModifier func(gd *minecraft.GameData)
|
||||
|
||||
// called for every packet
|
||||
PacketFunc func(header packet.Header, payload []byte, src, dst net.Addr)
|
||||
|
||||
// called on every packet after login
|
||||
PacketCB func(pk packet.Packet, toServer bool, timeReceived time.Time) (packet.Packet, error)
|
||||
|
||||
// called after client connected
|
||||
OnClientConnect func(conn minecraft.IConn)
|
||||
SecondaryClientCB func(conn minecraft.IConn)
|
||||
|
||||
// called after server connected & downloaded resource packs
|
||||
OnServerConnect func() (cancel bool)
|
||||
// called after game started
|
||||
ConnectCB func(err error) bool
|
||||
|
||||
// called when the proxy stops
|
||||
OnEnd func()
|
||||
}
|
||||
|
||||
func (p *ProxyContext) SendPopup(text string) {
|
||||
if p.Client != nil {
|
||||
p.Client.WritePacket(&packet.Text{
|
||||
TextType: packet.TextTypePopup,
|
||||
Message: text,
|
||||
})
|
||||
}
|
||||
type ProxyContext struct {
|
||||
Server minecraft.IConn
|
||||
Client minecraft.IConn
|
||||
clientAddr net.Addr
|
||||
Listener *minecraft.Listener
|
||||
|
||||
AlwaysGetPacks bool
|
||||
WithClient bool
|
||||
IgnoreDisconnect bool
|
||||
CustomClientData *login.ClientData
|
||||
|
||||
commands map[string]IngameCommand
|
||||
handlers []*ProxyHandler
|
||||
}
|
||||
|
||||
type IngameCommand struct {
|
||||
Exec func(cmdline []string) bool
|
||||
Cmd protocol.Command
|
||||
func NewProxy() (*ProxyContext, error) {
|
||||
p := &ProxyContext{
|
||||
commands: make(map[string]IngameCommand),
|
||||
AlwaysGetPacks: false,
|
||||
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
|
||||
}
|
||||
|
||||
func (p *ProxyContext) CommandHandlerPacketCB(pk packet.Packet, proxy *ProxyContext, toServer bool) (packet.Packet, error) {
|
||||
switch _pk := pk.(type) {
|
||||
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) ClientWritePacket(pk packet.Packet) error {
|
||||
if p.Client == nil {
|
||||
return nil
|
||||
}
|
||||
return p.Client.WritePacket(pk)
|
||||
}
|
||||
|
||||
func (p *ProxyContext) SendMessage(text string) {
|
||||
p.ClientWritePacket(&packet.Text{
|
||||
TextType: packet.TextTypeSystem,
|
||||
Message: "§8[§bBedrocktool§8]§r " + text,
|
||||
})
|
||||
}
|
||||
|
||||
func (p *ProxyContext) SendPopup(text string) {
|
||||
p.ClientWritePacket(&packet.Text{
|
||||
TextType: packet.TextTypePopup,
|
||||
Message: text,
|
||||
})
|
||||
}
|
||||
|
||||
func (p *ProxyContext) AddHandler(handler *ProxyHandler) {
|
||||
p.handlers = append(p.handlers, handler)
|
||||
}
|
||||
|
||||
func (p *ProxyContext) CommandHandlerPacketCB(pk packet.Packet, toServer bool, _ time.Time) (packet.Packet, error) {
|
||||
switch pk := pk.(type) {
|
||||
case *packet.CommandRequest:
|
||||
cmd := strings.Split(_pk.CommandLine, " ")
|
||||
cmd := strings.Split(pk.CommandLine, " ")
|
||||
name := cmd[0][1:]
|
||||
if h, ok := p.commands[name]; ok {
|
||||
if h.Exec(cmd[1:]) {
|
||||
|
@ -93,26 +223,26 @@ func (p *ProxyContext) CommandHandlerPacketCB(pk packet.Packet, proxy *ProxyCont
|
|||
}
|
||||
}
|
||||
case *packet.AvailableCommands:
|
||||
cmds := make([]protocol.Command, len(p.commands))
|
||||
cmds := make([]protocol.Command, 0, len(p.commands))
|
||||
for _, ic := range p.commands {
|
||||
cmds = append(cmds, ic.Cmd)
|
||||
}
|
||||
pk = &packet.AvailableCommands{
|
||||
Constraints: _pk.Constraints,
|
||||
Commands: append(_pk.Commands, cmds...),
|
||||
Constraints: pk.Constraints,
|
||||
Commands: append(pk.Commands, cmds...),
|
||||
}
|
||||
}
|
||||
return pk, nil
|
||||
}
|
||||
|
||||
func proxyLoop(ctx context.Context, proxy *ProxyContext, toServer bool, packetCBs []PacketCallback) error {
|
||||
var c1, c2 *minecraft.Conn
|
||||
func (p *ProxyContext) proxyLoop(ctx context.Context, toServer bool) error {
|
||||
var c1, c2 minecraft.IConn
|
||||
if toServer {
|
||||
c1 = proxy.Client
|
||||
c2 = proxy.Server
|
||||
c1 = p.Client
|
||||
c2 = p.Server
|
||||
} else {
|
||||
c1 = proxy.Server
|
||||
c2 = proxy.Client
|
||||
c1 = p.Server
|
||||
c2 = p.Client
|
||||
}
|
||||
|
||||
for {
|
||||
|
@ -125,17 +255,24 @@ func proxyLoop(ctx context.Context, proxy *ProxyContext, toServer bool, packetCB
|
|||
return err
|
||||
}
|
||||
|
||||
for _, packetCB := range packetCBs {
|
||||
pk, err = packetCB(pk, proxy, toServer)
|
||||
if err != nil {
|
||||
return err
|
||||
pkName := reflect.TypeOf(pk).String()
|
||||
for _, handler := range p.handlers {
|
||||
if handler.PacketCB != nil {
|
||||
pk, err = handler.PacketCB(pk, toServer, time.Now())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if pk == nil {
|
||||
logrus.Tracef("Dropped Packet: %s", pkName)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if pk != nil {
|
||||
if pk != nil && c2 != nil {
|
||||
if err := c2.WritePacket(pk); err != nil {
|
||||
if disconnect, ok := errors.Unwrap(err).(minecraft.DisconnectError); ok {
|
||||
G_disconnect_reason = disconnect.Error()
|
||||
DisconnectReason = disconnect.Error()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
@ -143,111 +280,276 @@ func proxyLoop(ctx context.Context, proxy *ProxyContext, toServer bool, packetCB
|
|||
}
|
||||
}
|
||||
|
||||
func NewProxy(log *logrus.Logger) *ProxyContext {
|
||||
if log == nil {
|
||||
log = logrus.StandardLogger()
|
||||
}
|
||||
return &ProxyContext{
|
||||
log: log,
|
||||
commands: make(map[string]IngameCommand),
|
||||
}
|
||||
// Disconnect disconnects both the client and server
|
||||
func (p *ProxyContext) Disconnect() {
|
||||
p.DisconnectClient()
|
||||
p.DisconnectServer()
|
||||
}
|
||||
|
||||
var Client_addr net.Addr
|
||||
|
||||
func (p *ProxyContext) Run(ctx context.Context, server_address string) (err error) {
|
||||
if strings.HasSuffix(server_address, ".pcap") {
|
||||
return fmt.Errorf("not supported anymore")
|
||||
}
|
||||
if strings.HasSuffix(server_address, ".pcap2") {
|
||||
return create_replay_connection(ctx, p.log, server_address, p.ConnectCB, p.PacketCB)
|
||||
// Disconnect disconnects the client
|
||||
func (p *ProxyContext) DisconnectClient() {
|
||||
if p.Client == nil {
|
||||
return
|
||||
}
|
||||
p.Client.Close()
|
||||
}
|
||||
|
||||
// Disconnect disconnects from the server
|
||||
func (p *ProxyContext) DisconnectServer() {
|
||||
if p.Server == nil {
|
||||
return
|
||||
}
|
||||
p.Server.Close()
|
||||
}
|
||||
|
||||
func (p *ProxyContext) IsClient(addr net.Addr) bool {
|
||||
return p.clientAddr.String() == addr.String()
|
||||
}
|
||||
|
||||
var NewDebugLogger func(bool) *ProxyHandler
|
||||
var NewPacketCapturer func() *ProxyHandler
|
||||
|
||||
func (p *ProxyContext) connectClient(ctx context.Context, serverAddress string, cdpp **login.ClientData) (err error) {
|
||||
GetTokenSource() // ask for login before listening
|
||||
|
||||
var packs []*resource.Pack
|
||||
if G_preload_packs {
|
||||
p.log.Info("Preloading resourcepacks")
|
||||
var serverConn *minecraft.Conn
|
||||
serverConn, err = ConnectServer(ctx, server_address, nil, true, nil)
|
||||
if Options.Preload {
|
||||
logrus.Info(locale.Loc("preloading_packs", nil))
|
||||
serverConn, err := connectServer(ctx, serverAddress, nil, true, func(header packet.Header, payload []byte, src, dst net.Addr) {})
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed to connect to %s: %s", server_address, err)
|
||||
return
|
||||
return fmt.Errorf(locale.Loc("failed_to_connect", locale.Strmap{"Address": serverAddress, "Err": err}))
|
||||
}
|
||||
serverConn.Close()
|
||||
packs = serverConn.ResourcePacks()
|
||||
p.log.Infof("%d packs loaded", len(packs))
|
||||
logrus.Infof(locale.Locm("pack_count_loaded", locale.Strmap{"Count": len(packs)}, len(packs)))
|
||||
}
|
||||
|
||||
_status := minecraft.NewStatusProvider("Server")
|
||||
status := minecraft.NewStatusProvider(fmt.Sprintf("%s Proxy", serverAddress))
|
||||
p.Listener, err = minecraft.ListenConfig{
|
||||
StatusProvider: _status,
|
||||
ResourcePacks: packs,
|
||||
StatusProvider: status,
|
||||
ResourcePacks: packs,
|
||||
AcceptedProtocols: []minecraft.Protocol{
|
||||
dummyProto{id: 544, ver: "1.19.20"},
|
||||
//dummyProto{id: 567, ver: "1.19.60"},
|
||||
},
|
||||
}.Listen("raknet", ":19132")
|
||||
if err != nil {
|
||||
return
|
||||
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")
|
||||
logrus.Infof(locale.Loc("listening_on", locale.Strmap{"Address": p.Listener.Addr()}))
|
||||
logrus.Infof(locale.Loc("help_connect", nil))
|
||||
|
||||
var c net.Conn
|
||||
c, err = p.Listener.Accept()
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
p.Listener.Close()
|
||||
}()
|
||||
|
||||
c, err := p.Listener.Accept()
|
||||
if err != nil {
|
||||
p.log.Fatal(err)
|
||||
return err
|
||||
}
|
||||
p.Client = c.(*minecraft.Conn)
|
||||
|
||||
cd := p.Client.ClientData()
|
||||
p.Server, err = ConnectServer(ctx, server_address, &cd, false, p.PacketFunc)
|
||||
*cdpp = &cd
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *ProxyContext) Run(ctx context.Context, serverAddress, name string) (err error) {
|
||||
if Options.Debug || Options.ExtraDebug {
|
||||
p.AddHandler(NewDebugLogger(Options.ExtraDebug))
|
||||
}
|
||||
if Options.Capture {
|
||||
p.AddHandler(NewPacketCapturer())
|
||||
}
|
||||
p.AddHandler(&ProxyHandler{
|
||||
Name: "Commands",
|
||||
PacketCB: p.CommandHandlerPacketCB,
|
||||
})
|
||||
|
||||
for _, handler := range p.handlers {
|
||||
if handler.AddressAndName != nil {
|
||||
handler.AddressAndName(serverAddress, name)
|
||||
}
|
||||
if handler.ProxyRef != nil {
|
||||
handler.ProxyRef(p)
|
||||
}
|
||||
}
|
||||
|
||||
defer func() {
|
||||
for _, handler := range p.handlers {
|
||||
if handler.OnEnd != nil {
|
||||
handler.OnEnd()
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
isReplay := false
|
||||
if strings.HasPrefix(serverAddress, "PCAP!") {
|
||||
isReplay = true
|
||||
}
|
||||
|
||||
var cdp *login.ClientData = nil
|
||||
if p.WithClient && !isReplay {
|
||||
err = p.connectClient(ctx, serverAddress, &cdp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if p.Listener != nil {
|
||||
if p.Client != nil {
|
||||
p.Listener.Disconnect(p.Client.(*minecraft.Conn), DisconnectReason)
|
||||
}
|
||||
p.Listener.Close()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
if p.CustomClientData != nil {
|
||||
cdp = p.CustomClientData
|
||||
}
|
||||
|
||||
for _, handler := range p.handlers {
|
||||
if handler.OnClientConnect == nil {
|
||||
continue
|
||||
}
|
||||
handler.OnClientConnect(p.Client)
|
||||
}
|
||||
|
||||
packetFunc := func(header packet.Header, payload []byte, src, dst net.Addr) {
|
||||
if header.PacketID == packet.IDRequestNetworkSettings {
|
||||
p.clientAddr = src
|
||||
}
|
||||
for _, handler := range p.handlers {
|
||||
if handler.PacketFunc == nil {
|
||||
continue
|
||||
}
|
||||
handler.PacketFunc(header, payload, src, dst)
|
||||
}
|
||||
}
|
||||
|
||||
if isReplay {
|
||||
p.Server, err = createReplayConnector(serverAddress[5:], packetFunc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
p.Server, err = connectServer(ctx, serverAddress, cdp, p.AlwaysGetPacks, 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
|
||||
}
|
||||
for _, handler := range p.handlers {
|
||||
if handler.ConnectCB == nil {
|
||||
continue
|
||||
}
|
||||
ignore := handler.ConnectCB(err)
|
||||
if ignore {
|
||||
err = nil
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
err = fmt.Errorf(locale.Loc("failed_to_connect", locale.Strmap{"Address": serverAddress, "Err": err}))
|
||||
}
|
||||
return err
|
||||
}
|
||||
defer p.Server.Close()
|
||||
defer p.Listener.Disconnect(p.Client, G_disconnect_reason)
|
||||
|
||||
if p.ConnectCB != nil {
|
||||
p.ConnectCB(p)
|
||||
for _, handler := range p.handlers {
|
||||
if handler.OnServerConnect == nil {
|
||||
continue
|
||||
}
|
||||
cancel := handler.OnServerConnect()
|
||||
if cancel {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
gd := p.Server.GameData()
|
||||
for _, handler := range p.handlers {
|
||||
if handler.GameDataModifier != nil {
|
||||
handler.GameDataModifier(&gd)
|
||||
}
|
||||
}
|
||||
|
||||
// spawn and start the game
|
||||
if err = spawnConn(ctx, p.Client, p.Server, gd); err != nil {
|
||||
err = fmt.Errorf(locale.Loc("failed_to_spawn", locale.Strmap{"Err": err}))
|
||||
return err
|
||||
}
|
||||
|
||||
for _, handler := range p.handlers {
|
||||
if handler.ConnectCB == nil {
|
||||
continue
|
||||
}
|
||||
if !handler.ConnectCB(nil) {
|
||||
logrus.Info("Disconnecting")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
cbs := []PacketCallback{
|
||||
p.CommandHandlerPacketCB,
|
||||
}
|
||||
if p.PacketCB != nil {
|
||||
cbs = append(cbs, p.PacketCB)
|
||||
doProxy := func(client bool, onErr func()) {
|
||||
defer wg.Done()
|
||||
if err := p.proxyLoop(ctx, client); err != nil {
|
||||
logrus.Error(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// server to client
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
if err := proxyLoop(ctx, p, false, cbs); err != nil {
|
||||
p.log.Error(err)
|
||||
return
|
||||
}
|
||||
}()
|
||||
go doProxy(false, func() {
|
||||
p.DisconnectClient()
|
||||
})
|
||||
|
||||
// client to server
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
if err := proxyLoop(ctx, p, true, cbs); err != nil {
|
||||
p.log.Error(err)
|
||||
return
|
||||
}
|
||||
}()
|
||||
if p.Client != nil {
|
||||
wg.Add(1)
|
||||
go doProxy(true, func() {
|
||||
p.DisconnectServer()
|
||||
})
|
||||
}
|
||||
|
||||
wantSecondary := fp.Filter(func(handler *ProxyHandler) bool {
|
||||
return handler.SecondaryClientCB != nil
|
||||
})(p.handlers)
|
||||
|
||||
if len(wantSecondary) > 0 {
|
||||
go func() {
|
||||
for {
|
||||
c, err := p.Listener.Accept()
|
||||
if err != nil {
|
||||
logrus.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, handler := range wantSecondary {
|
||||
go handler.SecondaryClientCB(c.(*minecraft.Conn))
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
return err
|
||||
}
|
||||
|
||||
var pool = packet.NewPool()
|
||||
|
||||
func DecodePacket(header packet.Header, payload []byte) packet.Packet {
|
||||
var pk packet.Packet
|
||||
if pkFunc, ok := pool[header.PacketID]; ok {
|
||||
pk = pkFunc()
|
||||
} else {
|
||||
pk = &packet.Unknown{PacketID: header.PacketID, Payload: payload}
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if recoveredErr := recover(); recoveredErr != nil {
|
||||
logrus.Errorf("%T: %s", pk, recoveredErr.(error))
|
||||
}
|
||||
}()
|
||||
pk.Marshal(protocol.NewReader(bytes.NewBuffer(payload), 0))
|
||||
return pk
|
||||
}
|
||||
|
|
|
@ -2,22 +2,17 @@ package utils
|
|||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/google/subcommands"
|
||||
"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) {
|
||||
realms, err := api.Realms(ctx)
|
||||
func getRealm(ctx context.Context, realmName, id string) (name string, address string, err error) {
|
||||
realms, err := GetRealmsAPI().Realms(ctx)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
for _, realm := range realms {
|
||||
if strings.HasPrefix(realm.Name, realm_name) {
|
||||
if strings.HasPrefix(realm.Name, realmName) {
|
||||
if id != "" && id != fmt.Sprint(id) {
|
||||
continue
|
||||
}
|
||||
|
@ -31,30 +26,3 @@ func get_realm(ctx context.Context, api *realms.Client, realm_name, id string) (
|
|||
}
|
||||
return "", "", fmt.Errorf("realm not found")
|
||||
}
|
||||
|
||||
type RealmListCMD struct{}
|
||||
|
||||
func (*RealmListCMD) Name() string { return "list-realms" }
|
||||
func (*RealmListCMD) Synopsis() string { return "prints all realms you have access to" }
|
||||
|
||||
func (c *RealmListCMD) SetFlags(f *flag.FlagSet) {}
|
||||
func (c *RealmListCMD) Usage() string {
|
||||
return c.Name() + ": " + c.Synopsis() + "\n"
|
||||
}
|
||||
|
||||
func (c *RealmListCMD) Execute(ctx context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
|
||||
api := realms.NewClient(GetTokenSource())
|
||||
realms, err := api.Realms(ctx)
|
||||
if err != nil {
|
||||
logrus.Error(err)
|
||||
return 1
|
||||
}
|
||||
for _, realm := range realms {
|
||||
fmt.Printf("Name: %s\tid: %d\n", realm.Name, realm.ID)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func init() {
|
||||
RegisterCommand(&RealmListCMD{})
|
||||
}
|
||||
|
|
524
utils/replay.go
524
utils/replay.go
|
@ -1,128 +1,466 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"reflect"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/sandertv/gophertunnel/minecraft"
|
||||
"github.com/sandertv/gophertunnel/minecraft/protocol/login"
|
||||
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
|
||||
"github.com/sandertv/gophertunnel/minecraft/resource"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func create_replay_connection(ctx context.Context, log *logrus.Logger, filename string, onConnect ConnectCallback, packetCB PacketCallback) error {
|
||||
log.Infof("Reading replay %s", filename)
|
||||
type replayHeader struct {
|
||||
Version int32
|
||||
}
|
||||
|
||||
f, err := os.Open(filename)
|
||||
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)
|
||||
}
|
||||
|
||||
type replayConnector struct {
|
||||
f *os.File
|
||||
totalSize int64
|
||||
ver int
|
||||
|
||||
packets chan packet.Packet
|
||||
spawn chan struct{}
|
||||
close chan struct{}
|
||||
once sync.Once
|
||||
|
||||
pool packet.Pool
|
||||
proto minecraft.Protocol
|
||||
clientData login.ClientData
|
||||
|
||||
gameData minecraft.GameData
|
||||
|
||||
packetFunc PacketFunc
|
||||
|
||||
downloadingPacks map[string]*downloadingPack
|
||||
resourcePacks []*resource.Pack
|
||||
}
|
||||
|
||||
// downloadingPack is a resource pack that is being downloaded by a client connection.
|
||||
type downloadingPack struct {
|
||||
buf *bytes.Buffer
|
||||
chunkSize uint32
|
||||
size uint64
|
||||
expectedIndex uint32
|
||||
newFrag chan []byte
|
||||
contentKey string
|
||||
}
|
||||
|
||||
func (r *replayConnector) readHeader() error {
|
||||
r.ver = 1
|
||||
|
||||
magic := make([]byte, 4)
|
||||
io.ReadAtLeast(r.f, magic, 4)
|
||||
if bytes.Equal(magic, replayMagic) {
|
||||
var header replayHeader
|
||||
if err := binary.Read(r.f, binary.LittleEndian, &header); err != nil {
|
||||
return err
|
||||
}
|
||||
r.ver = int(header.Version)
|
||||
} else {
|
||||
logrus.Info("Version 1 capture assumed.")
|
||||
r.f.Seek(-4, io.SeekCurrent)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *replayConnector) readPacket() (payload []byte, toServer bool, err error) {
|
||||
var magic uint32 = 0
|
||||
var packetLength uint32 = 0
|
||||
timeReceived := time.Now()
|
||||
|
||||
offset, _ := r.f.Seek(0, io.SeekCurrent)
|
||||
if offset == r.totalSize {
|
||||
logrus.Info("Reached End")
|
||||
return nil, toServer, nil
|
||||
}
|
||||
|
||||
binary.Read(r.f, binary.LittleEndian, &magic)
|
||||
if magic != 0xAAAAAAAA {
|
||||
return nil, toServer, fmt.Errorf("wrong Magic")
|
||||
}
|
||||
binary.Read(r.f, binary.LittleEndian, &packetLength)
|
||||
binary.Read(r.f, binary.LittleEndian, &toServer)
|
||||
if r.ver >= 2 {
|
||||
var timeMs int64
|
||||
binary.Read(r.f, binary.LittleEndian, &timeMs)
|
||||
timeReceived = time.UnixMilli(timeMs)
|
||||
}
|
||||
|
||||
payload = make([]byte, packetLength)
|
||||
n, err := r.f.Read(payload)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, toServer, err
|
||||
}
|
||||
var size int64
|
||||
{
|
||||
stat, err := f.Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
if n != int(packetLength) {
|
||||
return nil, toServer, fmt.Errorf("truncated")
|
||||
}
|
||||
|
||||
var magic2 uint32
|
||||
binary.Read(r.f, binary.LittleEndian, &magic2)
|
||||
if magic2 != 0xBBBBBBBB {
|
||||
return nil, toServer, fmt.Errorf("wrong Magic2")
|
||||
}
|
||||
|
||||
_ = timeReceived
|
||||
return payload, toServer, nil
|
||||
}
|
||||
|
||||
func (r *replayConnector) handleLoginSequence(pk packet.Packet) (bool, error) {
|
||||
switch pk := pk.(type) {
|
||||
case *packet.StartGame:
|
||||
r.SetGameData(minecraft.GameData{
|
||||
WorldName: pk.WorldName,
|
||||
WorldSeed: pk.WorldSeed,
|
||||
Difficulty: pk.Difficulty,
|
||||
EntityUniqueID: pk.EntityUniqueID,
|
||||
EntityRuntimeID: pk.EntityRuntimeID,
|
||||
PlayerGameMode: pk.PlayerGameMode,
|
||||
PersonaDisabled: pk.PersonaDisabled,
|
||||
CustomSkinsDisabled: pk.CustomSkinsDisabled,
|
||||
BaseGameVersion: pk.BaseGameVersion,
|
||||
PlayerPosition: pk.PlayerPosition,
|
||||
Pitch: pk.Pitch,
|
||||
Yaw: pk.Yaw,
|
||||
Dimension: pk.Dimension,
|
||||
WorldSpawn: pk.WorldSpawn,
|
||||
EditorWorld: pk.EditorWorld,
|
||||
WorldGameMode: pk.WorldGameMode,
|
||||
GameRules: pk.GameRules,
|
||||
Time: pk.Time,
|
||||
ServerBlockStateChecksum: pk.ServerBlockStateChecksum,
|
||||
CustomBlocks: pk.Blocks,
|
||||
Items: pk.Items,
|
||||
PlayerMovementSettings: pk.PlayerMovementSettings,
|
||||
ServerAuthoritativeInventory: pk.ServerAuthoritativeInventory,
|
||||
Experiments: pk.Experiments,
|
||||
ClientSideGeneration: pk.ClientSideGeneration,
|
||||
ChatRestrictionLevel: pk.ChatRestrictionLevel,
|
||||
DisablePlayerInteractions: pk.DisablePlayerInteractions,
|
||||
})
|
||||
|
||||
case *packet.ResourcePacksInfo:
|
||||
for _, pack := range pk.TexturePacks {
|
||||
r.downloadingPacks[pack.UUID] = &downloadingPack{
|
||||
size: pack.Size,
|
||||
buf: bytes.NewBuffer(make([]byte, 0, pack.Size)),
|
||||
newFrag: make(chan []byte),
|
||||
contentKey: pack.ContentKey,
|
||||
}
|
||||
}
|
||||
size = stat.Size()
|
||||
|
||||
case *packet.ResourcePackDataInfo:
|
||||
pack, ok := r.downloadingPacks[pk.UUID]
|
||||
if !ok {
|
||||
// We either already downloaded the pack or we got sent an invalid UUID, that did not match any pack
|
||||
// sent in the ResourcePacksInfo packet.
|
||||
return false, fmt.Errorf("unknown pack to download with UUID %v", pk.UUID)
|
||||
}
|
||||
if pack.size != pk.Size {
|
||||
// Size mismatch: The ResourcePacksInfo packet had a size for the pack that did not match with the
|
||||
// size sent here.
|
||||
logrus.Printf("pack %v had a different size in the ResourcePacksInfo packet than the ResourcePackDataInfo packet\n", pk.UUID)
|
||||
pack.size = pk.Size
|
||||
}
|
||||
pack.chunkSize = pk.DataChunkSize
|
||||
|
||||
chunkCount := uint32(pk.Size / uint64(pk.DataChunkSize))
|
||||
if pk.Size%uint64(pk.DataChunkSize) != 0 {
|
||||
chunkCount++
|
||||
}
|
||||
|
||||
go func() {
|
||||
for i := uint32(0); i < chunkCount; i++ {
|
||||
select {
|
||||
case <-r.close:
|
||||
return
|
||||
case frag := <-pack.newFrag:
|
||||
// Write the fragment to the full buffer of the downloading resource pack.
|
||||
_, _ = pack.buf.Write(frag)
|
||||
}
|
||||
}
|
||||
|
||||
if pack.buf.Len() != int(pack.size) {
|
||||
logrus.Printf("incorrect resource pack size: expected %v, but got %v\n", pack.size, pack.buf.Len())
|
||||
return
|
||||
}
|
||||
|
||||
// First parse the resource pack from the total byte buffer we obtained.
|
||||
newPack, err := resource.FromBytes(pack.buf.Bytes())
|
||||
if err != nil {
|
||||
logrus.Printf("invalid full resource pack data for UUID %v: %v\n", pk.UUID, err)
|
||||
return
|
||||
}
|
||||
|
||||
r.resourcePacks = append(r.resourcePacks, newPack.WithContentKey(pack.contentKey))
|
||||
}()
|
||||
|
||||
case *packet.ResourcePackChunkData:
|
||||
pack, ok := r.downloadingPacks[pk.UUID]
|
||||
if !ok {
|
||||
// We haven't received a ResourcePackDataInfo packet from the server, so we can't use this data to
|
||||
// download a resource pack.
|
||||
return false, fmt.Errorf("resource pack chunk data for resource pack that was not being downloaded")
|
||||
}
|
||||
lastData := pack.buf.Len()+int(pack.chunkSize) >= int(pack.size)
|
||||
if !lastData && uint32(len(pk.Data)) != pack.chunkSize {
|
||||
// The chunk data didn't have the full size and wasn't the last data to be sent for the resource pack,
|
||||
// meaning we got too little data.
|
||||
return false, fmt.Errorf("resource pack chunk data had a length of %v, but expected %v", len(pk.Data), pack.chunkSize)
|
||||
}
|
||||
if pk.ChunkIndex != pack.expectedIndex {
|
||||
return false, fmt.Errorf("resource pack chunk data had chunk index %v, but expected %v", pk.ChunkIndex, pack.expectedIndex)
|
||||
}
|
||||
pack.expectedIndex++
|
||||
pack.newFrag <- pk.Data
|
||||
|
||||
case *packet.SetLocalPlayerAsInitialised:
|
||||
if pk.EntityRuntimeID != r.gameData.EntityRuntimeID {
|
||||
return false, fmt.Errorf("entity runtime ID mismatch: entity runtime ID in StartGame and SetLocalPlayerAsInitialised packets should be equal")
|
||||
}
|
||||
close(r.spawn)
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
proxy := NewProxy(logrus.StandardLogger())
|
||||
proxy.Server = minecraft.NewConn()
|
||||
|
||||
game_started := false
|
||||
i := 0
|
||||
func (r *replayConnector) loop() {
|
||||
gameStarted := false
|
||||
defer r.Close()
|
||||
for {
|
||||
i += 1
|
||||
var magic uint32 = 0
|
||||
var packet_length uint32 = 0
|
||||
var toServer bool = false
|
||||
|
||||
offset, _ := f.Seek(0, io.SeekCurrent)
|
||||
if offset == size {
|
||||
log.Info("Reached End")
|
||||
return nil
|
||||
}
|
||||
|
||||
binary.Read(f, binary.LittleEndian, &magic)
|
||||
if magic != 0xAAAAAAAA {
|
||||
logrus.Fatal("Wrong Magic")
|
||||
}
|
||||
binary.Read(f, binary.LittleEndian, &packet_length)
|
||||
binary.Read(f, binary.LittleEndian, &toServer)
|
||||
payload := make([]byte, packet_length)
|
||||
n, err := f.Read(payload)
|
||||
payload, toServer, err := r.readPacket()
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return nil
|
||||
logrus.Error(err)
|
||||
}
|
||||
if n != int(packet_length) {
|
||||
log.Errorf("Truncated %d", i)
|
||||
return nil
|
||||
if payload == nil {
|
||||
return
|
||||
}
|
||||
var src, dst = r.RemoteAddr(), r.LocalAddr()
|
||||
if toServer {
|
||||
src, dst = r.LocalAddr(), r.RemoteAddr()
|
||||
}
|
||||
|
||||
var magic2 uint32
|
||||
binary.Read(f, binary.LittleEndian, &magic2)
|
||||
if magic2 != 0xBBBBBBBB {
|
||||
logrus.Fatal("Wrong Magic2")
|
||||
}
|
||||
|
||||
pk_data, err := minecraft.ParseData(payload, proxy.Server)
|
||||
pkData, err := minecraft.ParseData(payload, r, src, dst)
|
||||
if err != nil {
|
||||
return err
|
||||
logrus.Error(err)
|
||||
return
|
||||
}
|
||||
pks, err := pk_data.Decode(proxy.Server)
|
||||
pks, err := pkData.Decode(r)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
logrus.Error(err)
|
||||
continue
|
||||
}
|
||||
|
||||
for _, pk := range pks {
|
||||
logrus.Printf("%s", reflect.TypeOf(pk).String()[1:])
|
||||
|
||||
if game_started {
|
||||
if packetCB != nil {
|
||||
packetCB(pk, proxy, toServer)
|
||||
}
|
||||
if !gameStarted {
|
||||
gameStarted, _ = r.handleLoginSequence(pk)
|
||||
} else {
|
||||
switch pk := pk.(type) {
|
||||
case *packet.StartGame:
|
||||
proxy.Server.SetGameData(minecraft.GameData{
|
||||
WorldName: pk.WorldName,
|
||||
WorldSeed: pk.WorldSeed,
|
||||
Difficulty: pk.Difficulty,
|
||||
EntityUniqueID: pk.EntityUniqueID,
|
||||
EntityRuntimeID: pk.EntityRuntimeID,
|
||||
PlayerGameMode: pk.PlayerGameMode,
|
||||
PersonaDisabled: pk.PersonaDisabled,
|
||||
CustomSkinsDisabled: pk.CustomSkinsDisabled,
|
||||
BaseGameVersion: pk.BaseGameVersion,
|
||||
PlayerPosition: pk.PlayerPosition,
|
||||
Pitch: pk.Pitch,
|
||||
Yaw: pk.Yaw,
|
||||
Dimension: pk.Dimension,
|
||||
WorldSpawn: pk.WorldSpawn,
|
||||
EditorWorld: pk.EditorWorld,
|
||||
WorldGameMode: pk.WorldGameMode,
|
||||
GameRules: pk.GameRules,
|
||||
Time: pk.Time,
|
||||
ServerBlockStateChecksum: pk.ServerBlockStateChecksum,
|
||||
CustomBlocks: pk.Blocks,
|
||||
Items: pk.Items,
|
||||
PlayerMovementSettings: pk.PlayerMovementSettings,
|
||||
ServerAuthoritativeInventory: pk.ServerAuthoritativeInventory,
|
||||
Experiments: pk.Experiments,
|
||||
ClientSideGeneration: pk.ClientSideGeneration,
|
||||
ChatRestrictionLevel: pk.ChatRestrictionLevel,
|
||||
DisablePlayerInteractions: pk.DisablePlayerInteractions,
|
||||
})
|
||||
game_started = true
|
||||
if onConnect != nil {
|
||||
onConnect(proxy)
|
||||
}
|
||||
}
|
||||
r.packets <- pk
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func createReplayConnector(filename string, packetFunc PacketFunc) (r *replayConnector, err error) {
|
||||
r = &replayConnector{
|
||||
pool: packet.NewPool(),
|
||||
proto: minecraft.DefaultProtocol,
|
||||
packetFunc: packetFunc,
|
||||
spawn: make(chan struct{}),
|
||||
close: make(chan struct{}),
|
||||
packets: make(chan packet.Packet),
|
||||
downloadingPacks: make(map[string]*downloadingPack),
|
||||
}
|
||||
|
||||
logrus.Infof("Reading replay %s", filename)
|
||||
|
||||
r.f, err = os.Open(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stat, err := r.f.Stat()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r.totalSize = stat.Size()
|
||||
|
||||
err = r.readHeader()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
go r.loop()
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (r *replayConnector) Close() error {
|
||||
r.once.Do(func() {
|
||||
close(r.close)
|
||||
close(r.packets)
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *replayConnector) Authenticated() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (r *replayConnector) ChunkRadius() int {
|
||||
return 80
|
||||
}
|
||||
|
||||
func (r *replayConnector) ClientCacheEnabled() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (r *replayConnector) ClientData() login.ClientData {
|
||||
return r.clientData
|
||||
}
|
||||
|
||||
func (r *replayConnector) DoSpawn() error {
|
||||
return r.DoSpawnContext(context.Background())
|
||||
}
|
||||
|
||||
func (r *replayConnector) DoSpawnContext(ctx context.Context) error {
|
||||
select {
|
||||
case <-r.close:
|
||||
return errors.New("do spawn")
|
||||
case <-ctx.Done():
|
||||
return errors.New("do spawn")
|
||||
case <-r.spawn:
|
||||
// Conn was spawned successfully.
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (r *replayConnector) DoSpawnTimeout(timeout time.Duration) error {
|
||||
c, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
return r.DoSpawnContext(c)
|
||||
}
|
||||
|
||||
func (r *replayConnector) Flush() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *replayConnector) GameData() minecraft.GameData {
|
||||
return r.gameData
|
||||
}
|
||||
|
||||
func (r *replayConnector) IdentityData() login.IdentityData {
|
||||
return login.IdentityData{}
|
||||
}
|
||||
|
||||
func (r *replayConnector) Latency() time.Duration {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (r *replayConnector) LocalAddr() net.Addr {
|
||||
return &net.UDPAddr{
|
||||
IP: net.IPv4(1, 1, 1, 1),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *replayConnector) Read(b []byte) (n int, err error) {
|
||||
return 0, errors.New("not Implemented")
|
||||
}
|
||||
|
||||
func (r *replayConnector) ReadPacket() (pk packet.Packet, err error) {
|
||||
select {
|
||||
case <-r.close:
|
||||
return nil, net.ErrClosed
|
||||
case p, ok := <-r.packets:
|
||||
if !ok {
|
||||
err = net.ErrClosed
|
||||
}
|
||||
return p, err
|
||||
}
|
||||
}
|
||||
|
||||
func (r *replayConnector) Write(b []byte) (n int, err error) {
|
||||
return 0, errors.New("not Implemented")
|
||||
}
|
||||
|
||||
func (r *replayConnector) WritePacket(pk packet.Packet) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *replayConnector) RemoteAddr() net.Addr {
|
||||
return &net.UDPAddr{
|
||||
IP: net.IPv4(2, 2, 2, 2),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *replayConnector) ResourcePacks() []*resource.Pack {
|
||||
return r.resourcePacks
|
||||
}
|
||||
|
||||
func (r *replayConnector) SetGameData(data minecraft.GameData) {
|
||||
r.gameData = data
|
||||
}
|
||||
|
||||
func (r *replayConnector) StartGame(data minecraft.GameData) error {
|
||||
return r.StartGameContext(context.Background(), data)
|
||||
}
|
||||
|
||||
func (r *replayConnector) StartGameContext(ctx context.Context, data minecraft.GameData) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *replayConnector) StartGameTimeout(data minecraft.GameData, timeout time.Duration) error {
|
||||
c, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
return r.StartGameContext(c, data)
|
||||
}
|
||||
|
||||
func (r *replayConnector) SetDeadline(t time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *replayConnector) SetReadDeadline(t time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *replayConnector) SetWriteDeadline(time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *replayConnector) Pool() packet.Pool {
|
||||
return r.pool
|
||||
}
|
||||
|
||||
func (r *replayConnector) ShieldID() int32 {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (r *replayConnector) Proto() minecraft.Protocol {
|
||||
return r.proto
|
||||
}
|
||||
|
||||
func (r *replayConnector) PacketFunc(header packet.Header, payload []byte, src, dst net.Addr) {
|
||||
if r.packetFunc != nil {
|
||||
r.packetFunc(header, payload, src, dst)
|
||||
}
|
||||
}
|
||||
|
|
Binary file not shown.
|
@ -1,14 +1,81 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
|
||||
"github.com/sandertv/gophertunnel/minecraft"
|
||||
"github.com/sandertv/gophertunnel/minecraft/resource"
|
||||
)
|
||||
|
||||
func GetPacks(server *minecraft.Conn) (packs map[string]*resource.Pack, err error) {
|
||||
packs = make(map[string]*resource.Pack)
|
||||
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.IConn) (packs []Pack, err error) {
|
||||
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 = append(packs, &Packb{pack2})
|
||||
} else {
|
||||
packs = append(packs, pack)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
|
@ -0,0 +1,148 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"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,omitempty"`
|
||||
Bones []any `json:"bones"`
|
||||
}
|
||||
|
||||
func (skin *Skin) Hash() uuid.UUID {
|
||||
h := append(skin.CapeData, append(skin.SkinData, skin.SkinGeometry...)...)
|
||||
return uuid.NewSHA1(uuid.NameSpaceURL, h)
|
||||
}
|
||||
|
||||
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,165 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/sandertv/gophertunnel/minecraft/protocol"
|
||||
"github.com/sandertv/gophertunnel/minecraft/resource"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type SkinMeta struct {
|
||||
SkinID string
|
||||
PlayFabID string
|
||||
PremiumSkin bool
|
||||
PersonaSkin bool
|
||||
CapeID string
|
||||
SkinColour string
|
||||
ArmSize string
|
||||
Trusted bool
|
||||
PersonaPieces []protocol.PersonaPiece
|
||||
}
|
||||
|
||||
type _skinWithIndex struct {
|
||||
i int
|
||||
skin *Skin
|
||||
}
|
||||
|
||||
func (s _skinWithIndex) Name(name string) string {
|
||||
if s.i == 1 {
|
||||
return name
|
||||
}
|
||||
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 := WriteManifest(&manifest, fpath); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -1,15 +1,58 @@
|
|||
package utils
|
||||
|
||||
import "github.com/sanbornm/go-selfupdate/selfupdate"
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
"github.com/sanbornm/go-selfupdate/selfupdate"
|
||||
"github.com/shirou/gopsutil/v3/cpu"
|
||||
"github.com/shirou/gopsutil/v3/mem"
|
||||
)
|
||||
|
||||
var Version string
|
||||
var CmdName = "bedrocktool"
|
||||
|
||||
const updateServer = "https://updates.yuv.pink/"
|
||||
|
||||
type trequester struct {
|
||||
selfupdate.Requester
|
||||
}
|
||||
|
||||
func (httpRequester *trequester) Fetch(url string) (io.ReadCloser, error) {
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// set user agent to know what versions are run
|
||||
h, _ := os.Hostname()
|
||||
v, _ := mem.VirtualMemory()
|
||||
c, _ := cpu.Info()
|
||||
var ct string
|
||||
if len(c) > 0 {
|
||||
ct = c[0].ModelName
|
||||
}
|
||||
req.Header.Add("User-Agent", fmt.Sprintf("%s '%s' '%s' %d %d '%s'", CmdName, Version, h, runtime.NumCPU(), v.Total, ct))
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
return nil, fmt.Errorf("bad http status from %s: %v", url, resp.Status)
|
||||
}
|
||||
|
||||
return resp.Body, nil
|
||||
}
|
||||
|
||||
var Updater = &selfupdate.Updater{
|
||||
CurrentVersion: Version,
|
||||
ApiURL: updateServer,
|
||||
BinURL: updateServer,
|
||||
Dir: "update/",
|
||||
CmdName: "bedrocktool", // app name
|
||||
CmdName: CmdName,
|
||||
Requester: &trequester{},
|
||||
}
|
||||
|
|
182
utils/utils.go
182
utils/utils.go
|
@ -1,18 +1,24 @@
|
|||
// Package utils ...
|
||||
package utils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"crypto/aes"
|
||||
"crypto/sha256"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/bedrock-tool/bedrocktool/locale"
|
||||
"github.com/google/uuid"
|
||||
"github.com/sandertv/gophertunnel/minecraft"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
@ -20,35 +26,21 @@ import (
|
|||
//"github.com/sandertv/gophertunnel/minecraft/gatherings"
|
||||
|
||||
"github.com/sandertv/gophertunnel/minecraft/protocol/login"
|
||||
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
|
||||
"github.com/sandertv/gophertunnel/minecraft/resource"
|
||||
)
|
||||
|
||||
const SERVER_ADDRESS_HELP = `accepted server address formats:
|
||||
123.234.123.234
|
||||
123.234.123.234:19132
|
||||
realm:<Realmname>
|
||||
realm:<Realmname>:<Id>
|
||||
|
||||
`
|
||||
|
||||
var (
|
||||
G_debug bool
|
||||
G_preload_packs bool
|
||||
G_interactive bool
|
||||
)
|
||||
|
||||
var A string
|
||||
|
||||
func init() {
|
||||
b, _ := base64.RawStdEncoding.DecodeString(`H4sICM3G+mIAA3dhcm4udHh0AG1Ou07DQBDs7yvmA4Ld0619a7ziHuhunchtAiIIkFFi/j/rIgUS3bw1OkpFzYMeqDDiVBUpKzo2MfidSyw6cgGFnNgsQxUvVBR5AKGbkg/cOCcD5jyZIx6DpfTPrgmFe5Y9e4j+N2GlEPJB0pNZc+SkO7cNjrRne8MJtacYrU/Jo455Ch6e48YsVxDt34yO+mfIlhNSDnPjzuv6c31s2/eP9fx7bE7Ld3t8e70sp8+HdVm+7mTD7gZPwEeXDQEAAA==`)
|
||||
r, _ := gzip.NewReader(bytes.NewBuffer(b))
|
||||
d, _ := io.ReadAll(r)
|
||||
A = string(d)
|
||||
var Options struct {
|
||||
Debug bool
|
||||
Preload bool
|
||||
IsInteractive bool
|
||||
ExtraDebug bool
|
||||
Capture bool
|
||||
PathCustomUserData string
|
||||
}
|
||||
|
||||
var name_regexp = regexp.MustCompile(`\||(?:§.?)`)
|
||||
var nameRegexp = regexp.MustCompile(`\||(?:§.?)`)
|
||||
|
||||
// cleans name so it can be used as a filename
|
||||
// CleanupName cleans name so it can be used as a filename
|
||||
func CleanupName(name string) string {
|
||||
name = strings.Split(name, "\n")[0]
|
||||
var _tmp struct {
|
||||
|
@ -58,93 +50,67 @@ func CleanupName(name string) string {
|
|||
if err == nil {
|
||||
name = _tmp.K
|
||||
}
|
||||
name = string(name_regexp.ReplaceAll([]byte(name), []byte("")))
|
||||
name = string(nameRegexp.ReplaceAll([]byte(name), []byte("")))
|
||||
name = strings.TrimSpace(name)
|
||||
return name
|
||||
}
|
||||
|
||||
// connections
|
||||
|
||||
func ConnectServer(ctx context.Context, address string, ClientData *login.ClientData, want_packs bool, packetFunc PacketFunc) (serverConn *minecraft.Conn, err error) {
|
||||
packet_func := func(header packet.Header, payload []byte, src, dst net.Addr) {
|
||||
if G_debug {
|
||||
PacketLogger(header, payload, src, dst)
|
||||
}
|
||||
if packetFunc != nil {
|
||||
packetFunc(header, payload, src, dst)
|
||||
}
|
||||
}
|
||||
|
||||
func connectServer(ctx context.Context, address string, ClientData *login.ClientData, wantPacks bool, packetFunc PacketFunc) (serverConn *minecraft.Conn, err error) {
|
||||
cd := login.ClientData{}
|
||||
if ClientData != nil {
|
||||
cd = *ClientData
|
||||
}
|
||||
|
||||
logrus.Infof("Connecting to %s\n", address)
|
||||
logrus.Info(locale.Loc("connecting", locale.Strmap{"Address": address}))
|
||||
serverConn, err = minecraft.Dialer{
|
||||
TokenSource: GetTokenSource(),
|
||||
ClientData: cd,
|
||||
PacketFunc: packet_func,
|
||||
DownloadResourcePack: func(id uuid.UUID, version string) bool {
|
||||
return want_packs
|
||||
PacketFunc: packetFunc,
|
||||
DownloadResourcePack: func(id uuid.UUID, version string, current int, total int) bool {
|
||||
return wantPacks
|
||||
},
|
||||
}.DialContext(ctx, "raknet", address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return serverConn, err
|
||||
}
|
||||
|
||||
logrus.Debug("Connected.")
|
||||
Client_addr = serverConn.LocalAddr()
|
||||
logrus.Debug(locale.Loc("connected", nil))
|
||||
return serverConn, nil
|
||||
}
|
||||
|
||||
func spawn_conn(ctx context.Context, clientConn *minecraft.Conn, serverConn *minecraft.Conn) error {
|
||||
func spawnConn(ctx context.Context, clientConn minecraft.IConn, serverConn minecraft.IConn, gd minecraft.GameData) error {
|
||||
wg := sync.WaitGroup{}
|
||||
errs := make(chan error, 2)
|
||||
if clientConn != nil {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
errs <- clientConn.StartGame(gd)
|
||||
}()
|
||||
}
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
errs <- clientConn.StartGame(serverConn.GameData())
|
||||
}()
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
errs <- serverConn.DoSpawn()
|
||||
}()
|
||||
|
||||
// wait for both to finish
|
||||
wg.Wait()
|
||||
for i := 0; i < 2; i++ {
|
||||
select {
|
||||
case err := <-errs:
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to start game: %s", err)
|
||||
return errors.New(locale.Loc("failed_start_game", locale.Strmap{"Err": err}))
|
||||
}
|
||||
case <-ctx.Done():
|
||||
return fmt.Errorf("connection cancelled")
|
||||
return errors.New(locale.Loc("connection_cancelled", nil))
|
||||
default:
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// get longest line length
|
||||
func max_len(lines []string) int {
|
||||
o := 0
|
||||
for _, line := range lines {
|
||||
if o < len(line) {
|
||||
o = len(line)
|
||||
}
|
||||
}
|
||||
return o
|
||||
}
|
||||
|
||||
// make text centered
|
||||
func MarginLines(lines []string) string {
|
||||
ret := ""
|
||||
max := max_len(lines)
|
||||
for _, line := range lines {
|
||||
if len(line) != max {
|
||||
ret += strings.Repeat(" ", max/2-len(line)/4)
|
||||
}
|
||||
ret += line + "\n"
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// SplitExt splits path to filename and extension
|
||||
func SplitExt(filename string) (name, ext string) {
|
||||
name, ext = path.Base(filename), path.Ext(filename)
|
||||
|
@ -163,3 +129,65 @@ func Clamp(a, b int) int {
|
|||
}
|
||||
return a
|
||||
}
|
||||
|
||||
func RandSeededUUID(str string) string {
|
||||
h := sha256.Sum256([]byte(str))
|
||||
id, _ := uuid.NewRandomFromReader(bytes.NewBuffer(h[:]))
|
||||
return id.String()
|
||||
}
|
||||
|
||||
func WriteManifest(manifest *resource.Manifest, fpath string) error {
|
||||
w, err := os.Create(path.Join(fpath, "manifest.json"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
e := json.NewEncoder(w)
|
||||
e.SetIndent("", "\t")
|
||||
if err = e.Encode(manifest); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func CfbDecrypt(data []byte, key []byte) []byte {
|
||||
cipher, _ := aes.NewCipher([]byte(key))
|
||||
|
||||
shiftRegister := append(key[:16], data...)
|
||||
iv := make([]byte, 16)
|
||||
off := 0
|
||||
for ; off < len(data); off += 1 {
|
||||
cipher.Encrypt(iv, shiftRegister)
|
||||
data[off] ^= iv[0]
|
||||
shiftRegister = shiftRegister[1:]
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
func abs(n float32) float32 {
|
||||
if n < 0 {
|
||||
n = -n
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func SizeofFmt(num float32) string {
|
||||
for _, unit := range []string{"", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi"} {
|
||||
if abs(num) < 1024.0 {
|
||||
return fmt.Sprintf("%3.1f%sB", num, unit)
|
||||
}
|
||||
num /= 1024.0
|
||||
}
|
||||
return fmt.Sprintf("%.1fYiB", num)
|
||||
}
|
||||
|
||||
func ShowFile(path string) {
|
||||
path, _ = filepath.Abs(path)
|
||||
if runtime.GOOS == "windows" {
|
||||
cmd := exec.Command(`explorer`, "/select,", path)
|
||||
cmd.Start()
|
||||
return
|
||||
}
|
||||
if runtime.GOOS == "linux" {
|
||||
|
||||
}
|
||||
}
|
||||
|
|
35
utils/zip.go
35
utils/zip.go
|
@ -2,26 +2,31 @@ package utils
|
|||
|
||||
import (
|
||||
"archive/zip"
|
||||
"fmt"
|
||||
"compress/flate"
|
||||
"io"
|
||||
"io/fs"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func UnpackZip(r io.ReaderAt, size int64, unpack_folder string) {
|
||||
func UnpackZip(r io.ReaderAt, size int64, unpackFolder string) {
|
||||
zr, _ := zip.NewReader(r, size)
|
||||
for _, src_file := range zr.File {
|
||||
out_path := path.Join(unpack_folder, src_file.Name)
|
||||
if src_file.Mode().IsDir() {
|
||||
os.Mkdir(out_path, 0o755)
|
||||
for _, srcFile := range zr.File {
|
||||
srcName := strings.ReplaceAll(srcFile.Name, "\\", "/")
|
||||
outPath := path.Join(unpackFolder, srcName)
|
||||
if srcFile.Mode().IsDir() {
|
||||
os.Mkdir(outPath, 0o755)
|
||||
} else {
|
||||
os.MkdirAll(path.Dir(out_path), 0o755)
|
||||
fr, _ := src_file.Open()
|
||||
f, _ := os.Create(path.Join(unpack_folder, src_file.Name))
|
||||
os.MkdirAll(path.Dir(outPath), 0o755)
|
||||
fr, _ := srcFile.Open()
|
||||
f, _ := os.Create(path.Join(unpackFolder, srcName))
|
||||
io.Copy(f, fr)
|
||||
f.Close()
|
||||
fr.Close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -29,16 +34,22 @@ func UnpackZip(r io.ReaderAt, size int64, unpack_folder string) {
|
|||
func ZipFolder(filename, folder string) error {
|
||||
f, err := os.Create(filename)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
zw := zip.NewWriter(f)
|
||||
|
||||
// Register a custom Deflate compressor.
|
||||
zw.RegisterCompressor(zip.Deflate, func(out io.Writer) (io.WriteCloser, error) {
|
||||
return flate.NewWriter(out, flate.NoCompression)
|
||||
})
|
||||
|
||||
err = filepath.WalkDir(folder, func(path string, d fs.DirEntry, err error) error {
|
||||
if !d.Type().IsDir() {
|
||||
rel := path[len(folder)+1:]
|
||||
zwf, _ := zw.Create(rel)
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
logrus.Error(err)
|
||||
}
|
||||
zwf.Write(data)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue