Compare commits

...

31 Commits

Author SHA1 Message Date
olebeck 796a846b8e settings in gui wrong 2023-04-05 00:03:03 +02:00
olebeck 99f7b012bf add flags for inventory and entity 2023-04-04 20:06:05 +02:00
olebeck 3b1aa74100 fix blocks not showing up in creative menu 2023-04-04 19:38:11 +02:00
olebeck 3dadabf706 update nbtconv 2023-04-04 17:57:24 +02:00
olebeck a5f77eca3c fix item metadata being written to the wrong key 2023-04-04 17:12:21 +02:00
olebeck 2996a752fc settings not properly applied 2023-04-04 16:25:19 +02:00
olebeck 085244e656 fix an issue with resourcepack names 2023-04-04 16:02:12 +02:00
olebeck 543e1335ee fix nil 2023-04-04 04:09:01 +02:00
olebeck 9a677feeac fix debug 2023-04-04 03:57:01 +02:00
olebeck 32bfdfa319 fix multi worlds 2023-04-04 03:51:13 +02:00
olebeck bf786b1010 add grab cursor 2023-04-04 03:27:55 +02:00
olebeck e8dae60049 fix renames 2023-04-04 02:47:25 +02:00
olebeck 17d86c9ddb move handlers 2023-04-04 02:44:13 +02:00
olebeck 4e7cfeb3b4 make worlds a proper handler 2023-04-04 02:42:17 +02:00
olebeck f1cb7df05e make subcommands more modular 2023-04-02 16:49:24 +02:00
olebeck 9b3a31879a small fixes 2023-04-02 14:20:50 +02:00
olebeck 88589933ea format a bit 2023-04-02 00:22:50 +02:00
olebeck 29417b600c go.sum 2023-04-01 21:18:44 +02:00
olebeck eca888e3f2 fix regression on legacy 2023-04-01 21:18:40 +02:00
olebeck 4c0dd665f1 fix out of bounds in inventory 2023-03-31 15:54:25 +02:00
olebeck b7ad339a64 async update check 2023-03-30 13:54:11 +02:00
olebeck 191f1ecd9a go.sum 2023-03-30 13:48:28 +02:00
olebeck 1e990d7fcb performance optimizations 2023-03-30 13:48:03 +02:00
olebeck d3a8219d99 add player inventory 2023-03-28 15:22:11 +02:00
olebeck 94e02d677c checkerboard chunk render 2023-03-27 14:11:40 +02:00
olebeck c5e7146f24 fix gui build failing 2023-03-26 13:06:40 +02:00
olebeck cdf7caea5b fix a revert 2023-03-26 12:45:37 +02:00
olebeck 47fd5cc2ec fix some wrong map rendering 2023-03-25 23:04:53 +01:00
olebeck cef4a5dbfe fix 2023-03-25 22:50:17 +01:00
olebeck 6376195732 fix bugs with worlds on vanilla server 2023-03-25 22:19:14 +01:00
olebeck 1b1bddc17f ignore skin id changes without new data 2023-03-24 23:29:53 +01:00
58 changed files with 2909 additions and 1896 deletions

5
.gitignore vendored
View File

@ -28,3 +28,8 @@ keys.db
packets.log.gpg packets.log.gpg
customdata.json customdata.json
/other-projects/
__pycache__/entityflags.cpython-310.pyc
gathering.py
*.ipynb
entityflags.py

View File

@ -36,12 +36,9 @@ PLATFORMS = [
def clean(): def clean():
if os.path.exists("./tmp"): shutil.rmtree("./tmp", True)
shutil.rmtree("./tmp") shutil.rmtree("./builds", True)
if os.path.exists("./builds"): shutil.rmtree("./updates", True)
shutil.rmtree("./builds")
if os.path.exists("./updates"):
shutil.rmtree("./updates")
for file in os.listdir("./cmd/bedrocktool"): for file in os.listdir("./cmd/bedrocktool"):
if file.endswith(".syso"): if file.endswith(".syso"):
os.remove(f"./cmd/bedrocktool/{file}") os.remove(f"./cmd/bedrocktool/{file}")

View File

@ -9,6 +9,7 @@ import (
"os/signal" "os/signal"
"runtime/debug" "runtime/debug"
"syscall" "syscall"
"time"
"github.com/bedrock-tool/bedrocktool/locale" "github.com/bedrock-tool/bedrocktool/locale"
"github.com/bedrock-tool/bedrocktool/utils" "github.com/bedrock-tool/bedrocktool/utils"
@ -35,12 +36,23 @@ func (c *CLI) Init() bool {
func (c *CLI) Start(ctx context.Context, cancel context.CancelFunc) error { func (c *CLI) Start(ctx context.Context, cancel context.CancelFunc) error {
flag.Parse() flag.Parse()
utils.InitDNS() utils.InitDNS()
utils.InitExtraDebug(ctx)
subcommands.Execute(ctx) subcommands.Execute(ctx)
time.Sleep(50 * time.Millisecond)
cancel()
time.Sleep(50 * time.Millisecond)
return nil return nil
} }
func main() { func main() {
/*
cf, _ := os.Create("cpu.pprof")
err := pprof.StartCPUProfile(cf)
if err != nil {
logrus.Error(err)
}
defer pprof.StopCPUProfile()
*/
defer func() { defer func() {
if err := recover(); err != nil { if err := recover(); err != nil {
logrus.Errorf(locale.Loc("fatal_error", nil)) logrus.Errorf(locale.Loc("fatal_error", nil))
@ -69,14 +81,16 @@ func main() {
logrus.Infof(locale.Loc("bedrocktool_version", locale.Strmap{"Version": utils.Version})) logrus.Infof(locale.Loc("bedrocktool_version", locale.Strmap{"Version": utils.Version}))
} }
newVersion, err := utils.Updater.UpdateAvailable() go func() {
if err != nil { newVersion, err := utils.Updater.UpdateAvailable()
logrus.Error(err) if err != nil {
} logrus.Error(err)
}
if newVersion != "" && utils.Version != "" { if newVersion != "" && utils.Version != "" {
logrus.Infof(locale.Loc("update_available", locale.Strmap{"Version": newVersion})) logrus.Infof(locale.Loc("update_available", locale.Strmap{"Version": newVersion}))
} }
}()
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
@ -116,7 +130,7 @@ func main() {
logrus.Error("Failed to init UI!") logrus.Error("Failed to init UI!")
return return
} }
err = ui.Start(ctx, cancel) err := ui.Start(ctx, cancel)
cancel() cancel()
if err != nil { if err != nil {
logrus.Error(err) logrus.Error(err)

26
go.mod
View File

@ -6,31 +6,34 @@ go 1.20
replace github.com/sandertv/gophertunnel => github.com/olebeck/gophertunnel v1.28.1-1 replace github.com/sandertv/gophertunnel => github.com/olebeck/gophertunnel v1.28.1-1
//replace github.com/df-mc/dragonfly => ./dragonfly //replace github.com/df-mc/dragonfly => ./dragonfly
replace github.com/df-mc/dragonfly => github.com/olebeck/dragonfly v0.9.3-8 replace github.com/df-mc/dragonfly => github.com/olebeck/dragonfly v0.9.4-7
//replace gioui.org => ./gio
replace gioui.org => github.com/olebeck/gio v0.0.0-20230321105529-d424f1a59af9 replace gioui.org => github.com/olebeck/gio v0.0.0-20230321105529-d424f1a59af9
require ( require (
gioui.org v0.0.0-20221219171716-c455f0f342ef gioui.org v0.0.0-20230323230841-d7b1c7c33b33
gioui.org/x v0.0.0-20230313161557-05b40af72ed0 gioui.org/x v0.0.0-20230313161557-05b40af72ed0
github.com/cloudfoundry-attic/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21 github.com/cloudfoundry-attic/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21
github.com/df-mc/dragonfly v0.9.3 github.com/df-mc/dragonfly v0.9.4
github.com/df-mc/goleveldb v1.1.9 github.com/df-mc/goleveldb v1.1.9
github.com/fatih/color v1.14.1 github.com/fatih/color v1.15.0
github.com/flytam/filenamify v1.1.2 github.com/flytam/filenamify v1.1.2
github.com/go-gl/mathgl v1.0.0 github.com/go-gl/mathgl v1.0.0
github.com/google/subcommands v1.2.0 github.com/google/subcommands v1.2.0
github.com/google/uuid v1.3.0 github.com/google/uuid v1.3.0
github.com/jinzhu/copier v0.3.5 github.com/jinzhu/copier v0.3.5
github.com/miekg/dns v1.1.51 github.com/miekg/dns v1.1.52
github.com/nicksnyder/go-i18n/v2 v2.2.1 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/sanbornm/go-selfupdate v0.0.0-20210106163404-c9b625feac49
github.com/sandertv/go-raknet v1.12.0 github.com/sandertv/go-raknet v1.12.0
github.com/sandertv/gophertunnel v1.28.1 github.com/sandertv/gophertunnel v1.28.1
github.com/shirou/gopsutil/v3 v3.23.2
github.com/sirupsen/logrus v1.9.0 github.com/sirupsen/logrus v1.9.0
golang.design/x/lockfree v0.0.1 golang.design/x/lockfree v0.0.1
golang.org/x/crypto v0.7.0 golang.org/x/crypto v0.7.0
golang.org/x/exp v0.0.0-20230304125523-9ff063c70017 golang.org/x/exp v0.0.0-20230321023759-10a507213a29
golang.org/x/oauth2 v0.6.0 golang.org/x/oauth2 v0.6.0
golang.org/x/text v0.8.0 golang.org/x/text v0.8.0
gopkg.in/square/go-jose.v2 v2.6.0 gopkg.in/square/go-jose.v2 v2.6.0
@ -43,21 +46,30 @@ require (
git.wow.st/gmp/jni v0.0.0-20210610011705-34026c7e22d0 // indirect git.wow.st/gmp/jni v0.0.0-20210610011705-34026c7e22d0 // indirect
github.com/benoitkugler/textlayout v0.3.0 // indirect github.com/benoitkugler/textlayout v0.3.0 // indirect
github.com/brentp/intintmap v0.0.0-20190211203843-30dc0ade9af9 // indirect github.com/brentp/intintmap v0.0.0-20190211203843-30dc0ade9af9 // indirect
github.com/cespare/xxhash v1.1.0 // indirect
github.com/changkun/lockfree v0.0.1 // indirect github.com/changkun/lockfree v0.0.1 // indirect
github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21 // indirect github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21 // indirect
github.com/df-mc/atomic v1.10.0 // indirect github.com/df-mc/atomic v1.10.0 // indirect
github.com/df-mc/worldupgrader v1.0.3 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-text/typesetting v0.0.0-20221214153724-0399769901d5 // indirect github.com/go-text/typesetting v0.0.0-20221214153724-0399769901d5 // indirect
github.com/golang/protobuf v1.5.2 // indirect github.com/golang/protobuf v1.5.2 // indirect
github.com/golang/snappy v0.0.4 // indirect github.com/golang/snappy v0.0.4 // indirect
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
github.com/klauspost/compress v1.15.15 // indirect github.com/klauspost/compress v1.15.15 // indirect
github.com/kr/binarydist v0.1.0 // indirect github.com/kr/binarydist v0.1.0 // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect github.com/mattn/go-isatty v0.0.17 // indirect
github.com/muhammadmuzzammil1998/jsonc v1.0.0 // indirect github.com/muhammadmuzzammil1998/jsonc v1.0.0 // indirect
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
github.com/rogpeppe/go-internal v1.9.0 // 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 go.uber.org/atomic v1.10.0 // indirect
golang.org/x/exp/shiny v0.0.0-20220827204233-334a2380cb91 // indirect golang.org/x/exp/shiny v0.0.0-20220827204233-334a2380cb91 // indirect
golang.org/x/image v0.6.0 // indirect golang.org/x/image v0.5.0 // indirect
golang.org/x/mod v0.8.0 // indirect golang.org/x/mod v0.8.0 // indirect
golang.org/x/net v0.8.0 // indirect golang.org/x/net v0.8.0 // indirect
golang.org/x/sys v0.6.0 // indirect golang.org/x/sys v0.6.0 // indirect

79
go.sum
View File

@ -1,6 +1,4 @@
eliasnaur.com/font v0.0.0-20220124212145-832bb8fc08c3 h1:djFprmHZgrSepsHAIRMp5UJn3PzsoTg9drI+BDmif5Q= eliasnaur.com/font v0.0.0-20220124212145-832bb8fc08c3 h1:djFprmHZgrSepsHAIRMp5UJn3PzsoTg9drI+BDmif5Q=
gioui.org v0.0.0-20221219171716-c455f0f342ef h1:0nxeswOamzSRIjFjRmASp+sWCAFzVt7UCC3HlRHIB6k=
gioui.org v0.0.0-20221219171716-c455f0f342ef/go.mod h1:3lLo7xMHYnnHTrgKNNctBjEKKH3wQCO2Sn7ti5Jy8mU=
gioui.org/cpu v0.0.0-20210808092351-bfe733dd3334/go.mod h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ= gioui.org/cpu v0.0.0-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 h1:AGDDxsJE1RpcXTAxPG2B4jrwVUJGFDjINIPi1jtO6pc=
gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2/go.mod h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ= gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2/go.mod h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ=
@ -13,6 +11,8 @@ git.wow.st/gmp/jni v0.0.0-20210610011705-34026c7e22d0/go.mod h1:+axXBRUTIDlCeE73
github.com/BurntSushi/toml v1.0.0 h1:dtDWrepsVPfW9H/4y7dDgFc2MBUSeJhlaDtK13CxFlU= github.com/BurntSushi/toml v1.0.0 h1:dtDWrepsVPfW9H/4y7dDgFc2MBUSeJhlaDtK13CxFlU=
github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/TeaEntityLab/fpGo/v2 v2.3.2 h1:qlGjFzXukp9IgVQl32cz4PosbiFEnwXa2EpDbwddHS8=
github.com/TeaEntityLab/fpGo/v2 v2.3.2/go.mod h1:b06fRNLSnNiUwfSskBC3f3cocA4mAC2iySdtaP/T9uA=
github.com/benoitkugler/pstokenizer v1.0.0/go.mod h1:l1G2Voirz0q/jj0TQfabNxVsa8HZXh/VMxFSRALWTiE= github.com/benoitkugler/pstokenizer v1.0.0/go.mod h1:l1G2Voirz0q/jj0TQfabNxVsa8HZXh/VMxFSRALWTiE=
github.com/benoitkugler/textlayout v0.3.0 h1:2ehWXEkgb6RUokTjXh1LzdGwG4dRP6X3dqhYYDYhUVk= github.com/benoitkugler/textlayout v0.3.0 h1:2ehWXEkgb6RUokTjXh1LzdGwG4dRP6X3dqhYYDYhUVk=
github.com/benoitkugler/textlayout v0.3.0/go.mod h1:o+1hFV+JSHBC9qNLIuwVoLedERU7sBPgEFcuSgfvi/w= github.com/benoitkugler/textlayout v0.3.0/go.mod h1:o+1hFV+JSHBC9qNLIuwVoLedERU7sBPgEFcuSgfvi/w=
@ -35,13 +35,17 @@ github.com/df-mc/atomic v1.10.0 h1:0ZuxBKwR/hxcFGorKiHIp+hY7hgY+XBTzhCYD2NqSEg=
github.com/df-mc/atomic v1.10.0/go.mod h1:Gw9rf+rPIbydMjA329Jn4yjd/O2c/qusw3iNp4tFGSc= github.com/df-mc/atomic v1.10.0/go.mod h1:Gw9rf+rPIbydMjA329Jn4yjd/O2c/qusw3iNp4tFGSc=
github.com/df-mc/goleveldb v1.1.9 h1:ihdosZyy5jkQKrxucTQmN90jq/2lUwQnJZjIYIC/9YU= github.com/df-mc/goleveldb v1.1.9 h1:ihdosZyy5jkQKrxucTQmN90jq/2lUwQnJZjIYIC/9YU=
github.com/df-mc/goleveldb v1.1.9/go.mod h1:+NHCup03Sci5q84APIA21z3iPZCuk6m6ABtg4nANCSk= github.com/df-mc/goleveldb v1.1.9/go.mod h1:+NHCup03Sci5q84APIA21z3iPZCuk6m6ABtg4nANCSk=
github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w= github.com/df-mc/worldupgrader v1.0.3 h1:3nbthy6vfSNQZdqHBR+E5Fh3mCeWmCwLtqrYDiPUG5I=
github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg= github.com/df-mc/worldupgrader v1.0.3/go.mod h1:6ybkJ/BV9b0XkcPzcLmvgT9Nv/xgBXdDQTmRhu7b8zQ=
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 h1:dGlfWU4zrhDlsmvob4IFcfgjG5vIjfo4UwLyec6Wx94=
github.com/flytam/filenamify v1.1.2/go.mod h1:Dzf9kVycwcsBlr2ATg6uxjqiFgKGH+5SKFuhdeP5zu8= github.com/flytam/filenamify v1.1.2/go.mod h1:Dzf9kVycwcsBlr2ATg6uxjqiFgKGH+5SKFuhdeP5zu8=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/go-gl/mathgl v1.0.0 h1:t9DznWJlXxxjeeKLIdovCOVJQk/GzDEL7h/h+Ro2B68= github.com/go-gl/mathgl v1.0.0 h1:t9DznWJlXxxjeeKLIdovCOVJQk/GzDEL7h/h+Ro2B68=
github.com/go-gl/mathgl v1.0.0/go.mod h1:yhpkQzEiH9yPyxDUGzkmgScbaBVlhC06qodikEM0ZwQ= github.com/go-gl/mathgl v1.0.0/go.mod h1:yhpkQzEiH9yPyxDUGzkmgScbaBVlhC06qodikEM0ZwQ=
github.com/go-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-20221214153724-0399769901d5 h1:iOA0HmtpANn48hX2nlDNMu0VVaNza35HJG0WeetBVzQ= github.com/go-text/typesetting v0.0.0-20221214153724-0399769901d5 h1:iOA0HmtpANn48hX2nlDNMu0VVaNza35HJG0WeetBVzQ=
github.com/go-text/typesetting v0.0.0-20221214153724-0399769901d5/go.mod h1:/cmOXaoTiO+lbCwkTZBgCvevJpbFsZ5reXIpEJVh5MI= github.com/go-text/typesetting v0.0.0-20221214153724-0399769901d5/go.mod h1:/cmOXaoTiO+lbCwkTZBgCvevJpbFsZ5reXIpEJVh5MI=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@ -53,7 +57,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 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 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.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 h1:vWQspBTo2nEqTUFita5/KeEWlUL8kQObDFbub/EN9oE=
github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
@ -69,31 +75,27 @@ github.com/klauspost/compress v1.15.15 h1:EF27CXIuDsYJ6mmvtBRlEuB2UVOqHG1tAXgZ7y
github.com/klauspost/compress v1.15.15/go.mod h1:ZcK2JAFqKOpnBlxcLsJzYfrS9X1akm9fHZNnD9+Vo/4= 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 h1:6kAoLA9FMMnNGSehX0s1PdjbEaACznAv/W219j2uvyo=
github.com/kr/binarydist v0.1.0/go.mod h1:DY7S//GCoz1BCd0B0EVrinCKAZN3pXe+MDaIZbXQVgM= github.com/kr/binarydist v0.1.0/go.mod h1:DY7S//GCoz1BCd0B0EVrinCKAZN3pXe+MDaIZbXQVgM=
github.com/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 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= 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/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/miekg/dns v1.1.51 h1:0+Xg7vObnhrz/4ZCZcZh7zPXlmU0aveS2HDBd0m0qSo= github.com/miekg/dns v1.1.52 h1:Bmlc/qsNNULOe6bpXcUTsuOajd0DzRHwup6D9k1An0c=
github.com/miekg/dns v1.1.51/go.mod h1:2Z9d3CP1LQWihRZUf29mQ19yDThaI4DAYzte2CaQW5c= github.com/miekg/dns v1.1.52/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY=
github.com/muhammadmuzzammil1998/jsonc v1.0.0 h1:8o5gBQn4ZA3NBA9DlTujCj2a4w0tqWrPVjDwhzkgTIs= github.com/muhammadmuzzammil1998/jsonc v1.0.0 h1:8o5gBQn4ZA3NBA9DlTujCj2a4w0tqWrPVjDwhzkgTIs=
github.com/muhammadmuzzammil1998/jsonc v1.0.0/go.mod h1:saF2fIVw4banK0H4+/EuqfFLpRnoy5S+ECwTOCcRcSU= github.com/muhammadmuzzammil1998/jsonc v1.0.0/go.mod h1:saF2fIVw4banK0H4+/EuqfFLpRnoy5S+ECwTOCcRcSU=
github.com/nicksnyder/go-i18n/v2 v2.2.1 h1:aOzRCdwsJuoExfZhoiXHy4bjruwCMdt5otbYojM/PaA= 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/nicksnyder/go-i18n/v2 v2.2.1/go.mod h1:fF2++lPHlo+/kPaj3nB0uxtPwzlPm+BlgwGX7MkeGj0=
github.com/olebeck/dragonfly v0.9.3-5 h1:8MUpzKz1UZ0K+DJrywBV6C/ydGMggTxGg1n9VFAZPpI= github.com/olebeck/dragonfly v0.9.4-5 h1:ixVX0Fe3wX8FryJiKC5gFVS4KDrKtd1dXJoBkzWhd1c=
github.com/olebeck/dragonfly v0.9.3-5/go.mod h1:ODAzVcmM7KvKgPB89hoYndQiVVgTm9FlDXUko1H3YVs= github.com/olebeck/dragonfly v0.9.4-5/go.mod h1:k8OQvjmfj+JbrwQf1qHfMETlFHOp0WJLjILN+QVqh+c=
github.com/olebeck/dragonfly v0.9.3-6 h1:ots1osXxuP6fQg4C8tt3y8xfpc+JNqOarI5zufisKFs= github.com/olebeck/dragonfly v0.9.4-6 h1:Pom7oMbUA/kFu6PCwr3mWtTOSPvgzD2/71+mUsqK42o=
github.com/olebeck/dragonfly v0.9.3-6/go.mod h1:ODAzVcmM7KvKgPB89hoYndQiVVgTm9FlDXUko1H3YVs= github.com/olebeck/dragonfly v0.9.4-6/go.mod h1:k8OQvjmfj+JbrwQf1qHfMETlFHOp0WJLjILN+QVqh+c=
github.com/olebeck/dragonfly v0.9.3-7 h1:zIMWsox18efEe7R+VmJOTJRePU9x2YN3CE1uzfse1AM= github.com/olebeck/dragonfly v0.9.4-7 h1:xzSc9U9upx+mxayAHN1MxkD+PStVgqksJ4uls0o3g4w=
github.com/olebeck/dragonfly v0.9.3-7/go.mod h1:nnnmYWgSTNQb9x33nBthqN/2vyHlUaijfo+e2y3W5j4= github.com/olebeck/dragonfly v0.9.4-7/go.mod h1:k8OQvjmfj+JbrwQf1qHfMETlFHOp0WJLjILN+QVqh+c=
github.com/olebeck/dragonfly v0.9.3-8 h1:w514gGVTK2iv3TDI8EuzSCDHrF2Hv2f+/lIicW0J6Bg=
github.com/olebeck/dragonfly v0.9.3-8/go.mod h1:nnnmYWgSTNQb9x33nBthqN/2vyHlUaijfo+e2y3W5j4=
github.com/olebeck/gio v0.0.0-20230321105529-d424f1a59af9 h1:TqDsMHwjW5ZYfh+RE8ussT62m0qXqo+QjzSXb7BCVA4= github.com/olebeck/gio v0.0.0-20230321105529-d424f1a59af9 h1:TqDsMHwjW5ZYfh+RE8ussT62m0qXqo+QjzSXb7BCVA4=
github.com/olebeck/gio v0.0.0-20230321105529-d424f1a59af9/go.mod h1:+W1Kpf96YcfissZocFqIp6O42FDTuphkObbEybp+Ffc= github.com/olebeck/gio v0.0.0-20230321105529-d424f1a59af9/go.mod h1:+W1Kpf96YcfissZocFqIp6O42FDTuphkObbEybp+Ffc=
github.com/olebeck/gophertunnel v1.27.4-3 h1:RktAdTNTvCFn6PQou0H3RyqrTo3/xH0bqODrHb/oXAs=
github.com/olebeck/gophertunnel v1.27.4-3/go.mod h1:ekREo7U9TPHh86kbuPMaWA93NMyWsfVvP/iNT3XhAb8=
github.com/olebeck/gophertunnel v1.27.4-4 h1:ZzmZquRW0WdoH/smsOk91O+40dotn7574bID+BkHbuk=
github.com/olebeck/gophertunnel v1.27.4-4/go.mod h1:ekREo7U9TPHh86kbuPMaWA93NMyWsfVvP/iNT3XhAb8=
github.com/olebeck/gophertunnel v1.28.1-1 h1:bw2jeMz94YHF5qQYhq1Yq/6fALkklGu7k26YbPI4DSs= github.com/olebeck/gophertunnel v1.28.1-1 h1:bw2jeMz94YHF5qQYhq1Yq/6fALkklGu7k26YbPI4DSs=
github.com/olebeck/gophertunnel v1.28.1-1/go.mod h1:ekREo7U9TPHh86kbuPMaWA93NMyWsfVvP/iNT3XhAb8= github.com/olebeck/gophertunnel v1.28.1-1/go.mod h1:ekREo7U9TPHh86kbuPMaWA93NMyWsfVvP/iNT3XhAb8=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
@ -103,17 +105,36 @@ 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/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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 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 h1:LuxslTBxJrrNeKfqoywIERWWhH43TgiVAiPEVlhgNBA=
github.com/sanbornm/go-selfupdate v0.0.0-20210106163404-c9b625feac49/go.mod h1:fY313ZGG810aWruFYcyq3coFpHDrWJVoMfSRI81y1r4= 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 h1:olUzZlIJyX/pgj/mrsLCZYjKLNDsYiWdvQ4NIm3z0DA=
github.com/sandertv/go-raknet v1.12.0/go.mod h1:Gx+WgZBMQ0V2UoouGoJ8Wj6CDrMBQ4SB2F/ggpl5/+Y= github.com/sandertv/go-raknet v1.12.0/go.mod h1:Gx+WgZBMQ0V2UoouGoJ8Wj6CDrMBQ4SB2F/ggpl5/+Y=
github.com/shirou/gopsutil/v3 v3.23.2 h1:PAWSuiAszn7IhPMBtXsbSCafej7PqUOvY6YywlQUExU=
github.com/shirou/gopsutil/v3 v3.23.2/go.mod h1:gv0aQw33GLo3pG8SiWKiQrbDzbRY1K80RyZJ7V4Th1M=
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= 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/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/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
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/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 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
golang.design/x/lockfree v0.0.1 h1:IHFNwZgM5bnZYWkEbzn5lWHMYr8WsRBdCJ/RBVY0xMM= golang.design/x/lockfree v0.0.1 h1:IHFNwZgM5bnZYWkEbzn5lWHMYr8WsRBdCJ/RBVY0xMM=
@ -122,17 +143,16 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A= golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/exp v0.0.0-20230304125523-9ff063c70017 h1:3Ea9SZLCB0aRIhSEjM+iaGIlzzeDJdpi579El/YIhEE= golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug=
golang.org/x/exp v0.0.0-20230304125523-9ff063c70017/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/exp/shiny v0.0.0-20220827204233-334a2380cb91 h1:ryT6Nf0R83ZgD8WnFFdfI8wCeyqgdXWN4+CkFVNPAT0= golang.org/x/exp/shiny v0.0.0-20220827204233-334a2380cb91 h1:ryT6Nf0R83ZgD8WnFFdfI8wCeyqgdXWN4+CkFVNPAT0=
golang.org/x/exp/shiny v0.0.0-20220827204233-334a2380cb91/go.mod h1:VjAR7z0ngyATZTELrBSkxOOHhhlnVUxDye4mcjx5h/8= 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-20190321063152-3fc05d484e9f/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20210504121937-7319ad40d33e/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20210504121937-7319ad40d33e/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
golang.org/x/image v0.6.0 h1:bR8b5okrPI3g/gyZakLZHeWxAR8Dn5CyxXv1hLH5g/4= golang.org/x/image v0.5.0 h1:5JMiNunQeQw++mMOz48/ISeNu3Iweh/JaZU8ZLqHRrI=
golang.org/x/image v0.6.0/go.mod h1:MXLdDR43H7cDJq5GEGXEVeeNhPgi+YYEQ2pC1byI1x0= golang.org/x/image v0.5.0/go.mod h1:FVC7BI/5Ym8R25iw5OLsgshdUBbT1h5jZTpA+mvAdZ4=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= 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/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -141,8 +161,6 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/oauth2 v0.6.0 h1:Lh8GPgSKBfWSwFvtuWOfeI3aAAnbXTSutYxJiOJFgIw= golang.org/x/oauth2 v0.6.0 h1:Lh8GPgSKBfWSwFvtuWOfeI3aAAnbXTSutYxJiOJFgIw=
@ -151,10 +169,11 @@ golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-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-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@ -167,8 +186,6 @@ golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@ -181,7 +198,6 @@ golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k=
golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= 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/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -194,6 +210,7 @@ google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/inconshreveable/go-update.v0 v0.0.0-20150814200126-d8b0b1d421aa h1:drvf2JoUL1fz3ttkGNkw+rf3kZa2//7XkYGpSO4NHNA= gopkg.in/inconshreveable/go-update.v0 v0.0.0-20150814200126-d8b0b1d421aa h1:drvf2JoUL1fz3ttkGNkw+rf3kZa2//7XkYGpSO4NHNA=

71
handlers/capture.go Normal file
View File

@ -0,0 +1,71 @@
package handlers
import (
"bytes"
"encoding/binary"
"fmt"
"io"
"net"
"os"
"sync"
"time"
"github.com/bedrock-tool/bedrocktool/utils"
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
)
var dumpLock sync.Mutex
func dumpPacket(f io.WriteCloser, toServer bool, payload []byte) {
dumpLock.Lock()
defer dumpLock.Unlock()
f.Write([]byte{0xAA, 0xAA, 0xAA, 0xAA})
packetSize := uint32(len(payload))
binary.Write(f, binary.LittleEndian, packetSize)
binary.Write(f, binary.LittleEndian, toServer)
binary.Write(f, binary.LittleEndian, time.Now().UnixMilli())
f.Write(payload)
f.Write([]byte{0xBB, 0xBB, 0xBB, 0xBB})
}
type packetCapturer struct {
proxy *utils.ProxyContext
fio *os.File
}
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) {
IsfromClient := p.proxy.IsClient(src)
buf := bytes.NewBuffer(nil)
header.Write(buf)
buf.Write(payload)
dumpPacket(p.fio, IsfromClient, 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() {
dumpLock.Lock()
defer dumpLock.Unlock()
p.fio.Close()
},
}
}

52
handlers/chat_log.go Normal file
View File

@ -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()
},
}
}

271
handlers/packet_logger.go Normal file
View File

@ -0,0 +1,271 @@
package handlers
import (
"bufio"
"bytes"
"fmt"
"io"
"net"
"os"
"reflect"
"strings"
"sync"
"github.com/bedrock-tool/bedrocktool/utils"
"github.com/bedrock-tool/bedrocktool/utils/crypt"
"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 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 (
FLog io.Writer
dmpLock sync.Mutex
)
func dmpStruct(level int, inputStruct any, withType bool, isInList bool) (s string) {
tBase := strings.Repeat("\t", level)
if inputStruct == nil {
return "nil"
}
ii := reflect.Indirect(reflect.ValueOf(inputStruct))
typeName := reflect.TypeOf(inputStruct).String()
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 += dmpStruct(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, dmpStruct(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 += dmpStruct(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(), dmpStruct(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
}
func DumpStruct(data interface{}) {
if FLog == nil {
return
}
FLog.Write([]byte(dmpStruct(0, data, true, false)))
FLog.Write([]byte("\n\n\n"))
}
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
if extraVerbose {
// open plain text log
logPlain, err := os.Create("packets.log")
if err != nil {
logrus.Error(err)
}
// open gpg log
logCrypt, err := os.Create("packets.log.gpg")
if err != nil {
logrus.Error(err)
} else {
// encrypter for the log
logCryptEnc, err = crypt.Encer("packets.log", logCrypt)
if err != nil {
logrus.Error(err)
}
}
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) {
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))
if extraVerbose {
dmpLock.Lock()
packetsLogF.Write([]byte(dmpStruct(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() {
if packetsLogF != nil {
packetsLogF.Flush()
}
if logPlain != nil {
logPlain.Close()
}
if logCryptEnc != nil {
logCryptEnc.Close()
}
if logCrypt != nil {
logCrypt.Close()
}
},
}
}
func init() {
// hacky solution to allow proxy to add this
utils.NewDebugLogger = NewDebugLogger
}

View File

@ -0,0 +1,112 @@
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.blockNBT = make(map[protocol.SubChunkPos][]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 {
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, s.dimension.Range(), s.ispre118, s.hasCustomBlocks)
if err != nil {
logrus.Error(err)
return
}
if blockNBTs != nil {
s.blockNBT[protocol.SubChunkPos{
pk.Position.X(), 0, pk.Position.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([]protocol.SubChunkOffset, 0, len(pk.SubChunkEntries))
for _, sub := range pk.SubChunkEntries {
offsets = append(offsets, sub.Offset)
var (
absX = pk.Position[0] + int32(sub.Offset[0])
absY = pk.Position[1] + int32(sub.Offset[1])
absZ = pk.Position[2] + int32(sub.Offset[2])
subPos = protocol.SubChunkPos{absX, absY, absZ}
pos = world.ChunkPos{absX, absZ}
)
ch, ok := s.chunks[pos]
if !ok {
logrus.Error(locale.Loc("subchunk_before_chunk", nil))
continue
}
blockNBT, err := ch.ApplySubChunkEntry(uint8(absY), &sub)
if err != nil {
logrus.Error(err)
}
if blockNBT != nil {
s.blockNBT[subPos] = blockNBT
}
chunk.LightArea([]*chunk.Chunk{ch}, 0, 0).Fill()
}
for _, p := range s.server.Players() {
p.Session().ViewSubChunks(world.SubChunkPos(pk.Position), offsets)
}
}

View File

@ -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},
}
}

View File

@ -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
}

View File

@ -0,0 +1,64 @@
package seconduser
import (
"sync"
"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/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) LoadChunk(position world.ChunkPos, dim world.Dimension) (c *chunk.Chunk, exists bool, err error) {
c, ok := p.s.chunks[position]
return c, ok, nil
}
func (p *provider) SaveChunk(position world.ChunkPos, c *chunk.Chunk, dim world.Dimension) error {
return nil
}
func (p *provider) LoadEntities(position world.ChunkPos, dim world.Dimension, reg world.EntityRegistry) ([]world.Entity, error) {
return nil, nil
}
func (p *provider) SaveEntities(position world.ChunkPos, entities []world.Entity, dim world.Dimension) error {
return nil
}
func (p *provider) LoadBlockNBT(position world.ChunkPos, dim world.Dimension) ([]map[string]any, error) {
return nil, nil
}
func (p *provider) SaveBlockNBT(position world.ChunkPos, data []map[string]any, dim world.Dimension) error {
return nil
}

View File

@ -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
blockNBT map[protocol.SubChunkPos][]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),
blockNBT: make(map[protocol.SubChunkPos][]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.Conn) {
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.Conn) {
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())
}) {
}
}

127
handlers/skins.go Normal file
View File

@ -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 "", nil, false
}
s.playerNames[playerID] = playerName
skin := &utils.Skin{Skin: playerSkin}
if s.OnlyIfHasGeometry && !skin.HaveGeometry() {
return "", 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
},
}
}

182
handlers/worlds/chunk.go Normal file
View File

@ -0,0 +1,182 @@
package worlds
import (
"github.com/bedrock-tool/bedrocktool/locale"
"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) {
if len(w.worldState.chunks) > 0 {
w.SaveAndReset()
} else {
logrus.Info(locale.Loc("not_saving_empty", nil))
w.Reset(w.CurrentName())
}
dimensionID := pk.Dimension
if w.serverState.ispre118 && dimensionID == 0 {
dimensionID += 10
}
d, _ := world.DimensionByID(int(dimensionID))
w.worldState.dimension = d
}
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
}
if blockNBTs != nil {
w.worldState.blockNBT[protocol.SubChunkPos{
pk.Position.X(), 0, pk.Position.Z(),
}] = blockNBTs
}
w.worldState.chunks[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(pk.Position, ch, true)
}
}
}
func (w *worldsHandler) processSubChunk(pk *packet.SubChunk) {
posToRedraw := make(map[protocol.ChunkPos]bool)
for _, sub := range pk.SubChunkEntries {
var (
absX = pk.Position[0] + int32(sub.Offset[0])
absY = pk.Position[1] + int32(sub.Offset[1])
absZ = pk.Position[2] + int32(sub.Offset[2])
subPos = protocol.SubChunkPos{absX, absY, absZ}
pos = protocol.ChunkPos{absX, absZ}
)
ch, ok := w.worldState.chunks[pos]
if !ok {
logrus.Error(locale.Loc("subchunk_before_chunk", nil))
continue
}
blockNBT, err := ch.ApplySubChunkEntry(uint8(absY), &sub)
if err != nil {
logrus.Error(err)
}
if blockNBT != nil {
w.worldState.blockNBT[subPos] = 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:
if w.settings.BlockUpdates {
sp := protocol.SubChunkPos{pk.Position.X() << 4, 0, pk.Position.Z() << 4}
b, ok := w.worldState.blockNBT[sp]
if !ok {
w.worldState.blockNBT[sp] = []map[string]any{pk.NBTData}
} else {
for i, v := range b {
x, y, z := v["x"].(int32), v["y"].(int32), v["z"].(int32)
if x == pk.Position.X() && y == pk.Position.Y() && z == pk.Position.Z() {
b[i] = pk.NBTData
break
}
}
}
}
case *packet.UpdateBlock:
if w.settings.BlockUpdates {
cp := protocol.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, w.worldState.chunks[cp], true)
}
}
case *packet.UpdateSubChunkBlocks:
if w.settings.BlockUpdates {
cp := protocol.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, w.worldState.chunks[cp], true)
}
}
}
return pk
}

BIN
handlers/worlds/chunk.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 432 B

View File

@ -1,11 +1,12 @@
package world_test package worlds_test
import ( import (
"image/png" "image/png"
"os" "os"
"runtime/pprof"
"testing" "testing"
"github.com/bedrock-tool/bedrocktool/subcommands/world" "github.com/bedrock-tool/bedrocktool/utils"
"github.com/df-mc/dragonfly/server/block/cube" "github.com/df-mc/dragonfly/server/block/cube"
"github.com/df-mc/dragonfly/server/world/chunk" "github.com/df-mc/dragonfly/server/world/chunk"
) )
@ -13,7 +14,7 @@ import (
func Test(t *testing.T) { func Test(t *testing.T) {
data, _ := os.ReadFile("chunk.bin") data, _ := os.ReadFile("chunk.bin")
ch, _, _ := chunk.NetworkDecode(33, data, 6, cube.Range{0, 255}, true, false) ch, _, _ := chunk.NetworkDecode(33, data, 6, cube.Range{0, 255}, true, false)
i := world.Chunk2Img(ch) i := utils.Chunk2Img(ch, true)
f, _ := os.Create("chunk.png") f, _ := os.Create("chunk.png")
png.Encode(f, i) png.Encode(f, i)
f.Close() f.Close()
@ -21,19 +22,32 @@ func Test(t *testing.T) {
func Benchmark_chunk_decode(b *testing.B) { func Benchmark_chunk_decode(b *testing.B) {
data, _ := os.ReadFile("chunk.bin") data, _ := os.ReadFile("chunk.bin")
cf, _ := os.Create("cpu.pprof")
err := pprof.StartCPUProfile(cf)
if err != nil {
b.Error(err)
}
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
_, _, err := chunk.NetworkDecode(33, data, 6, cube.Range{0, 255}, true, false) _, _, err := chunk.NetworkDecode(33, data, 6, cube.Range{0, 255}, true, false)
if err != nil { if err != nil {
b.Error(err) b.Error(err)
} }
} }
pprof.StopCPUProfile()
} }
func Benchmark_render_chunk(b *testing.B) { func Benchmark_render_chunk(b *testing.B) {
data, _ := os.ReadFile("chunk.bin") data, _ := os.ReadFile("chunk.bin")
ch, _, _ := chunk.NetworkDecode(33, data, 6, cube.Range{0, 255}, true, false) ch, _, _ := chunk.NetworkDecode(33, data, 6, cube.Range{0, 255}, true, false)
for i := 0; i < b.N; i++ { cf, _ := os.Create("cpu.pprof")
world.Chunk2Img(ch) err := pprof.StartCPUProfile(cf)
if err != nil {
b.Error(err)
} }
for i := 0; i < b.N; i++ {
utils.Chunk2Img(ch, true)
}
pprof.StopCPUProfile()
} }

View File

@ -1,4 +1,4 @@
package world package worlds
import ( import (
"github.com/bedrock-tool/bedrocktool/utils/behaviourpack" "github.com/bedrock-tool/bedrocktool/utils/behaviourpack"
@ -61,16 +61,17 @@ func (e serverEntity) Type() world.EntityType {
return e.EntityType return e.EntityType
} }
func (w *WorldState) processAddActor(pk *packet.AddActor) { func (w *worldsHandler) processAddActor(pk *packet.AddActor) {
e, ok := w.entities[pk.EntityRuntimeID] e, ok := w.worldState.entities[pk.EntityRuntimeID]
if !ok { if !ok {
e = &entityState{ e = &entityState{
RuntimeID: pk.EntityRuntimeID, RuntimeID: pk.EntityRuntimeID,
UniqueID: pk.EntityUniqueID, UniqueID: pk.EntityUniqueID,
EntityType: pk.EntityType, EntityType: pk.EntityType,
Inventory: make(map[byte]map[byte]protocol.ItemInstance),
Metadata: make(map[uint32]any), Metadata: make(map[uint32]any),
} }
w.entities[pk.EntityRuntimeID] = e w.worldState.entities[pk.EntityRuntimeID] = e
w.bp.AddEntity(behaviourpack.EntityIn{ w.bp.AddEntity(behaviourpack.EntityIn{
Identifier: pk.EntityType, Identifier: pk.EntityType,
@ -125,6 +126,9 @@ func entityMetadataToNBT(metadata protocol.EntityMetadata, nbt map[string]any) {
if color2, ok := metadata[protocol.EntityDataKeyColorTwoIndex]; ok { if color2, ok := metadata[protocol.EntityDataKeyColorTwoIndex]; ok {
nbt["Color2"] = color2 nbt["Color2"] = color2
} }
if skinID, ok := metadata[protocol.EntityDataKeySkinID]; ok {
nbt["SkinID"] = int32(skinID.(int32))
}
if name, ok := metadata[protocol.EntityDataKeyName]; ok { if name, ok := metadata[protocol.EntityDataKeyName]; ok {
nbt["CustomName"] = name nbt["CustomName"] = name
@ -137,47 +141,49 @@ func entityMetadataToNBT(metadata protocol.EntityMetadata, nbt map[string]any) {
} }
} }
if metadata.Flag(protocol.EntityDataKeyFlags, protocol.EntityDataFlagNoAI) { if _, ok := metadata[protocol.EntityDataKeyFlags]; ok {
nbt["IsAutonomous"] = false if metadata.Flag(protocol.EntityDataKeyFlags, protocol.EntityDataFlagNoAI) {
} nbt["IsAutonomous"] = false
for k, v := range flagNames { }
nbt[v] = metadata.Flag(protocol.EntityDataKeyFlags, k) for k, v := range flagNames {
} nbt[v] = metadata.Flag(protocol.EntityDataKeyFlags, k)
}
AlwaysShowName := metadata.Flag(protocol.EntityDataKeyFlags, protocol.EntityDataFlagAlwaysShowName) AlwaysShowName := metadata.Flag(protocol.EntityDataKeyFlags, protocol.EntityDataFlagAlwaysShowName)
if AlwaysShowName { if AlwaysShowName {
nbt["CustomNameVisible"] = true nbt["CustomNameVisible"] = true
} }
type effect struct { type effect struct {
Id byte Id byte
Amplifier byte Amplifier byte
Duration int32 Duration int32
DurationEasy int32 DurationEasy int32
DurationNormal int32 DurationNormal int32
DurationHard int32 DurationHard int32
Ambient bool Ambient bool
ShowParticles bool ShowParticles bool
DisplayOnScreenTextureAnimation bool DisplayOnScreenTextureAnimation bool
} }
activeEffects := []effect{} activeEffects := []effect{}
addEffect := func(id int, showParticles bool) { addEffect := func(id int, showParticles bool) {
activeEffects = append(activeEffects, effect{ activeEffects = append(activeEffects, effect{
Id: byte(id), Id: byte(id),
Amplifier: 1, Amplifier: 1,
Duration: 10000000, Duration: 10000000,
ShowParticles: false, ShowParticles: false,
}) })
} }
invisible := metadata.Flag(protocol.EntityDataKeyFlags, protocol.EntityDataFlagInvisible) invisible := metadata.Flag(protocol.EntityDataKeyFlags, protocol.EntityDataFlagInvisible)
if invisible { if invisible {
addEffect(packet.EffectInvisibility, false) addEffect(packet.EffectInvisibility, false)
} }
if len(activeEffects) > 0 { if len(activeEffects) > 0 {
nbt["ActiveEffects"] = activeEffects nbt["ActiveEffects"] = activeEffects
}
} }
} }
@ -200,36 +206,42 @@ func (s *entityState) ToServerEntity() serverEntity {
entityMetadataToNBT(s.Metadata, e.EntityType.NBT) entityMetadataToNBT(s.Metadata, e.EntityType.NBT)
if s.Helmet != nil || s.Chestplate != nil || s.Leggings != nil || s.Boots != nil { if s.Helmet != nil || s.Chestplate != nil || s.Leggings != nil || s.Boots != nil {
armor := make([]map[string]any, 0, 4) e.EntityType.NBT["Armor"] = []map[string]any{
armor = append(armor, nbtconv.WriteItem(stackToItem(s.Helmet.Stack), true)) nbtconv.WriteItem(stackToItem(s.Helmet.Stack), true),
armor = append(armor, nbtconv.WriteItem(stackToItem(s.Chestplate.Stack), true)) nbtconv.WriteItem(stackToItem(s.Chestplate.Stack), true),
armor = append(armor, nbtconv.WriteItem(stackToItem(s.Leggings.Stack), true)) nbtconv.WriteItem(stackToItem(s.Leggings.Stack), true),
armor = append(armor, nbtconv.WriteItem(stackToItem(s.Boots.Stack), true)) nbtconv.WriteItem(stackToItem(s.Boots.Stack), true),
e.EntityType.NBT["Armor"] = armor }
} }
return e return e
} }
func (w *WorldState) ProcessEntityPackets(pk packet.Packet) packet.Packet { func (w *worldsHandler) ProcessEntityPackets(pk packet.Packet) packet.Packet {
if !w.settings.SaveEntities {
return pk
}
switch pk := pk.(type) { switch pk := pk.(type) {
case *packet.AddActor: case *packet.AddActor:
w.processAddActor(pk) w.processAddActor(pk)
case *packet.RemoveActor: case *packet.RemoveActor:
case *packet.SetActorData: case *packet.SetActorData:
e, ok := w.entities[pk.EntityRuntimeID] e, ok := w.worldState.entities[pk.EntityRuntimeID]
if ok { if ok {
for k, v := range pk.EntityMetadata { e.Metadata = pk.EntityMetadata
e.Metadata[k] = v w.bp.AddEntity(behaviourpack.EntityIn{
} Identifier: e.EntityType,
Attr: nil,
Meta: pk.EntityMetadata,
})
} }
case *packet.SetActorMotion: case *packet.SetActorMotion:
e, ok := w.entities[pk.EntityRuntimeID] e, ok := w.worldState.entities[pk.EntityRuntimeID]
if ok { if ok {
e.Velocity = pk.Velocity e.Velocity = pk.Velocity
} }
case *packet.MoveActorDelta: case *packet.MoveActorDelta:
e, ok := w.entities[pk.EntityRuntimeID] e, ok := w.worldState.entities[pk.EntityRuntimeID]
if ok { if ok {
if pk.Flags&packet.MoveActorDeltaFlagHasX != 0 { if pk.Flags&packet.MoveActorDeltaFlagHasX != 0 {
e.Position[0] = pk.Position[0] e.Position[0] = pk.Position[0]
@ -251,14 +263,14 @@ func (w *WorldState) ProcessEntityPackets(pk packet.Packet) packet.Packet {
//} //}
} }
case *packet.MoveActorAbsolute: case *packet.MoveActorAbsolute:
e, ok := w.entities[pk.EntityRuntimeID] e, ok := w.worldState.entities[pk.EntityRuntimeID]
if ok { if ok {
e.Position = pk.Position e.Position = pk.Position
e.Pitch = pk.Rotation.X() e.Pitch = pk.Rotation.X()
e.Yaw = pk.Rotation.Y() e.Yaw = pk.Rotation.Y()
} }
case *packet.MobEquipment: case *packet.MobEquipment:
e, ok := w.entities[pk.EntityRuntimeID] e, ok := w.worldState.entities[pk.EntityRuntimeID]
if ok { if ok {
w, ok := e.Inventory[pk.WindowID] w, ok := e.Inventory[pk.WindowID]
if !ok { if !ok {
@ -268,7 +280,7 @@ func (w *WorldState) ProcessEntityPackets(pk packet.Packet) packet.Packet {
w[pk.HotBarSlot] = pk.NewItem w[pk.HotBarSlot] = pk.NewItem
} }
case *packet.MobArmourEquipment: case *packet.MobArmourEquipment:
e, ok := w.entities[pk.EntityRuntimeID] e, ok := w.worldState.entities[pk.EntityRuntimeID]
if ok { if ok {
e.Helmet = &pk.Helmet e.Helmet = &pk.Helmet
e.Chestplate = &pk.Chestplate e.Chestplate = &pk.Chestplate

388
handlers/worlds/items.go Normal file
View File

@ -0,0 +1,388 @@
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/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
}
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.worldState.blockNBT[cp]
for i, v := range nbts {
NBTPos := protocol.BlockPos{v["x"].(int32), v["y"].(int32), v["z"].(int32)}
if NBTPos == pos {
w.worldState.blockNBT[cp][i]["Items"] = nbtconv.InvToNBT(inv)
break
}
}
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)
case *packet.MobArmourEquipment:
if pk.EntityRuntimeID == w.proxy.Server.GameData().EntityRuntimeID {
}
}
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)
}
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.proxy.Server.GameData().PlayerPosition
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
}

View File

@ -1,4 +1,4 @@
package world package worlds
import ( import (
"image" "image"
@ -10,6 +10,7 @@ import (
"github.com/bedrock-tool/bedrocktool/locale" "github.com/bedrock-tool/bedrocktool/locale"
"github.com/bedrock-tool/bedrocktool/ui/messages" "github.com/bedrock-tool/bedrocktool/ui/messages"
"github.com/bedrock-tool/bedrocktool/utils" "github.com/bedrock-tool/bedrocktool/utils"
"github.com/go-gl/mathgl/mgl32"
"golang.design/x/lockfree" "golang.design/x/lockfree"
"github.com/df-mc/dragonfly/server/world/chunk" "github.com/df-mc/dragonfly/server/world/chunk"
@ -64,8 +65,9 @@ func (m *MapUI) GetBounds() (min, max protocol.ChunkPos) {
} }
type RenderElem struct { type RenderElem struct {
pos protocol.ChunkPos pos protocol.ChunkPos
ch *chunk.Chunk ch *chunk.Chunk
complete bool
} }
type MapUI struct { type MapUI struct {
@ -78,10 +80,10 @@ type MapUI struct {
l sync.RWMutex l sync.RWMutex
ticker *time.Ticker ticker *time.Ticker
w *WorldState w *worldsHandler
} }
func NewMapUI(w *WorldState) *MapUI { func NewMapUI(w *worldsHandler) *MapUI {
m := &MapUI{ m := &MapUI{
img: image.NewRGBA(image.Rect(0, 0, 128, 128)), img: image.NewRGBA(image.Rect(0, 0, 128, 128)),
zoomLevel: 16, zoomLevel: 16,
@ -94,7 +96,7 @@ func NewMapUI(w *WorldState) *MapUI {
} }
func (m *MapUI) Start() { func (m *MapUI) Start() {
r := m.w.gui.Message("can_show_images", nil) r := m.w.gui.Message(messages.CanShowImages{})
if r.Ok { if r.Ok {
m.showOnGui = true m.showOnGui = true
} }
@ -181,14 +183,14 @@ func (m *MapUI) SchedRedraw() {
// Redraw draws chunk images to the map image // Redraw draws chunk images to the map image
func (m *MapUI) Redraw() { func (m *MapUI) Redraw() {
m.l.Lock() m.l.Lock()
updatedChunks := []protocol.ChunkPos{} updatedChunks := make([]protocol.ChunkPos, 0, m.renderQueue.Length())
for { for {
r, ok := m.renderQueue.Dequeue().(*RenderElem) r, ok := m.renderQueue.Dequeue().(*RenderElem)
if !ok { if !ok {
break break
} }
if r.ch != nil { if r.ch != nil {
m.renderedChunks[r.pos] = Chunk2Img(r.ch) m.renderedChunks[r.pos] = utils.Chunk2Img(r.ch, !r.complete)
} else { } else {
m.renderedChunks[r.pos] = black16x16 m.renderedChunks[r.pos] = black16x16
} }
@ -197,11 +199,10 @@ func (m *MapUI) Redraw() {
m.l.Unlock() m.l.Unlock()
middle := protocol.ChunkPos{ middle := protocol.ChunkPos{
int32(m.w.PlayerPos.Position.X()), int32(m.w.serverState.PlayerPos.Position.X()),
int32(m.w.PlayerPos.Position.Z()), int32(m.w.serverState.PlayerPos.Position.Z()),
} }
// total_width := 32 * math.Ceil(float64(chunks_x)/32)
chunksPerLine := float64(128 / m.zoomLevel) chunksPerLine := float64(128 / m.zoomLevel)
pxPerBlock := 128 / chunksPerLine / 16 // how many pixels per block pxPerBlock := 128 / chunksPerLine / 16 // how many pixels per block
pxSizeChunk := int(math.Floor(pxPerBlock * 16)) pxSizeChunk := int(math.Floor(pxPerBlock * 16))
@ -223,12 +224,11 @@ func (m *MapUI) Redraw() {
utils.DrawImgScaledPos(m.img, m.renderedChunks[_ch], px, pxSizeChunk) utils.DrawImgScaledPos(m.img, m.renderedChunks[_ch], px, pxSizeChunk)
} }
} }
ChunkCount := len(m.renderedChunks)
if m.showOnGui { if m.showOnGui {
min, max := m.GetBounds() min, max := m.GetBounds()
m.w.gui.Message(messages.UpdateMap, messages.UpdateMapPayload{ m.w.gui.Message(messages.UpdateMap{
ChunkCount: ChunkCount, ChunkCount: len(m.renderedChunks),
Rotation: m.w.PlayerPos.Yaw, Rotation: m.w.serverState.PlayerPos.Yaw,
UpdatedTiles: updatedChunks, UpdatedTiles: updatedChunks,
Tiles: m.renderedChunks, Tiles: m.renderedChunks,
BoundsMin: min, BoundsMin: min,
@ -261,19 +261,34 @@ func (m *MapUI) ToImage() *image.RGBA {
return img2 return img2
} }
func (m *MapUI) SetChunk(pos protocol.ChunkPos, ch *chunk.Chunk) { func (m *MapUI) SetChunk(pos protocol.ChunkPos, ch *chunk.Chunk, complete bool) {
m.renderQueue.Enqueue(&RenderElem{pos, ch}) m.renderQueue.Enqueue(&RenderElem{pos, ch, complete})
m.SchedRedraw() m.SchedRedraw()
} }
func (w *WorldState) ProcessAnimate(pk *packet.Animate) { func (w *worldsHandler) ProcessAnimate(pk *packet.Animate) {
if pk.ActionType == packet.AnimateActionSwingArm { if pk.ActionType == packet.AnimateActionSwingArm {
w.mapUI.ChangeZoom() w.mapUI.ChangeZoom()
w.proxy.SendPopup(locale.Loc("zoom_level", locale.Strmap{"Level": w.mapUI.zoomLevel})) w.proxy.SendPopup(locale.Loc("zoom_level", locale.Strmap{"Level": w.mapUI.zoomLevel}))
} }
} }
func (w *WorldState) processMapPacketsClient(pk packet.Packet, forward *bool) packet.Packet { 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) { switch pk := pk.(type) {
case *packet.MovePlayer: case *packet.MovePlayer:
w.SetPlayerPos(pk.Position, pk.Pitch, pk.Yaw, pk.HeadYaw) w.SetPlayerPos(pk.Position, pk.Pitch, pk.Yaw, pk.HeadYaw)

567
handlers/worlds/world.go Normal file
View File

@ -0,0 +1,567 @@
package worlds
import (
"context"
"encoding/json"
"fmt"
"image"
"image/png"
"math/rand"
"os"
"path"
"strconv"
"strings"
"time"
"github.com/bedrock-tool/bedrocktool/locale"
"github.com/bedrock-tool/bedrocktool/ui/messages"
"github.com/bedrock-tool/bedrocktool/utils"
"github.com/bedrock-tool/bedrocktool/utils/behaviourpack"
"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[protocol.ChunkPos]*chunk.Chunk
blockNBT map[protocol.SubChunkPos][]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
proxy *utils.ProxyContext
mapUI *MapUI
gui utils.UI
bp *behaviourpack.BehaviourPack
worldState worldState
serverState serverState
settings WorldSettings
}
var black16x16 = image.NewRGBA(image.Rect(0, 0, 16, 16))
func init() {
for i := 3; i < len(black16x16.Pix); i += 4 {
black16x16.Pix[i] = 255
}
}
func NewWorldsHandler(ctx context.Context, ui utils.UI, settings WorldSettings) *utils.ProxyHandler {
w := &worldsHandler{
ctx: ctx,
mapUI: nil,
gui: ui,
bp: nil,
serverState: serverState{
ispre118: false,
worldCounter: 0,
ChunkRadius: 0,
playerInventory: nil,
PlayerPos: TPlayerPos{},
},
settings: settings,
}
w.mapUI = NewMapUI(w)
w.Reset(w.CurrentName())
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.Conn) {
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()
},
}
}
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 > 1 {
worldName = fmt.Sprintf("world-%d", w.serverState.worldCounter)
}
return worldName
}
func (w *worldsHandler) Reset(newName string) {
w.worldState = worldState{
dimension: w.worldState.dimension,
chunks: make(map[protocol.ChunkPos]*chunk.Chunk),
blockNBT: make(map[protocol.SubChunkPos][]map[string]any),
entities: make(map[uint64]*entityState),
openItemContainers: make(map[byte]*itemContainer),
Name: newName,
}
w.mapUI.Reset()
}
func (w *worldState) cullChunks() {
keys := make([]protocol.ChunkPos, 0, len(w.chunks))
for cp := range w.chunks {
keys = append(keys, cp)
}
for _, cp := range fp.Filter(func(cp protocol.ChunkPos) bool {
return !fp.Some(func(sc *chunk.SubChunk) bool {
return !sc.Empty()
})(w.chunks[cp].Sub())
})(keys) {
delete(w.chunks, cp)
}
}
func (w *worldState) Save(folder string) (*mcdb.Provider, error) {
provider, err := mcdb.New(logrus.StandardLogger(), folder, opt.DefaultCompression)
if err != nil {
return nil, err
}
// save chunk data
for cp, c := range w.chunks {
provider.SaveChunk((world.ChunkPos)(cp), c, w.dimension)
}
// save block nbt data
blockNBT := make(map[world.ChunkPos][]map[string]any)
for scp, v := range w.blockNBT { // 3d to 2d
cp := world.ChunkPos{scp.X(), scp.Z()}
blockNBT[cp] = append(blockNBT[cp], v...)
}
for cp, v := range blockNBT {
err = provider.SaveBlockNBT(cp, v, w.dimension)
if err != nil {
logrus.Error(err)
}
}
// save entities
chunkEntities := make(map[world.ChunkPos][]world.Entity)
for _, es := range w.entities {
cp := world.ChunkPos{int32(es.Position.X()) >> 4, int32(es.Position.Z()) >> 4}
chunkEntities[cp] = append(chunkEntities[cp], es.ToServerEntity())
}
for cp, v := range chunkEntities {
err = provider.SaveEntities(cp, v, w.dimension)
if err != nil {
logrus.Error(err)
}
}
return provider, err
}
// SaveAndReset writes the world to a folder, resets all the chunks
func (w *worldsHandler) SaveAndReset() {
w.worldState.cullChunks()
if len(w.worldState.chunks) == 0 {
w.Reset(w.CurrentName())
return
}
logrus.Infof(locale.Loc("saving_world", locale.Strmap{"Name": w.worldState.Name, "Count": len(w.worldState.chunks)}))
w.gui.Message(messages.SavingWorld{
Name: w.worldState.Name,
Chunks: len(w.worldState.chunks),
})
// open world
folder := path.Join("worlds", fmt.Sprintf("%s/%s", w.serverState.Name, w.worldState.Name))
os.RemoveAll(folder)
os.MkdirAll(folder, 0o777)
provider, err := w.worldState.Save(folder)
if err != nil {
logrus.Error(err)
return
}
err = provider.SaveLocalPlayerData(w.playerData())
if err != nil {
logrus.Error(err)
}
playerPos := w.proxy.Server.GameData().PlayerPosition
spawnPos := cube.Pos{int(playerPos.X()), int(playerPos.Y()), int(playerPos.Z())}
// write metadata
s := provider.Settings()
s.Spawn = spawnPos
s.Name = w.worldState.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
}
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
}
ld.RandomTickSpeed = 0
s.CurrentTick = 0
provider.SaveSettings(s)
if err = provider.Close(); err != nil {
logrus.Error(err)
}
w.serverState.worldCounter += 1
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 {
var rdeps []dep
for k, p := range packs {
if p.Encrypted() && !p.CanDecrypt() {
logrus.Warnf("Cant add %s, it is encrypted", p.Name())
continue
}
logrus.Infof(locale.Loc("adding_pack", locale.Strmap{"Name": k}))
name := p.Name()
name = strings.ReplaceAll(name, ":", "_")
packFolder := path.Join(folder, "resource_packs", name)
os.MkdirAll(packFolder, 0o755)
utils.UnpackZip(p, int64(p.Len()), packFolder)
rdeps = append(rdeps, dep{
PackID: p.Manifest().Header.UUID,
Version: p.Manifest().Header.Version,
})
}
if len(rdeps) > 0 {
addPacksJSON("world_resource_packs.json", rdeps)
}
}
}
if w.settings.SaveImage {
f, _ := os.Create(folder + ".png")
png.Encode(f, w.mapUI.ToImage())
f.Close()
}
// zip it
filename := folder + ".mcworld"
if err := utils.ZipFolder(filename, folder); err != nil {
logrus.Error(err)
}
logrus.Info(locale.Loc("saved", locale.Strmap{"Name": filename}))
//os.RemoveAll(folder)
w.Reset(w.CurrentName())
w.gui.Message(messages.SetUIState(messages.UIStateMain))
}
func (w *worldsHandler) OnConnect(err error) bool {
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
}

View File

@ -54,11 +54,6 @@ func (c *BlindProxyCMD) Execute(ctx context.Context, ui utils.UI) error {
listener.ID(), "Creative", 1, listener.Addr().(*net.UDPAddr).Port, listener.Addr().(*net.UDPAddr).Port, listener.ID(), "Creative", 1, listener.Addr().(*net.UDPAddr).Port, listener.Addr().(*net.UDPAddr).Port,
))) )))
go func() {
<-ctx.Done()
listener.Close()
}()
clientConn, err := listener.Accept() clientConn, err := listener.Accept()
if err != nil { if err != nil {
return err return err

View File

@ -1,47 +1,18 @@
package subcommands package subcommands
import ( import (
"bytes"
"context" "context"
"encoding/binary"
"flag" "flag"
"io"
"net"
"os"
"sync"
"time"
"github.com/bedrock-tool/bedrocktool/handlers"
"github.com/bedrock-tool/bedrocktool/locale" "github.com/bedrock-tool/bedrocktool/locale"
"github.com/bedrock-tool/bedrocktool/utils" "github.com/bedrock-tool/bedrocktool/utils"
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
"github.com/sirupsen/logrus"
) )
func init() { func init() {
utils.RegisterCommand(&CaptureCMD{}) utils.RegisterCommand(&CaptureCMD{})
} }
var dumpLock sync.Mutex
func dumpPacket(f io.WriteCloser, toServer bool, payload []byte) {
dumpLock.Lock()
defer dumpLock.Unlock()
f.Write([]byte{0xAA, 0xAA, 0xAA, 0xAA})
packetSize := uint32(len(payload))
binary.Write(f, binary.LittleEndian, packetSize)
binary.Write(f, binary.LittleEndian, toServer)
binary.Write(f, binary.LittleEndian, time.Now().UnixMilli())
n, err := f.Write(payload)
if err != nil {
logrus.Error(err)
}
if n < int(packetSize) {
f.Write(make([]byte, int(packetSize)-n))
}
f.Write([]byte{0xBB, 0xBB, 0xBB, 0xBB})
}
type CaptureCMD struct { type CaptureCMD struct {
ServerAddress string ServerAddress string
} }
@ -58,31 +29,10 @@ func (c *CaptureCMD) Execute(ctx context.Context, ui utils.UI) error {
return err return err
} }
os.Mkdir("captures", 0o775)
fio, err := os.Create("captures/" + hostname + "-" + time.Now().Format("2006-01-02_15-04-05") + ".pcap2")
if err != nil {
return err
}
defer fio.Close()
utils.WriteReplayHeader(fio)
proxy, err := utils.NewProxy() proxy, err := utils.NewProxy()
if err != nil {
logrus.Fatal(err)
}
proxy.PacketFunc = func(header packet.Header, payload []byte, src, dst net.Addr) {
IsfromClient := src.String() == proxy.Client.LocalAddr().String()
buf := bytes.NewBuffer(nil)
header.Write(buf)
buf.Write(payload)
dumpPacket(fio, IsfromClient, buf.Bytes())
}
err = proxy.Run(ctx, address)
time.Sleep(2 * time.Second)
if err != nil { if err != nil {
return err return err
} }
return nil proxy.AddHandler(handlers.NewPacketCapturer())
return proxy.Run(ctx, address, hostname)
} }

View File

@ -3,15 +3,10 @@ package subcommands
import ( import (
"context" "context"
"flag" "flag"
"fmt"
"os"
"time"
"github.com/bedrock-tool/bedrocktool/handlers"
"github.com/bedrock-tool/bedrocktool/locale" "github.com/bedrock-tool/bedrocktool/locale"
"github.com/bedrock-tool/bedrocktool/utils" "github.com/bedrock-tool/bedrocktool/utils"
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
"github.com/sirupsen/logrus"
) )
type ChatLogCMD struct { type ChatLogCMD struct {
@ -32,35 +27,12 @@ func (c *ChatLogCMD) Execute(ctx context.Context, ui utils.UI) error {
return err return err
} }
filename := fmt.Sprintf("%s_%s_chat.log", hostname, time.Now().Format("2006-01-02_15-04-05_Z07"))
f, err := os.Create(filename)
if err != nil {
return err
}
defer f.Close()
proxy, err := utils.NewProxy() proxy, err := utils.NewProxy()
if err != nil { if err != nil {
return err return err
} }
proxy.PacketCB = func(pk packet.Packet, toServer bool, t time.Time) (packet.Packet, error) { proxy.AddHandler(handlers.NewChatLogger())
if text, ok := pk.(*packet.Text); ok { return proxy.Run(ctx, address, hostname)
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] ", t.Format(time.RFC3339)))
logrus.Info(logLine)
if toServer {
f.WriteString("SENT: ")
}
f.WriteString(logLine + "\n")
}
return pk, nil
}
err = proxy.Run(ctx, address)
return err
} }
func init() { func init() {

View File

@ -3,53 +3,36 @@ package subcommands
import ( import (
"context" "context"
"flag" "flag"
"strings"
seconduser "github.com/bedrock-tool/bedrocktool/handlers/second-user"
"github.com/bedrock-tool/bedrocktool/locale" "github.com/bedrock-tool/bedrocktool/locale"
"github.com/bedrock-tool/bedrocktool/utils" "github.com/bedrock-tool/bedrocktool/utils"
) )
type DebugProxyCMD struct { type DebugProxyCMD struct {
ServerAddress string ServerAddress string
Filter string
} }
func (*DebugProxyCMD) Name() string { return "debug-proxy" } func (*DebugProxyCMD) Name() string { return "debug-proxy" }
func (*DebugProxyCMD) Synopsis() string { return locale.Loc("debug_proxy_synopsis", nil) } func (*DebugProxyCMD) Synopsis() string { return locale.Loc("debug_proxy_synopsis", nil) }
func (c *DebugProxyCMD) SetFlags(f *flag.FlagSet) { func (c *DebugProxyCMD) SetFlags(f *flag.FlagSet) {
f.StringVar(&c.ServerAddress, "address", "", locale.Loc("remote_address", nil)) f.StringVar(&c.ServerAddress, "address", "", locale.Loc("remote_address", nil))
f.StringVar(&c.Filter, "filter", "", locale.Loc("packet_filter", nil))
} }
func (c *DebugProxyCMD) Execute(ctx context.Context, ui utils.UI) error { func (c *DebugProxyCMD) Execute(ctx context.Context, ui utils.UI) error {
address, _, err := utils.ServerInput(ctx, c.ServerAddress) address, hostname, err := utils.ServerInput(ctx, c.ServerAddress)
if err != nil { if err != nil {
return err return err
} }
utils.Options.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() proxy, err := utils.NewProxy()
if err != nil { if err != nil {
return err return err
} }
err = proxy.Run(ctx, address) proxy.AddHandler(seconduser.NewSecondUser())
return err return proxy.Run(ctx, address, hostname)
} }
func init() { func init() {

View File

@ -3,128 +3,15 @@ package skins
import ( import (
"context" "context"
"flag" "flag"
"fmt"
"os"
"path"
"strings"
"time"
"github.com/bedrock-tool/bedrocktool/handlers"
"github.com/bedrock-tool/bedrocktool/locale" "github.com/bedrock-tool/bedrocktool/locale"
"github.com/bedrock-tool/bedrocktool/ui/messages" "github.com/bedrock-tool/bedrocktool/ui/messages"
"github.com/bedrock-tool/bedrocktool/utils" "github.com/bedrock-tool/bedrocktool/utils"
"github.com/google/uuid" "github.com/sandertv/gophertunnel/minecraft"
"github.com/sandertv/gophertunnel/minecraft/protocol"
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
"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 skinsSession struct {
PlayerNameFilter string
OnlyIfHasGeometry bool
ServerName string
Proxy *utils.ProxyContext
fpath string
playerSkinPacks map[uuid.UUID]*SkinPack
playerNames map[uuid.UUID]string
}
func NewSkinsSession(proxy *utils.ProxyContext, serverName, fpath string) *skinsSession {
return &skinsSession{
ServerName: serverName,
Proxy: proxy,
fpath: fpath,
playerSkinPacks: make(map[uuid.UUID]*SkinPack),
playerNames: make(map[uuid.UUID]string),
}
}
func (s *skinsSession) AddPlayerSkin(playerID uuid.UUID, playerName string, skin *Skin) (added bool) {
p, ok := s.playerSkinPacks[playerID]
if !ok {
creating := fmt.Sprintf("Creating Skinpack for %s", playerName)
s.Proxy.SendPopup(creating)
logrus.Info(creating)
p = NewSkinPack(playerName, s.fpath)
s.playerSkinPacks[playerID] = p
}
if p.AddSkin(skin) {
if ok {
addedStr := fmt.Sprintf("Added a skin to %s", playerName)
s.Proxy.SendPopup(addedStr)
logrus.Info(addedStr)
}
added = true
}
if err := p.Save(path.Join(s.fpath, playerName), s.ServerName); err != nil {
logrus.Error(err)
}
return added
}
func (s *skinsSession) AddSkin(playerName string, playerID uuid.UUID, playerSkin *protocol.Skin) (string, *Skin, bool) {
if playerName == "" {
playerName = s.playerNames[playerID]
if playerName == "" {
playerName = playerID.String()
}
}
if !strings.HasPrefix(playerName, s.PlayerNameFilter) {
return "", nil, false
}
s.playerNames[playerID] = playerName
skin := &Skin{playerSkin}
if s.OnlyIfHasGeometry && !skin.HaveGeometry() {
return "", nil, false
}
wasAdded := s.AddPlayerSkin(playerID, playerName, skin)
return playerName, skin, wasAdded
}
type skinAdd struct {
PlayerName string
Skin *protocol.Skin
}
func (s *skinsSession) 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
}
type SkinCMD struct { type SkinCMD struct {
ServerAddress string ServerAddress string
Filter string Filter string
@ -148,41 +35,25 @@ func (c *SkinCMD) Execute(ctx context.Context, ui utils.UI) error {
proxy, _ := utils.NewProxy() proxy, _ := utils.NewProxy()
proxy.WithClient = !c.NoProxy proxy.WithClient = !c.NoProxy
proxy.OnClientConnect = func(hasClient bool) { proxy.AddHandler(handlers.NewSkinSaver(func(sa handlers.SkinAdd) {
ui.Message(messages.SetUIState, messages.UIStateConnecting) ui.Message(messages.NewSkin{
} PlayerName: sa.PlayerName,
proxy.ConnectCB = func(err error) bool { Skin: sa.Skin,
if err != nil { })
return false }))
} proxy.AddHandler(&utils.ProxyHandler{
ui.Message(messages.SetUIState, messages.UIStateMain) Name: "Skin CMD",
logrus.Info(locale.Loc("ctrl_c_to_exit", nil)) OnClientConnect: func(conn *minecraft.Conn) {
return true ui.Message(messages.SetUIState(messages.UIStateConnecting))
} },
})
outPathBase := fmt.Sprintf("skins/%s", hostname)
os.MkdirAll(outPathBase, 0o755)
s := NewSkinsSession(proxy, hostname, outPathBase)
proxy.PacketCB = func(pk packet.Packet, toServer bool, _ time.Time) (packet.Packet, error) {
if !toServer {
for _, s := range s.ProcessPacket(pk) {
ui.Message(messages.NewSkin, messages.NewSkinPayload{
PlayerName: s.PlayerName,
Skin: s.Skin,
})
}
}
return pk, nil
}
if proxy.WithClient { if proxy.WithClient {
ui.Message(messages.SetUIState, messages.UIStateConnect) ui.Message(messages.SetUIState(messages.UIStateConnect))
} else { } else {
ui.Message(messages.SetUIState, messages.UIStateConnecting) ui.Message(messages.SetUIState(messages.UIStateConnecting))
} }
err = proxy.Run(ctx, address) err = proxy.Run(ctx, address, hostname)
return err return err
} }

Binary file not shown.

View File

@ -1,123 +0,0 @@
package world
import (
"github.com/bedrock-tool/bedrocktool/locale"
"github.com/df-mc/dragonfly/server/world"
"github.com/df-mc/dragonfly/server/world/chunk"
"github.com/sandertv/gophertunnel/minecraft/protocol"
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
"github.com/sirupsen/logrus"
)
func (w *WorldState) processChangeDimension(pk *packet.ChangeDimension) {
if len(w.chunks) > 0 {
w.SaveAndReset()
} else {
logrus.Info(locale.Loc("not_saving_empty", nil))
w.Reset()
}
dimensionID := pk.Dimension
if w.ispre118 {
dimensionID += 10
}
w.Dim = dimensionIDMap[uint8(dimensionID)]
}
func (w *WorldState) processLevelChunk(pk *packet.LevelChunk) {
_, exists := w.chunks[pk.Position]
if exists {
return
}
// ignore empty chunks THANKS WEIRD SERVER SOFTWARE DEVS
if len(pk.RawPayload) == 0 {
logrus.Info(locale.Loc("empty_chunk", nil))
return
}
ch, blockNBTs, err := chunk.NetworkDecode(world.AirRID(), pk.RawPayload, int(pk.SubChunkCount), w.Dim.Range(), w.ispre118, w.bp.HasBlocks())
if err != nil {
logrus.Error(err)
return
}
if blockNBTs != nil {
w.blockNBT[protocol.SubChunkPos{
pk.Position.X(), 0, pk.Position.Z(),
}] = blockNBTs
}
w.chunks[pk.Position] = ch
max := w.Dim.Range().Height() / 16
switch pk.SubChunkCount {
case protocol.SubChunkRequestModeLimited:
max = int(pk.HighestSubChunk)
fallthrough
case protocol.SubChunkRequestModeLimitless:
w.proxy.Server.WritePacket(&packet.SubChunkRequest{
Dimension: int32(w.Dim.EncodeDimension()),
Position: protocol.SubChunkPos{
pk.Position.X(), 0, pk.Position.Z(),
},
Offsets: offsetTable[:max],
})
default:
// legacy
empty := true
for _, sub := range ch.Sub() {
if !sub.Empty() {
empty = false
break
}
}
if !empty {
w.mapUI.SetChunk(pk.Position, ch)
}
}
}
func (w *WorldState) processSubChunk(pk *packet.SubChunk) {
posToRedraw := make(map[protocol.ChunkPos]bool)
for _, sub := range pk.SubChunkEntries {
var (
absX = pk.Position[0] + int32(sub.Offset[0])
absY = pk.Position[1] + int32(sub.Offset[1])
absZ = pk.Position[2] + int32(sub.Offset[2])
subPos = protocol.SubChunkPos{absX, absY, absZ}
pos = protocol.ChunkPos{absX, absZ}
)
ch, ok := w.chunks[pos]
if !ok {
logrus.Error(locale.Loc("subchunk_before_chunk", nil))
continue
}
blockNBT, err := ch.ApplySubChunkEntry(uint8(absY), &sub)
if err != nil {
logrus.Error(err)
}
if blockNBT != nil {
w.blockNBT[subPos] = blockNBT
}
posToRedraw[pos] = true
}
// redraw the chunks
for pos := range posToRedraw {
w.mapUI.SetChunk(pos, w.chunks[pos])
}
w.mapUI.SchedRedraw()
}
func (w *WorldState) ProcessChunkPackets(pk packet.Packet) packet.Packet {
switch pk := pk.(type) {
case *packet.ChangeDimension:
w.processChangeDimension(pk)
case *packet.LevelChunk:
w.processLevelChunk(pk)
w.proxy.SendPopup(locale.Locm("popup_chunk_count", locale.Strmap{"Count": len(w.chunks), "Name": w.WorldName}, len(w.chunks)))
case *packet.SubChunk:
w.processSubChunk(pk)
}
return pk
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 513 B

View File

@ -1,174 +0,0 @@
package world
import (
"github.com/bedrock-tool/bedrocktool/locale"
"github.com/bedrock-tool/bedrocktool/utils/nbtconv"
"github.com/df-mc/dragonfly/server/block"
"github.com/df-mc/dragonfly/server/item"
"github.com/df-mc/dragonfly/server/item/inventory"
"github.com/df-mc/dragonfly/server/world"
"github.com/sandertv/gophertunnel/minecraft/protocol"
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
"github.com/sirupsen/logrus"
)
type itemContainer struct {
OpenPacket *packet.ContainerOpen
Content *packet.InventoryContent
}
func (w *WorldState) processItemPacketsServer(pk packet.Packet) packet.Packet {
if !w.experimentInventory {
return pk
}
switch pk := pk.(type) {
case *packet.ContainerOpen:
// 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:
// save content
existing, ok := w.openItemContainers[byte(pk.WindowID)]
if !ok {
if pk.WindowID == 0x0 { // inventory
w.openItemContainers[byte(pk.WindowID)] = &itemContainer{
Content: pk,
}
}
break
}
existing.Content = pk
case *packet.ContainerClose:
// find container info
existing, ok := w.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
}
pos := existing.OpenPacket.ContainerPosition
cp := protocol.SubChunkPos{pos.X() << 4, pos.Z() << 4}
// create inventory
inv := inventory.New(len(existing.Content.Content), nil)
for i, c := range existing.Content.Content {
item := stackToItem(c.Stack)
inv.SetItem(i, item)
}
// put into subchunk
nbts := w.blockNBT[cp]
for i, v := range nbts {
NBTPos := protocol.BlockPos{v["x"].(int32), v["y"].(int32), v["z"].(int32)}
if NBTPos == pos {
w.blockNBT[cp][i]["Items"] = nbtconv.InvToNBT(inv)
break
}
}
w.proxy.SendMessage(locale.Loc("saved_block_inv", nil))
// remove it again
delete(w.openItemContainers, byte(pk.WindowID))
}
case *packet.ItemComponent:
w.bp.ApplyComponentEntries(pk.Items)
}
return pk
}
func (w *WorldState) processItemPacketsClient(pk packet.Packet, forward *bool) packet.Packet {
switch pk := pk.(type) {
case *packet.ItemStackRequest:
var requests []protocol.ItemStackRequest
for _, isr := range pk.Requests {
for _, sra := range isr.Actions {
if sra, ok := sra.(*protocol.TakeStackRequestAction); ok {
if sra.Source.StackNetworkID == MapItemPacket.Content[0].StackNetworkID {
continue
}
}
if sra, ok := sra.(*protocol.DropStackRequestAction); ok {
if sra.Source.StackNetworkID == MapItemPacket.Content[0].StackNetworkID {
continue
}
}
if sra, ok := sra.(*protocol.DestroyStackRequestAction); ok {
if sra.Source.StackNetworkID == MapItemPacket.Content[0].StackNetworkID {
continue
}
}
if sra, ok := sra.(*protocol.PlaceInContainerStackRequestAction); ok {
if sra.Source.StackNetworkID == MapItemPacket.Content[0].StackNetworkID {
continue
}
}
if sra, ok := sra.(*protocol.TakeOutContainerStackRequestAction); ok {
if sra.Source.StackNetworkID == MapItemPacket.Content[0].StackNetworkID {
continue
}
}
if sra, ok := sra.(*protocol.DestroyStackRequestAction); ok {
if sra.Source.StackNetworkID == MapItemPacket.Content[0].StackNetworkID {
continue
}
}
}
requests = append(requests, isr)
}
pk.Requests = requests
case *packet.MobEquipment:
if pk.NewItem.Stack.NBTData["map_uuid"] == int64(ViewMapID) {
*forward = false
}
}
return pk
}
// stackToItem converts a network ItemStack representation back to an item.Stack.
func stackToItem(it protocol.ItemStack) item.Stack {
t, ok := world.ItemByRuntimeID(it.NetworkID, int16(it.MetadataValue))
if !ok {
t = block.Air{}
}
if it.BlockRuntimeID > 0 {
// It shouldn't matter if it (for whatever reason) wasn't able to get the block runtime ID,
// since on the next line, we assert that the block is an item. If it didn't succeed, it'll
// return air anyway.
b, _ := world.BlockByRuntimeID(uint32(it.BlockRuntimeID))
if t, ok = b.(world.Item); !ok {
t = block.Air{}
}
}
//noinspection SpellCheckingInspection
if nbter, ok := t.(world.NBTer); ok && len(it.NBTData) != 0 {
t = nbter.DecodeNBT(it.NBTData).(world.Item)
}
s := item.NewStack(t, int(it.Count))
return nbtconv.ReadItem(it.NBTData, &s)
}

View File

@ -1,129 +1,26 @@
package world package world
import ( import (
"bytes"
"context" "context"
"encoding/json"
"flag" "flag"
"fmt"
"image"
"image/png"
"math/rand"
"os"
"path"
"strconv"
"strings"
"time"
"github.com/bedrock-tool/bedrocktool/handlers/worlds"
"github.com/bedrock-tool/bedrocktool/locale" "github.com/bedrock-tool/bedrocktool/locale"
"github.com/bedrock-tool/bedrocktool/ui/messages" "github.com/bedrock-tool/bedrocktool/ui/messages"
"github.com/bedrock-tool/bedrocktool/utils" "github.com/bedrock-tool/bedrocktool/utils"
"github.com/bedrock-tool/bedrocktool/utils/behaviourpack"
"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/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 WorldState struct {
ctx context.Context
proxy *utils.ProxyContext
mapUI *MapUI
gui utils.UI
bp *behaviourpack.BehaviourPack
// save state
ChunkRadius int
chunks map[protocol.ChunkPos]*chunk.Chunk
blockNBT map[protocol.SubChunkPos][]map[string]any
openItemContainers map[byte]*itemContainer
entities map[uint64]*entityState
Dim world.Dimension
PlayerPos TPlayerPos
worldCounter int
WorldName string
ServerName string
ispre118 bool
// settings
voidGen bool
withPacks bool
saveImage bool
experimentInventory bool
}
func NewWorldState(ctx context.Context, proxy *utils.ProxyContext, ServerName string, ui utils.UI) *WorldState {
w := &WorldState{
ctx: ctx,
proxy: proxy,
mapUI: nil,
gui: ui,
bp: behaviourpack.New(ServerName),
ServerName: ServerName,
chunks: make(map[protocol.ChunkPos]*chunk.Chunk),
blockNBT: make(map[protocol.SubChunkPos][]map[string]any),
openItemContainers: make(map[byte]*itemContainer),
entities: make(map[uint64]*entityState),
Dim: nil,
WorldName: "world",
PlayerPos: TPlayerPos{},
}
w.mapUI = NewMapUI(w)
w.gui.Message(messages.Init, messages.InitPayload{
Handler: w.uiMessage,
})
return w
}
var dimensionIDMap = 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 (
black16x16 = image.NewRGBA(image.Rect(0, 0, 16, 16))
offsetTable [24]protocol.SubChunkOffset
) )
func init() { func init() {
for i := range offsetTable {
offsetTable[i] = protocol.SubChunkOffset{0, int8(i), 0}
}
for i := 3; i < len(black16x16.Pix); i += 4 {
black16x16.Pix[i] = 255
}
utils.RegisterCommand(&WorldCMD{}) utils.RegisterCommand(&WorldCMD{})
} }
type WorldCMD struct { type WorldCMD struct {
ServerAddress string ServerAddress string
Packs bool Packs bool
EnableVoid bool EnableVoid bool
SaveImage bool SaveEntities bool
ExperimentInventory bool SaveInventories bool
SaveImage bool
} }
func (*WorldCMD) Name() string { return "worlds" } func (*WorldCMD) Name() string { return "worlds" }
@ -134,7 +31,8 @@ func (c *WorldCMD) SetFlags(f *flag.FlagSet) {
f.BoolVar(&c.Packs, "packs", false, locale.Loc("save_packs_with_world", 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.EnableVoid, "void", true, locale.Loc("enable_void", nil))
f.BoolVar(&c.SaveImage, "image", false, locale.Loc("save_image", nil)) f.BoolVar(&c.SaveImage, "image", false, locale.Loc("save_image", nil))
f.BoolVar(&c.ExperimentInventory, "inv", false, locale.Loc("test_block_inv", nil)) f.BoolVar(&c.SaveEntities, "save-entities", true, "Save Entities")
f.BoolVar(&c.SaveInventories, "save-inventories", true, "Save Inventories")
} }
func (c *WorldCMD) Execute(ctx context.Context, ui utils.UI) error { func (c *WorldCMD) Execute(ctx context.Context, ui utils.UI) error {
@ -148,467 +46,20 @@ func (c *WorldCMD) Execute(ctx context.Context, ui utils.UI) error {
return err return err
} }
w := NewWorldState(ctx, proxy, hostname, ui)
w.voidGen = c.EnableVoid
w.withPacks = c.Packs
w.saveImage = c.SaveImage
w.experimentInventory = c.ExperimentInventory
proxy.AlwaysGetPacks = true proxy.AlwaysGetPacks = true
proxy.ConnectCB = w.OnConnect proxy.AddHandler(worlds.NewWorldsHandler(ctx, ui, worlds.WorldSettings{
proxy.OnClientConnect = func(hasClient bool) { VoidGen: c.EnableVoid,
w.gui.Message(messages.SetUIState, messages.UIStateConnecting) WithPacks: c.Packs,
} SaveEntities: c.SaveEntities,
proxy.PacketCB = func(pk packet.Packet, toServer bool, _ time.Time) (packet.Packet, error) { SaveInventories: c.SaveInventories,
forward := true SaveImage: c.SaveImage,
}))
if toServer { ui.Message(messages.SetUIState(messages.UIStateConnect))
// from client err = proxy.Run(ctx, serverAddress, hostname)
pk = w.processItemPacketsClient(pk, &forward)
pk = w.processMapPacketsClient(pk, &forward)
} else {
// from server
switch pk := pk.(type) {
case *packet.ChunkRadiusUpdated:
w.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
}
w.gui.Message(messages.SetUIState, messages.UIStateConnect)
err = w.proxy.Run(ctx, serverAddress)
if err != nil { if err != nil {
return err return err
} }
w.SaveAndReset() ui.Message(messages.SetUIState(messages.UIStateFinished))
return nil return nil
} }
func (w *WorldState) uiMessage(name string, data interface{}) messages.MessageResponse {
r := messages.MessageResponse{
Ok: false,
Data: nil,
}
switch name {
case messages.SetVoidGen:
set_void_gen := data.(messages.SetVoidGenPayload)
r.Ok = w.setVoidGen(set_void_gen.Value, true)
case messages.SetWorldName:
set_world_name := data.(messages.SetWorldNamePayload)
r.Ok = w.setWorldName(set_world_name.WorldName, true)
}
return r
}
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.mapUI.SchedRedraw()
}
}
func (w *WorldState) setVoidGen(val bool, fromUI bool) bool {
w.voidGen = val
var s string
if w.voidGen {
s = locale.Loc("void_generator_true", nil)
} else {
s = locale.Loc("void_generator_false", nil)
}
w.proxy.SendMessage(s)
if !fromUI {
w.gui.Message(messages.SetVoidGen, messages.SetVoidGenPayload{
Value: w.voidGen,
})
}
return true
}
func (w *WorldState) setWorldName(val string, fromUI bool) bool {
w.WorldName = val
w.proxy.SendMessage(locale.Loc("worldname_set", locale.Strmap{"Name": w.WorldName}))
if !fromUI {
w.gui.Message(messages.SetWorldName, messages.SetWorldNamePayload{
WorldName: w.WorldName,
})
}
return true
}
func (w *WorldState) Reset() {
w.chunks = make(map[protocol.ChunkPos]*chunk.Chunk)
w.WorldName = fmt.Sprintf("world-%d", w.worldCounter)
w.mapUI.Reset()
}
// SaveAndReset writes the world to a folder, resets all the chunks
func (w *WorldState) SaveAndReset() {
// cull empty chunks
keys := make([]protocol.ChunkPos, 0, len(w.chunks))
for cp := range w.chunks {
keys = append(keys, cp)
}
for _, cp := range keys {
has_any := false
for _, sc := range w.chunks[cp].Sub() {
has_any = !sc.Empty()
if has_any {
break
}
}
if !has_any {
delete(w.chunks, cp)
}
}
if len(w.chunks) == 0 {
w.Reset()
return
}
logrus.Infof(locale.Loc("saving_world", locale.Strmap{"Name": w.WorldName, "Count": len(w.chunks)}))
// open world
folder := path.Join("worlds", fmt.Sprintf("%s/%s", w.ServerName, w.WorldName))
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[world.ChunkPos][]map[string]any)
for scp, v := range w.blockNBT { // 3d to 2d
cp := world.ChunkPos{scp.X(), scp.Z()}
blockNBT[cp] = append(blockNBT[cp], v...)
}
for cp, v := range blockNBT {
err = provider.SaveBlockNBT(cp, v, w.Dim)
if err != nil {
logrus.Error(err)
}
}
// save entities
chunkEntities := make(map[world.ChunkPos][]world.Entity)
for _, es := range w.entities {
cp := world.ChunkPos{int32(es.Position.X()) >> 4, int32(es.Position.Z()) >> 4}
chunkEntities[cp] = append(chunkEntities[cp], es.ToServerEntity())
}
for cp, v := range chunkEntities {
err = provider.SaveEntities(cp, v, w.Dim)
if err != nil {
logrus.Error(err)
}
}
// 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(locale.Loc("unknown_gamerule", locale.Strmap{"Name": 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
}
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)
if err = provider.Close(); err != nil {
logrus.Error(err)
}
w.worldCounter += 1
type dep struct {
PackID string `json:"pack_id"`
Version [3]int `json:"version"`
}
addPacksJSON := func(name string, deps []dep) {
f, err := os.Create(path.Join(folder, name))
if err != nil {
logrus.Error(err)
return
}
defer f.Close()
if err := json.NewEncoder(f).Encode(deps); err != nil {
logrus.Error(err)
return
}
}
// save behaviourpack
if w.bp.HasContent() {
name := strings.ReplaceAll(w.ServerName, "./", "")
name = strings.ReplaceAll(name, "/", "-")
packFolder := path.Join(folder, "behavior_packs", name)
os.MkdirAll(packFolder, 0o755)
for _, p := range w.proxy.Server.ResourcePacks() {
p := utils.PackFromBase(p)
w.bp.CheckAddLink(p)
}
w.bp.Save(packFolder)
addPacksJSON("world_behavior_packs.json", []dep{{
PackID: w.bp.Manifest.Header.UUID,
Version: w.bp.Manifest.Header.Version,
}})
// force resource packs for worlds with custom blocks
w.withPacks = true
}
// add resource packs
if w.withPacks {
packs, err := utils.GetPacks(w.proxy.Server)
if err != nil {
logrus.Error(err)
} else {
var rdeps []dep
for k, p := range packs {
if p.Encrypted() && !p.CanDecrypt() {
logrus.Warnf("Cant add %s, it is encrypted", p.Name())
continue
}
logrus.Infof(locale.Loc("adding_pack", locale.Strmap{"Name": k}))
packFolder := path.Join(folder, "resource_packs", p.Name())
os.MkdirAll(packFolder, 0o755)
data := make([]byte, p.Len())
p.ReadAt(data, 0)
utils.UnpackZip(bytes.NewReader(data), int64(len(data)), packFolder)
rdeps = append(rdeps, dep{
PackID: p.Manifest().Header.UUID,
Version: p.Manifest().Header.Version,
})
}
if len(rdeps) > 0 {
//addPacksJSON("world_resource_packs.json", rdeps)
}
}
}
if w.saveImage {
f, _ := os.Create(folder + ".png")
png.Encode(f, w.mapUI.ToImage())
f.Close()
}
time.Sleep(1 * time.Second)
// zip it
filename := folder + ".mcworld"
if err := utils.ZipFolder(filename, folder); err != nil {
fmt.Println(err)
}
logrus.Info(locale.Loc("saved", locale.Strmap{"Name": filename}))
//os.RemoveAll(folder)
w.Reset()
}
func (w *WorldState) OnConnect(err error) bool {
w.gui.Message(messages.SetUIState, messages.UIStateMain)
if err != nil {
return false
}
gd := w.proxy.Server.GameData()
w.ChunkRadius = int(gd.ChunkRadius)
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.ispre118 = ver < 18
}
if err != nil || len(gv) <= 1 {
logrus.Info(locale.Loc("guessing_version", nil))
}
dimensionID := gd.Dimension
if w.ispre118 {
logrus.Info(locale.Loc("using_under_118", nil))
dimensionID += 10
}
w.Dim = dimensionIDMap[uint8(dimensionID)]
}
w.proxy.SendMessage(locale.Loc("use_setname", nil))
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.voidGen, false)
},
Cmd: protocol.Command{
Name: "void",
Description: locale.Loc("void_desc", nil),
},
})
w.mapUI.Start()
w.proxy.ClientWritePacket(&packet.ChunkRadiusUpdated{
ChunkRadius: 80,
})
return true
}

View File

@ -114,8 +114,8 @@ func (g *GUI) run(w *app.Window) error {
} }
} }
func (g *GUI) Message(name string, data interface{}) messages.MessageResponse { func (g *GUI) Message(data interface{}) messages.MessageResponse {
r := g.router.Handler(name, data) r := g.router.Handler(data)
if r.Ok || r.Data != nil { if r.Ok || r.Data != nil {
return r return r
} }
@ -125,8 +125,8 @@ func (g *GUI) Message(name string, data interface{}) messages.MessageResponse {
Data: nil, Data: nil,
} }
switch name { switch data.(type) {
case "can_show_images": case messages.CanShowImages:
r.Ok = true r.Ok = true
} }

View File

@ -13,7 +13,7 @@ import (
"github.com/bedrock-tool/bedrocktool/ui/messages" "github.com/bedrock-tool/bedrocktool/ui/messages"
) )
type HandlerFunc = func(name string, data interface{}) messages.MessageResponse type HandlerFunc = func(data interface{}) messages.MessageResponse
type Page interface { type Page interface {
Actions() []component.AppBarAction Actions() []component.AppBarAction
@ -133,10 +133,10 @@ func (r *Router) Layout(gtx layout.Context, th *material.Theme) layout.Dimension
return layout.Dimensions{Size: gtx.Constraints.Max} return layout.Dimensions{Size: gtx.Constraints.Max}
} }
func (r *Router) Handler(name string, data interface{}) messages.MessageResponse { func (r *Router) Handler(data interface{}) messages.MessageResponse {
page, ok := r.pages[r.current] page, ok := r.pages[r.current]
if ok { if ok {
return page.Handler()(name, data) return page.Handler()(data)
} }
return messages.MessageResponse{} return messages.MessageResponse{}
} }

View File

@ -102,7 +102,6 @@ func (p *Page) Layout(gtx C, th *material.Theme) D {
go func() { go func() {
defer p.Router.Wg.Done() defer p.Router.Wg.Done()
utils.InitDNS() utils.InitDNS()
utils.InitExtraDebug(p.Router.Ctx)
err := cmd.Execute(p.Router.Ctx, utils.CurrentUI) err := cmd.Execute(p.Router.Ctx, utils.CurrentUI)
if err != nil { if err != nil {
@ -176,7 +175,7 @@ func (p *Page) Layout(gtx C, th *material.Theme) D {
} }
func (p *Page) Handler() pages.HandlerFunc { func (p *Page) Handler() pages.HandlerFunc {
return func(name string, data interface{}) messages.MessageResponse { return func(data interface{}) messages.MessageResponse {
return messages.MessageResponse{ return messages.MessageResponse{
Ok: false, Ok: false,
Data: nil, Data: nil,

View File

@ -24,7 +24,7 @@ type Page struct {
State messages.UIState State messages.UIState
SkinsList widget.List SkinsList widget.List
l sync.Mutex l sync.Mutex
Skins []messages.NewSkinPayload Skins []messages.NewSkin
} }
func New(router *pages.Router) *Page { func New(router *pages.Router) *Page {
@ -96,31 +96,23 @@ func (p *Page) Layout(gtx C, th *material.Theme) D {
return layout.Flex{}.Layout(gtx) return layout.Flex{}.Layout(gtx)
} }
func (p *Page) handler(name string, data interface{}) messages.MessageResponse { func (p *Page) handler(data interface{}) messages.MessageResponse {
r := messages.MessageResponse{ r := messages.MessageResponse{
Ok: false, Ok: false,
Data: nil, Data: nil,
} }
switch name { switch m := data.(type) {
case messages.SetUIState: case messages.SetUIState:
state := data.(messages.UIState) p.State = m
p.State = state
p.Router.Invalidate() p.Router.Invalidate()
r.Ok = true r.Ok = true
case messages.Init:
init := data.(messages.InitPayload)
_ = init
r.Ok = true
case messages.NewSkin: case messages.NewSkin:
p.l.Lock() p.l.Lock()
new_skin := data.(messages.NewSkinPayload) p.Skins = append(p.Skins, m)
p.Skins = append(p.Skins, new_skin)
r.Ok = true
p.l.Unlock() p.l.Unlock()
p.Router.Invalidate() p.Router.Invalidate()
r.Ok = true
} }
return r return r
} }

View File

@ -23,6 +23,7 @@ type Map struct {
center f32.Point center f32.Point
transform f32.Affine2D transform f32.Affine2D
grabbed bool grabbed bool
cursor image.Point
MapImage *image.RGBA MapImage *image.RGBA
BoundsMin protocol.ChunkPos BoundsMin protocol.ChunkPos
@ -40,7 +41,7 @@ func (m *Map) HandlePointerEvent(e pointer.Event) {
case pointer.Release: case pointer.Release:
m.grabbed = false m.grabbed = false
case pointer.Scroll: case pointer.Scroll:
scaleFactor := float32(math.Pow(1.01, float64(e.Scroll.Y))) 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.transform = m.transform.Scale(e.Position.Sub(m.center), f32.Pt(scaleFactor, scaleFactor))
m.scaleFactor *= scaleFactor m.scaleFactor *= scaleFactor
} }
@ -66,6 +67,13 @@ func (m *Map) Layout(gtx layout.Context) layout.Dimensions {
op.Affine(m.transform.Offset(m.center.Sub(size.Div(2)))).Add(gtx.Ops) op.Affine(m.transform.Offset(m.center.Sub(size.Div(2)))).Add(gtx.Ops)
m.imageOp.Add(gtx.Ops) m.imageOp.Add(gtx.Ops)
paint.PaintOp{}.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 size := gtx.Constraints.Max
@ -90,7 +98,7 @@ func drawTile(img *image.RGBA, min, pos protocol.ChunkPos, tile *image.RGBA) {
), tile, image.Point{}, draw.Src) ), tile, image.Point{}, draw.Src)
} }
func (m *Map) Update(u *messages.UpdateMapPayload) { func (m *Map) Update(u *messages.UpdateMap) {
if m.MapImage == nil { if m.MapImage == nil {
m.scaleFactor = 1 m.scaleFactor = 1
} }

View File

@ -1,8 +1,13 @@
package worlds package worlds
import ( import (
"fmt"
"image"
"sync"
"gioui.org/layout" "gioui.org/layout"
"gioui.org/unit" "gioui.org/unit"
"gioui.org/widget"
"gioui.org/widget/material" "gioui.org/widget/material"
"gioui.org/x/component" "gioui.org/x/component"
"github.com/bedrock-tool/bedrocktool/ui/gui" "github.com/bedrock-tool/bedrocktool/ui/gui"
@ -23,12 +28,21 @@ type Page struct {
chunkCount int chunkCount int
voidGen bool voidGen bool
worldName string worldName string
worldsList widget.List
worlds []messages.SavingWorld
l sync.Mutex
} }
func New(router *pages.Router) *Page { func New(router *pages.Router) *Page {
return &Page{ return &Page{
Router: router, Router: router,
worldMap: &Map{}, worldMap: &Map{},
worldsList: widget.List{
List: layout.List{
Axis: layout.Vertical,
},
},
} }
} }
@ -69,53 +83,68 @@ func (p *Page) Layout(gtx C, th *material.Theme) D {
return layout.Flex{ return layout.Flex{
Axis: layout.Vertical, Axis: layout.Vertical,
}.Layout(gtx, }.Layout(gtx,
layout.Rigid(material.Label(th, 20, "World Downloader Basic UI").Layout),
layout.Flexed(1, func(gtx C) D { layout.Flexed(1, func(gtx C) D {
return layout.Center.Layout(gtx, p.worldMap.Layout) 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(20).
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 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.Name).Layout),
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
return layout.Dimensions{Size: image.Pt(20, 20)}
}),
layout.Rigid(material.Label(th, th.TextSize, fmt.Sprintf("%d chunks", entry.Chunks)).Layout),
)
})
})
}),
)
} }
return layout.Flex{}.Layout(gtx) return layout.Dimensions{}
} }
func (u *Page) handler(name string, data interface{}) messages.MessageResponse { func (u *Page) handler(data interface{}) messages.MessageResponse {
r := messages.MessageResponse{ r := messages.MessageResponse{
Ok: false, Ok: false,
Data: nil, Data: nil,
} }
switch name { switch m := data.(type) {
case messages.SetUIState: case messages.SetUIState:
state := data.(messages.UIState) u.State = m
u.State = state
u.Router.Invalidate() u.Router.Invalidate()
r.Ok = true r.Ok = true
case messages.Init:
init := data.(messages.InitPayload)
_ = init
r.Ok = true
case messages.UpdateMap: case messages.UpdateMap:
update_map := data.(messages.UpdateMapPayload) u.chunkCount = m.ChunkCount
u.chunkCount = update_map.ChunkCount u.worldMap.Update(&m)
u.worldMap.Update(&update_map)
u.Router.Invalidate() u.Router.Invalidate()
r.Ok = true r.Ok = true
case messages.SetVoidGen: case messages.SetVoidGen:
set_void_gen := data.(messages.SetVoidGenPayload) u.voidGen = m.Value
u.voidGen = set_void_gen.Value
u.Router.Invalidate() u.Router.Invalidate()
r.Ok = true r.Ok = true
case messages.SetWorldName: case messages.SetWorldName:
set_world_name := data.(messages.SetWorldNamePayload) u.worldName = m.WorldName
u.worldName = set_world_name.WorldName u.Router.Invalidate()
r.Ok = true
case messages.SavingWorld:
u.l.Lock()
u.worlds = append(u.worlds, m)
u.l.Unlock()
u.Router.Invalidate() u.Router.Invalidate()
r.Ok = true r.Ok = true
} }
return r return r
} }

View File

@ -28,6 +28,8 @@ func (s *worldSettings) Apply() {
s.worlds.EnableVoid = s.voidGen.Value s.worlds.EnableVoid = s.voidGen.Value
s.worlds.SaveImage = s.saveImage.Value s.worlds.SaveImage = s.saveImage.Value
s.worlds.ServerAddress = s.serverAddress.Text() 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 { func (s *worldSettings) Layout(gtx layout.Context, th *material.Theme) layout.Dimensions {

View File

@ -8,4 +8,4 @@ import (
type C = layout.Context type C = layout.Context
type D = layout.Dimensions type D = layout.Dimensions
type HandlerFunc = func(name string, data interface{}) messages.MessageResponse type HandlerFunc = func(data interface{}) messages.MessageResponse

View File

@ -11,51 +11,42 @@ type MessageResponse struct {
Data interface{} Data interface{}
} }
type UIState = int type UIState int
const ( const (
UIStateConnect = iota UIStateConnect = iota
UIStateConnecting UIStateConnecting
UIStateMain UIStateMain
UIStateFinished
) )
type HandlerFunc = func(name string, data interface{}) MessageResponse type HandlerFunc = func(name string, data interface{}) MessageResponse
// //
const SetUIState = "set_ui_state" type SetUIState = UIState
type SetUIStatePayload = UIState
// //
const SetVoidGen = "set_void_gen" type SetVoidGen struct {
type SetVoidGenPayload struct {
Value bool Value bool
} }
// //
const SetWorldName = "set_world_name" type SetWorldName struct {
type SetWorldNamePayload struct {
WorldName string WorldName string
} }
// //
var Init = "init" type Init struct {
type InitPayload struct {
Handler HandlerFunc Handler HandlerFunc
} }
// //
var UpdateMap = "update_map" type UpdateMap struct {
type UpdateMapPayload struct {
ChunkCount int ChunkCount int
Rotation float32 Rotation float32
UpdatedTiles []protocol.ChunkPos UpdatedTiles []protocol.ChunkPos
@ -66,9 +57,14 @@ type UpdateMapPayload struct {
// //
var NewSkin = "new_skin" type NewSkin struct {
type NewSkinPayload struct {
PlayerName string PlayerName string
Skin *protocol.Skin Skin *protocol.Skin
} }
type SavingWorld struct {
Name string
Chunks int
}
type CanShowImages struct{}

View File

@ -8,6 +8,7 @@ import (
"strings" "strings"
"github.com/bedrock-tool/bedrocktool/utils" "github.com/bedrock-tool/bedrocktool/utils"
"github.com/repeale/fp-go"
"github.com/sandertv/gophertunnel/minecraft/resource" "github.com/sandertv/gophertunnel/minecraft/resource"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
@ -62,12 +63,34 @@ func (bp *BehaviourPack) CheckAddLink(pack utils.Pack) {
logrus.Error(err) logrus.Error(err)
return return
} }
if len(bp.blocks) > 0 {
hasBlocksJson := false
if bp.HasBlocks() {
_, err = z.Open("blocks.json") _, err = z.Open("blocks.json")
if err != nil { if err == nil {
return 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 h := pack.Manifest().Header
bp.AddDependency(h.UUID, h.Version) bp.AddDependency(h.UUID, h.Version)
} }
@ -80,6 +103,10 @@ func (bp *BehaviourPack) HasItems() bool {
return len(bp.items) > 0 return len(bp.items) > 0
} }
func (bp *BehaviourPack) HasEntities() bool {
return len(bp.entities) > 0
}
func (bp *BehaviourPack) HasContent() bool { func (bp *BehaviourPack) HasContent() bool {
return bp.HasBlocks() || bp.HasItems() return bp.HasBlocks() || bp.HasItems()
} }
@ -107,7 +134,7 @@ func (bp *BehaviourPack) Save(fpath string) error {
return e.Encode(thing) return e.Encode(thing)
} }
if len(bp.blocks) > 0 { // blocks if bp.HasBlocks() { // blocks
blocks_dir := path.Join(fpath, "blocks") blocks_dir := path.Join(fpath, "blocks")
os.Mkdir(blocks_dir, 0o755) os.Mkdir(blocks_dir, 0o755)
for _, be := range bp.blocks { for _, be := range bp.blocks {
@ -117,7 +144,7 @@ func (bp *BehaviourPack) Save(fpath string) error {
} }
} }
} }
if len(bp.items) > 0 { // items if bp.HasItems() { // items
items_dir := path.Join(fpath, "items") items_dir := path.Join(fpath, "items")
os.Mkdir(items_dir, 0o755) os.Mkdir(items_dir, 0o755)
for _, ib := range bp.items { for _, ib := range bp.items {
@ -127,7 +154,7 @@ func (bp *BehaviourPack) Save(fpath string) error {
} }
} }
} }
if len(bp.entities) > 0 { // items if bp.HasEntities() { // entities
items_dir := path.Join(fpath, "entities") items_dir := path.Join(fpath, "entities")
os.Mkdir(items_dir, 0o755) os.Mkdir(items_dir, 0o755)
for _, eb := range bp.entities { for _, eb := range bp.entities {

View File

@ -35,24 +35,24 @@ func (bp *BehaviourPack) AddEntity(entity EntityIn) {
return return
} }
if _, ok := bp.entities[entity.Identifier]; ok { entry, ok := bp.entities[entity.Identifier]
return 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,
},
}
} }
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 { for _, av := range entity.Attr {
switch av.Name { switch av.Name {
case "minecraft:health": case "minecraft:health":
@ -72,6 +72,13 @@ func (bp *BehaviourPack) AddEntity(entity EntityIn) {
"value": scale, "value": scale,
} }
} }
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 bp.entities[entity.Identifier] = entry
} }

View File

@ -1,11 +1,9 @@
package world package utils
import ( import (
"image" "image"
"image/color" "image/color"
"github.com/bedrock-tool/bedrocktool/utils"
"github.com/df-mc/dragonfly/server/block" "github.com/df-mc/dragonfly/server/block"
"github.com/df-mc/dragonfly/server/block/cube" "github.com/df-mc/dragonfly/server/block/cube"
"github.com/df-mc/dragonfly/server/world" "github.com/df-mc/dragonfly/server/world"
@ -14,53 +12,55 @@ import (
func isBlockLightblocking(b world.Block) bool { func isBlockLightblocking(b world.Block) bool {
d, isDiffuser := b.(block.LightDiffuser) d, isDiffuser := b.(block.LightDiffuser)
_, isSlab := b.(block.Slab)
noDiffuse := isDiffuser && d.LightDiffusionLevel() == 0 noDiffuse := isDiffuser && d.LightDiffusionLevel() == 0
return noDiffuse && !isSlab return !noDiffuse
} }
func blockColorAt(c *chunk.Chunk, x uint8, y int16, z uint8) (blockColor color.RGBA) { func blockColorAt(c *chunk.Chunk, x uint8, y int16, z uint8) (blockColor color.RGBA) {
if y <= int16(c.Range().Min()) { if y <= int16(c.Range().Min()) {
return color.RGBA{0, 0, 0, 0} return color.RGBA{0, 0, 0, 0}
} }
blockColor = color.RGBA{255, 0, 255, 255}
rid := c.Block(x, y, z, 0) rid := c.Block(x, y, z, 0)
if rid == 0 && y == int16(c.Range().Min()) { // void
blockColor = color.RGBA{0, 0, 0, 255}
} else {
b, found := world.BlockByRuntimeID(rid)
if found {
if isBlockLightblocking(b) {
return blockColorAt(c, x, y-1, z)
}
_, isWater := b.(block.Water)
if !isWater {
return b.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 blockColor = color.RGBA{255, 0, 255, 255}
waterColor := (&block.Water{}).Color() b, found := world.BlockByRuntimeID(rid)
waterColor.A = uint8(utils.Clamp(int(150+depth*7), 255)) if !found {
blockColor = utils.BlendColors(blockColor, waterColor) return blockColor
blockColor.R -= uint8(depth * 2) }
blockColor.G -= uint8(depth * 2)
blockColor.B -= uint8(depth * 2) 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)
}
/* /*
if blockColor.R == 0 || blockColor.R == 255 && blockColor.B == 255 { a := color.RGBA{255, 0, 255, 255}
if col == a {
name, nbt := b.EncodeBlock() name, nbt := b.EncodeBlock()
fmt.Printf("unknown color %d %s %s %s\n", rid, reflect.TypeOf(b), name, nbt) fmt.Printf("unknown color %d %s %s %s\n", rid, reflect.TypeOf(b), name, nbt)
b.Color()
} }
*/ */
return col
} }
return blockColor
} }
func chunkGetColorAt(c *chunk.Chunk, x uint8, y int16, z uint8) color.RGBA { func chunkGetColorAt(c *chunk.Chunk, x uint8, y int16, z uint8) color.RGBA {
@ -83,7 +83,7 @@ func chunkGetColorAt(c *chunk.Chunk, x uint8, y int16, z uint8) color.RGBA {
}, cube.Range{int(y + 1), int(y + 1)}) }, cube.Range{int(y + 1), int(y + 1)})
blockColor := blockColorAt(c, x, y, z) blockColor := blockColorAt(c, x, y, z)
if haveUp { if haveUp && (x+z)%2 == 0 {
if blockColor.R > 10 { if blockColor.R > 10 {
blockColor.R -= 10 blockColor.R -= 10
} }
@ -97,7 +97,7 @@ func chunkGetColorAt(c *chunk.Chunk, x uint8, y int16, z uint8) color.RGBA {
return blockColor return blockColor
} }
func Chunk2Img(c *chunk.Chunk) *image.RGBA { func Chunk2Img(c *chunk.Chunk, warn bool) *image.RGBA {
img := image.NewRGBA(image.Rect(0, 0, 16, 16)) img := image.NewRGBA(image.Rect(0, 0, 16, 16))
hm := c.HeightMapWithWater() hm := c.HeightMapWithWater()

View File

@ -17,7 +17,7 @@ import (
type UI interface { type UI interface {
Init() bool Init() bool
Start(context.Context, context.CancelFunc) error Start(context.Context, context.CancelFunc) error
Message(name string, data interface{}) messages.MessageResponse Message(data interface{}) messages.MessageResponse
ServerInput(context.Context, string) (string, string, error) ServerInput(context.Context, string) (string, string, error)
} }
@ -25,7 +25,7 @@ type BaseUI struct {
UI UI
} }
func (u *BaseUI) Message(name string, data interface{}) messages.MessageResponse { func (u *BaseUI) Message(data interface{}) messages.MessageResponse {
return messages.MessageResponse{ return messages.MessageResponse{
Ok: false, Ok: false,
Data: nil, Data: nil,
@ -74,7 +74,6 @@ func (c *InteractiveCLI) Start(ctx context.Context, cancel context.CancelFunc) e
flag.Parse() flag.Parse()
InitDNS() InitDNS()
InitExtraDebug(ctx)
subcommands.Execute(ctx) subcommands.Execute(ctx)

View File

@ -8,11 +8,11 @@ import (
func InvFromNBT(inv *inventory.Inventory, items []any) { func InvFromNBT(inv *inventory.Inventory, items []any) {
for _, itemData := range items { for _, itemData := range items {
data, _ := itemData.(map[string]any) data, _ := itemData.(map[string]any)
it := ReadItem(data, nil) it := Item(data, nil)
if it.Empty() { if it.Empty() {
continue continue
} }
_ = inv.SetItem(int(Map[byte](data, "Slot")), it) _ = inv.SetItem(int(Uint8(data, "Slot")), it)
} }
} }

View File

@ -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{}
}

View File

@ -3,78 +3,233 @@ package nbtconv
import ( import (
"bytes" "bytes"
"encoding/gob" "encoding/gob"
"github.com/df-mc/dragonfly/server/block/cube"
"github.com/df-mc/dragonfly/server/item" "github.com/df-mc/dragonfly/server/item"
"github.com/df-mc/dragonfly/server/world" "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. // Bool reads a uint8 value from a map at key k and returns true if it equals 1.
func ReadItem(data map[string]any, s *item.Stack) item.Stack { func Bool(m map[string]any, k string) bool {
disk := s == nil 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 { 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 s = &a
} }
readDamage(data, s, disk)
readAnvilCost(data, s) readAnvilCost(tag, s)
readDisplay(data, s) readDamage(tag, s, disk)
readEnchantments(data, s) readDisplay(tag, s)
readDragonflyData(data, s) readDragonflyData(tag, s)
readEnchantments(tag, s)
return *s return *s
} }
// ReadBlock decodes the data of a block into a world.Block. // Block decodes the data of a block into a world.Block.
func ReadBlock(m map[string]any) world.Block { func Block(m map[string]any, k string) world.Block {
name, _ := m["name"].(string) if mk, ok := m[k].(map[string]any); ok {
properties, _ := m["states"].(map[string]any) name, _ := mk["name"].(string)
b, _ := world.BlockByName(name, properties) properties, _ := mk["states"].(map[string]any)
return b b, _ := world.BlockByName(name, properties)
return b
}
return nil
} }
// readItemStack reads an item.Stack from the NBT in the map passed. // 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 var it world.Item
if blockItem, ok := MapBlock(m, "Block").(world.Item); ok { if blockItem, ok := Block(m, "Block").(world.Item); ok {
it = blockItem 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 it = v
} }
if it == nil { if it == nil {
return item.Stack{} return item.Stack{}
} }
if n, ok := it.(world.NBTer); ok { 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. // 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) { func readDamage(m map[string]any, s *item.Stack, disk bool) {
if disk { if disk {
*s = s.Damage(int(Map[int16](m, "Damage"))) *s = s.Damage(int(Int16(m, "Damage")))
return return
} }
*s = s.Damage(int(Map[int32](m, "Damage"))) *s = s.Damage(int(Int32(m, "Damage")))
} }
// readAnvilCost ... // readAnvilCost ...
func readAnvilCost(m map[string]any, s *item.Stack) { 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. // 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) { func readEnchantments(m map[string]any, s *item.Stack) {
enchantments, ok := m["ench"].([]map[string]any) enchantments, ok := m["ench"].([]map[string]any)
if !ok { if !ok {
for _, e := range Map[[]any](m, "ench") { for _, e := range Slice(m, "ench") {
if v, ok := e.(map[string]any); ok { if v, ok := e.(map[string]any); ok {
enchantments = append(enchantments, v) enchantments = append(enchantments, v)
} }
} }
} }
for _, ench := range enchantments { for _, ench := range enchantments {
if t, ok := item.EnchantmentByID(int(Map[int16](ench, "id"))); ok { if t, ok := item.EnchantmentByID(int(Int16(ench, "id"))); ok {
*s = s.WithEnchantments(item.NewEnchantment(t, int(Map[int16](ench, "lvl")))) *s = s.WithEnchantments(item.NewEnchantment(t, int(Int16(ench, "lvl"))))
} }
} }
} }

View File

@ -11,21 +11,27 @@ import (
// WriteItem encodes an item stack into a map that can be encoded using NBT. // WriteItem encodes an item stack into a map that can be encoded using NBT.
func WriteItem(s item.Stack, disk bool) map[string]any { 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 { if nbt, ok := s.Item().(world.NBTer); ok {
for k, v := range nbt.EncodeNBT() { 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 { if disk {
writeItemStack(m, s) writeItemStack(data, tag, s)
} else {
for k, v := range tag {
data[k] = v
}
} }
writeDamage(m, s, disk) return data
writeAnvilCost(m, s)
writeDisplay(m, s)
writeEnchantments(m, s)
writeDragonflyData(m, s)
return m
} }
// WriteBlock encodes a world.Block into a map that can be encoded using NBT. // 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. // 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() m["Name"], m["Damage"] = s.Item().EncodeItem()
if b, ok := s.Item().(world.Block); ok { if b, ok := s.Item().(world.Block); ok {
v := map[string]any{} v := map[string]any{}
@ -47,6 +53,9 @@ func writeItemStack(m map[string]any, s item.Stack) {
m["Block"] = v m["Block"] = v
} }
m["Count"] = byte(s.Count()) 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. // writeBlock writes the name, properties and version of a block to a map ready for NBT encoding.

View File

@ -1,8 +1,12 @@
package utils 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"), IP: net.ParseIP("10.0.0.0"),
Mask: net.CIDRMask(8, 32), Mask: net.CIDRMask(8, 32),
@ -19,12 +23,9 @@ var PrivateIPNetworks = []net.IPNet{
// IPPrivate checks if ip is private // IPPrivate checks if ip is private
func IPPrivate(ip net.IP) bool { func IPPrivate(ip net.IP) bool {
for _, ipNet := range PrivateIPNetworks { return fp.Some(func(ipNet net.IPNet) bool {
if ipNet.Contains(ip) { return ipNet.Contains(ip)
return true })(privateIPNetworks)
}
}
return false
} }
// GetLocalIP returns the non loopback local IP of the host // GetLocalIP returns the non loopback local IP of the host

View File

@ -1,224 +0,0 @@
package utils
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net"
"reflect"
"strings"
"sync"
"github.com/bedrock-tool/bedrocktool/locale"
"github.com/fatih/color"
"github.com/sandertv/gophertunnel/minecraft/protocol"
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
"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
FLog io.Writer
dmpLock sync.Mutex
)
func dmpStruct(level int, inputStruct any, withType bool, isInList bool) (s string) {
tBase := strings.Repeat("\t", level)
if inputStruct == nil {
return "nil"
}
ii := reflect.Indirect(reflect.ValueOf(inputStruct))
typeName := reflect.TypeOf(inputStruct).String()
typeString := ""
if withType {
if slices.Contains([]string{"bool", "string"}, typeName) {
} else {
typeString = typeName + " "
}
}
if strings.HasPrefix(typeName, "protocol.Optional") {
v := ii.MethodByName("Value").Call(nil)
val, set := v[0], v[1]
if !set.Bool() {
s += typeName + " Not Set"
} else {
s += typeName + "{\n" + tBase + "\t"
s += dmpStruct(level+1, val.Interface(), false, false)
s += "\n" + tBase + "}"
}
return
}
if ii.Kind() == reflect.Struct {
if ii.NumField() == 0 {
s += typeName + "{}"
} else {
s += typeName + "{\n"
for i := 0; i < ii.NumField(); i++ {
fieldType := ii.Type().Field(i)
if fieldType.IsExported() {
field := ii.Field(i).Interface()
d := dmpStruct(level+1, field, true, false)
s += tBase + fmt.Sprintf("\t%s = %s\n", fieldType.Name, d)
} else {
s += tBase + " " + fieldType.Name + " (unexported)"
}
}
s += tBase + "}"
}
} else if ii.Kind() == reflect.Slice {
var t reflect.Type
is_elem_struct := false
if ii.Len() > 0 {
e := ii.Index(0)
t = reflect.TypeOf(e.Interface())
is_elem_struct = t.Kind() == reflect.Struct
}
if ii.Len() > 1000 {
s += "[<slice too long>]"
} else if ii.Len() == 0 {
s += typeString + "[]"
} else {
s += typeString + "["
if is_elem_struct {
s += "\n"
}
for i := 0; i < ii.Len(); i++ {
if is_elem_struct {
s += tBase + "\t"
}
s += dmpStruct(level+1, ii.Index(i).Interface(), false, true)
if is_elem_struct {
s += "\n"
} else {
s += " "
}
}
if is_elem_struct {
s += tBase
}
s += "]"
}
} else if ii.Kind() == reflect.Map {
j, err := json.MarshalIndent(ii.Interface(), tBase, "\t")
if err != nil {
s += err.Error()
}
s += string(j)
} else {
is_array := ii.Kind() == reflect.Array
if !isInList && !is_array {
s += typeString
}
s += fmt.Sprintf("%#v", ii.Interface())
}
return s
}
func DumpStruct(data interface{}) {
if FLog == nil {
return
}
FLog.Write([]byte(dmpStruct(0, data, true, false)))
FLog.Write([]byte("\n\n\n"))
}
var ClientAddr net.Addr
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}
}
if pk.ID() == packet.IDRequestNetworkSettings {
ClientAddr = src
}
defer func() {
if recoveredErr := recover(); recoveredErr != nil {
logrus.Errorf("%T: %s", pk, recoveredErr.(error))
}
}()
pk.Marshal(protocol.NewReader(bytes.NewBuffer(payload), 0))
if FLog != nil {
dmpLock.Lock()
defer dmpLock.Unlock()
FLog.Write([]byte(dmpStruct(0, pk, true, false)))
FLog.Write([]byte("\n\n\n"))
}
pkName := reflect.TypeOf(pk).String()[1:]
if slices.Contains(MutedPackets, pkName) {
return
}
switch pk := pk.(type) {
case *packet.Disconnect:
logrus.Infof(locale.Loc("disconnect", locale.Strmap{"Pk": pk}))
}
dirS2C := color.GreenString("S") + "->" + color.CyanString("C")
dirC2S := color.CyanString("C") + "->" + color.GreenString("S")
var dir string = dirS2C
if ClientAddr != nil {
if src == ClientAddr {
dir = dirC2S
}
} else {
srcAddr, _, _ := net.SplitHostPort(src.String())
if IPPrivate(net.ParseIP(srcAddr)) {
dir = dirS2C
}
}
logrus.Debugf("%s 0x%02x, %s", dir, pk.ID(), pkName)
if slices.Contains(ExtraVerbose, pkName) {
logrus.Debugf("%+v", pk)
}
}

View File

@ -8,11 +8,13 @@ import (
"fmt" "fmt"
"net" "net"
"os" "os"
"reflect"
"strings" "strings"
"sync" "sync"
"time" "time"
"github.com/bedrock-tool/bedrocktool/locale" "github.com/bedrock-tool/bedrocktool/locale"
"github.com/repeale/fp-go"
"github.com/sandertv/gophertunnel/minecraft" "github.com/sandertv/gophertunnel/minecraft"
"github.com/sandertv/gophertunnel/minecraft/protocol" "github.com/sandertv/gophertunnel/minecraft/protocol"
"github.com/sandertv/gophertunnel/minecraft/protocol/login" "github.com/sandertv/gophertunnel/minecraft/protocol/login"
@ -23,6 +25,7 @@ import (
var DisconnectReason = "Connection lost" var DisconnectReason = "Connection lost"
/*
type dummyProto struct { type dummyProto struct {
id int32 id int32
ver string ver string
@ -38,36 +41,55 @@ func (p dummyProto) ConvertToLatest(pk packet.Packet, _ *minecraft.Conn) []packe
func (p dummyProto) ConvertFromLatest(pk packet.Packet, _ *minecraft.Conn) []packet.Packet { func (p dummyProto) ConvertFromLatest(pk packet.Packet, _ *minecraft.Conn) []packet.Packet {
return []packet.Packet{pk} return []packet.Packet{pk}
} }
*/
type ( type (
PacketFunc func(header packet.Header, payload []byte, src, dst net.Addr) PacketFunc func(header packet.Header, payload []byte, src, dst net.Addr)
PacketCallback func(pk packet.Packet, toServer bool, timeReceived time.Time) (packet.Packet, error) IngameCommand struct {
ClientConnectCallback func(hasClient bool)
ConnectCallback func(err error) bool
IngameCommand struct {
Exec func(cmdline []string) bool Exec func(cmdline []string) bool
Cmd protocol.Command Cmd protocol.Command
} }
) )
type ProxyHandler struct {
Name string
ProxyRef func(*ProxyContext)
//
AddressAndName func(address, hostname string) error
// called to change game data
GameDataModifier func(*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.Conn)
SecondaryClientCB func(conn *minecraft.Conn)
// called after game started
ConnectCB func(err error) bool
// called when the proxy stops
OnEnd func()
}
type ProxyContext struct { type ProxyContext struct {
Server *minecraft.Conn Server *minecraft.Conn
Client *minecraft.Conn Client *minecraft.Conn
Listener *minecraft.Listener clientAddr net.Addr
commands map[string]IngameCommand Listener *minecraft.Listener
AlwaysGetPacks bool AlwaysGetPacks bool
WithClient bool WithClient bool
IgnoreDisconnect bool IgnoreDisconnect bool
CustomClientData *login.ClientData CustomClientData *login.ClientData
// called for every packet commands map[string]IngameCommand
PacketFunc PacketFunc handlers []*ProxyHandler
// called after client connected
OnClientConnect ClientConnectCallback
// called after game started
ConnectCB ConnectCallback
// called on every packet after login
PacketCB PacketCallback
} }
func NewProxy() (*ProxyContext, error) { func NewProxy() (*ProxyContext, error) {
@ -182,6 +204,10 @@ func (p *ProxyContext) SendPopup(text string) {
}) })
} }
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) { func (p *ProxyContext) CommandHandlerPacketCB(pk packet.Packet, toServer bool, _ time.Time) (packet.Packet, error) {
switch _pk := pk.(type) { switch _pk := pk.(type) {
case *packet.CommandRequest: case *packet.CommandRequest:
@ -205,7 +231,7 @@ func (p *ProxyContext) CommandHandlerPacketCB(pk packet.Packet, toServer bool, _
return pk, nil return pk, nil
} }
func (p *ProxyContext) proxyLoop(ctx context.Context, toServer bool, packetCBs []PacketCallback) error { func (p *ProxyContext) proxyLoop(ctx context.Context, toServer bool) error {
var c1, c2 *minecraft.Conn var c1, c2 *minecraft.Conn
if toServer { if toServer {
c1 = p.Client c1 = p.Client
@ -225,10 +251,17 @@ func (p *ProxyContext) proxyLoop(ctx context.Context, toServer bool, packetCBs [
return err return err
} }
for _, packetCB := range packetCBs { pkName := reflect.TypeOf(pk).String()
pk, err = packetCB(pk, toServer, time.Now()) for _, handler := range p.handlers {
if err != nil { if handler.PacketCB != nil {
return err pk, err = handler.PacketCB(pk, toServer, time.Now())
if err != nil {
return err
}
if pk == nil {
logrus.Tracef("Dropped Packet: %s", pkName)
break
}
} }
} }
@ -243,7 +276,26 @@ func (p *ProxyContext) proxyLoop(ctx context.Context, toServer bool, packetCBs [
} }
} }
func (p *ProxyContext) Run(ctx context.Context, serverAddress string) (err error) { func (p *ProxyContext) IsClient(addr net.Addr) bool {
return p.clientAddr.String() == addr.String()
}
var NewDebugLogger func(bool) *ProxyHandler
func (p *ProxyContext) Run(ctx context.Context, serverAddress, name string) (err error) {
if Options.Debug || Options.ExtraDebug {
p.AddHandler(NewDebugLogger(Options.ExtraDebug))
}
for _, handler := range p.handlers {
if handler.AddressAndName != nil {
handler.AddressAndName(serverAddress, name)
}
if handler.ProxyRef != nil {
handler.ProxyRef(p)
}
}
if strings.HasPrefix(serverAddress, "PCAP!") { if strings.HasPrefix(serverAddress, "PCAP!") {
return createReplayConnection(ctx, serverAddress[5:], p) return createReplayConnection(ctx, serverAddress[5:], p)
} }
@ -255,11 +307,9 @@ func (p *ProxyContext) Run(ctx context.Context, serverAddress string) (err error
var packs []*resource.Pack var packs []*resource.Pack
if Options.Preload { if Options.Preload {
logrus.Info(locale.Loc("preloading_packs", nil)) logrus.Info(locale.Loc("preloading_packs", nil))
var serverConn *minecraft.Conn serverConn, err := connectServer(ctx, serverAddress, nil, true, func(header packet.Header, payload []byte, src, dst net.Addr) {})
serverConn, err = connectServer(ctx, serverAddress, nil, true, nil)
if err != nil { if err != nil {
err = fmt.Errorf(locale.Loc("failed_to_connect", locale.Strmap{"Address": serverAddress, "Err": err})) return fmt.Errorf(locale.Loc("failed_to_connect", locale.Strmap{"Address": serverAddress, "Err": err}))
return
} }
serverConn.Close() serverConn.Close()
packs = serverConn.ResourcePacks() packs = serverConn.ResourcePacks()
@ -277,9 +327,7 @@ func (p *ProxyContext) Run(ctx context.Context, serverAddress string) (err error
if err != nil { if err != nil {
return err return err
} }
defer func() {
go func() {
<-ctx.Done()
if p.Client != nil { if p.Client != nil {
p.Listener.Disconnect(p.Client, DisconnectReason) p.Listener.Disconnect(p.Client, DisconnectReason)
} }
@ -298,51 +346,81 @@ func (p *ProxyContext) Run(ctx context.Context, serverAddress string) (err error
cdp = &cd cdp = &cd
} }
if p.OnClientConnect != nil { for _, handler := range p.handlers {
p.OnClientConnect(p.WithClient) if handler.OnClientConnect == nil {
continue
}
handler.OnClientConnect(p.Client)
} }
if p.CustomClientData != nil { if p.CustomClientData != nil {
cdp = p.CustomClientData cdp = p.CustomClientData
} }
p.Server, err = connectServer(ctx, serverAddress, cdp, p.AlwaysGetPacks, p.PacketFunc) p.Server, err = connectServer(ctx, serverAddress, cdp, p.AlwaysGetPacks, 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 err != nil { if err != nil {
if p.ConnectCB != nil { for _, handler := range p.handlers {
if p.ConnectCB(err) { if handler.ConnectCB == nil {
continue
}
ignore := handler.ConnectCB(err)
if ignore {
err = nil err = nil
break
} }
} }
err = fmt.Errorf(locale.Loc("failed_to_connect", locale.Strmap{"Address": serverAddress, "Err": err}))
if err != nil {
err = fmt.Errorf(locale.Loc("failed_to_connect", locale.Strmap{"Address": serverAddress, "Err": err}))
}
return err return err
} }
defer p.Server.Close() defer p.Server.Close()
gd := p.Server.GameData()
for _, handler := range p.handlers {
if handler.GameDataModifier != nil {
handler.GameDataModifier(&gd)
}
}
// spawn and start the game // spawn and start the game
if err = spawnConn(ctx, p.Client, p.Server); err != nil { if err = spawnConn(ctx, p.Client, p.Server, gd); err != nil {
err = fmt.Errorf(locale.Loc("failed_to_spawn", locale.Strmap{"Err": err})) err = fmt.Errorf(locale.Loc("failed_to_spawn", locale.Strmap{"Err": err}))
return err return err
} }
if p.ConnectCB != nil { for _, handler := range p.handlers {
if !p.ConnectCB(nil) { if handler.ConnectCB == nil {
continue
}
if !handler.ConnectCB(nil) {
return errors.New("Cancelled") return errors.New("Cancelled")
} }
} }
// append self to handlers for commands
p.handlers = append(p.handlers, &ProxyHandler{
Name: "Commands",
PacketCB: p.CommandHandlerPacketCB,
})
wg := sync.WaitGroup{} wg := sync.WaitGroup{}
var cbs []PacketCallback
cbs = append(cbs, p.CommandHandlerPacketCB)
if p.PacketCB != nil {
cbs = append(cbs, p.PacketCB)
}
// server to client // server to client
wg.Add(1) wg.Add(1)
go func() { go func() {
defer wg.Done() defer wg.Done()
if err := p.proxyLoop(ctx, false, cbs); err != nil { if err := p.proxyLoop(ctx, false); err != nil {
logrus.Error(err) logrus.Error(err)
return return
} }
@ -353,13 +431,39 @@ func (p *ProxyContext) Run(ctx context.Context, serverAddress string) (err error
wg.Add(1) wg.Add(1)
go func() { go func() {
defer wg.Done() defer wg.Done()
if err := p.proxyLoop(ctx, true, cbs); err != nil { if err := p.proxyLoop(ctx, true); err != nil {
logrus.Error(err) logrus.Error(err)
return return
} }
}() }()
} }
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() wg.Wait()
for _, handler := range p.handlers {
if handler.OnEnd != nil {
handler.OnEnd()
}
}
return err return err
} }

View File

@ -63,6 +63,13 @@ func createReplayConnection(ctx context.Context, filename string, proxy *ProxyCo
f.Seek(-4, io.SeekCurrent) f.Seek(-4, io.SeekCurrent)
} }
server, client := &net.UDPAddr{
IP: net.IPv4(1, 1, 1, 1),
}, &net.UDPAddr{
IP: net.IPv4(2, 2, 2, 2),
}
proxy.clientAddr = client
proxy.Server = minecraft.NewConn() proxy.Server = minecraft.NewConn()
gameStarted := false gameStarted := false
@ -77,7 +84,7 @@ func createReplayConnection(ctx context.Context, filename string, proxy *ProxyCo
offset, _ := f.Seek(0, io.SeekCurrent) offset, _ := f.Seek(0, io.SeekCurrent)
if offset == totalSize { if offset == totalSize {
logrus.Info("Reached End") logrus.Info("Reached End")
return nil break
} }
binary.Read(f, binary.LittleEndian, &magic) binary.Read(f, binary.LittleEndian, &magic)
@ -96,7 +103,7 @@ func createReplayConnection(ctx context.Context, filename string, proxy *ProxyCo
n, err := f.Read(payload) n, err := f.Read(payload)
if err != nil { if err != nil {
logrus.Error(err) logrus.Error(err)
return nil break
} }
if n != int(packetLength) { if n != int(packetLength) {
return fmt.Errorf("truncated %d", i) return fmt.Errorf("truncated %d", i)
@ -123,13 +130,28 @@ func createReplayConnection(ctx context.Context, filename string, proxy *ProxyCo
b := protocol.NewWriter(f, 0) b := protocol.NewWriter(f, 0)
pk.Marshal(b) pk.Marshal(b)
if Options.Debug { hdr := packet.Header{PacketID: pk.ID()}
PacketLogger(packet.Header{PacketID: pk.ID()}, f.Bytes(), &net.UDPAddr{}, &net.UDPAddr{})
var src, dst net.Addr
if toServer {
src = client
dst = server
} else {
src = server
dst = client
}
for _, handler := range proxy.handlers {
if handler.PacketFunc != nil {
handler.PacketFunc(hdr, f.Bytes(), src, dst)
}
} }
if gameStarted { if gameStarted {
if proxy.PacketCB != nil { for _, handler := range proxy.handlers {
proxy.PacketCB(pk, toServer, timeReceived) if handler.PacketCB != nil {
handler.PacketCB(pk, toServer, timeReceived)
}
} }
} else { } else {
switch pk := pk.(type) { switch pk := pk.(type) {
@ -164,11 +186,19 @@ func createReplayConnection(ctx context.Context, filename string, proxy *ProxyCo
DisablePlayerInteractions: pk.DisablePlayerInteractions, DisablePlayerInteractions: pk.DisablePlayerInteractions,
}) })
gameStarted = true gameStarted = true
if proxy.ConnectCB != nil { for _, handler := range proxy.handlers {
proxy.ConnectCB(nil) if handler.ConnectCB != nil {
handler.ConnectCB(nil)
}
} }
} }
} }
} }
} }
for _, handler := range proxy.handlers {
if handler.OnEnd != nil {
handler.OnEnd()
}
}
return nil
} }

View File

@ -1,7 +1,6 @@
package skins package utils
import ( import (
"crypto/sha256"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
@ -23,17 +22,13 @@ type SkinGeometry struct {
Textureheight int `json:"textureheight"` Textureheight int `json:"textureheight"`
VisibleBoundsWidth float64 `json:"visible_bounds_width"` VisibleBoundsWidth float64 `json:"visible_bounds_width"`
VisibleBoundsHeight float64 `json:"visible_bounds_height"` VisibleBoundsHeight float64 `json:"visible_bounds_height"`
VisibleBoundsOffset []float64 `json:"visible_bounds_offset"` VisibleBoundsOffset []float64 `json:"visible_bounds_offset,omitempty"`
Bones []any `json:"bones"` Bones []any `json:"bones"`
} }
func (skin *Skin) Hash() uuid.UUID { func (skin *Skin) Hash() uuid.UUID {
h := sha256.New() h := append(skin.CapeData, append(skin.SkinData, skin.SkinGeometry...)...)
h.Write(skin.SkinData) return uuid.NewSHA1(uuid.NameSpaceURL, h)
h.Write(skin.SkinGeometry)
h.Write(skin.CapeData)
h.Write([]byte(skin.SkinID))
return uuid.NewSHA1(uuid.NameSpaceURL, h.Sum(nil))
} }
func (skin *Skin) getGeometry() (*SkinGeometry, string, error) { func (skin *Skin) getGeometry() (*SkinGeometry, string, error) {

View File

@ -1,4 +1,4 @@
package skins package utils
import ( import (
"encoding/json" "encoding/json"
@ -6,18 +6,33 @@ import (
"os" "os"
"path" "path"
"github.com/bedrock-tool/bedrocktool/utils"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/sandertv/gophertunnel/minecraft/protocol"
"github.com/sandertv/gophertunnel/minecraft/resource" "github.com/sandertv/gophertunnel/minecraft/resource"
"github.com/sirupsen/logrus" "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 { type _skinWithIndex struct {
i int i int
skin *Skin skin *Skin
} }
func (s _skinWithIndex) Name(name string) string { func (s _skinWithIndex) Name(name string) string {
if s.i == 1 {
return name
}
return fmt.Sprintf("%s-%d", name, s.i) return fmt.Sprintf("%s-%d", name, s.i)
} }
@ -141,7 +156,7 @@ func (s *SkinPack) Save(fpath, serverName string) error {
}, },
} }
if err := utils.WriteManifest(&manifest, fpath); err != nil { if err := WriteManifest(&manifest, fpath); err != nil {
return err return err
} }
} }

View File

@ -8,6 +8,8 @@ import (
"runtime" "runtime"
"github.com/sanbornm/go-selfupdate/selfupdate" "github.com/sanbornm/go-selfupdate/selfupdate"
"github.com/shirou/gopsutil/v3/cpu"
"github.com/shirou/gopsutil/v3/mem"
) )
var Version string var Version string
@ -26,7 +28,13 @@ func (httpRequester *trequester) Fetch(url string) (io.ReadCloser, error) {
} }
// set user agent to know what versions are run // set user agent to know what versions are run
h, _ := os.Hostname() h, _ := os.Hostname()
req.Header.Add("User-Agent", fmt.Sprintf("%s %s '%s' %d", CmdName, Version, h, runtime.NumCPU())) 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) resp, err := http.DefaultClient.Do(req)
if err != nil { if err != nil {

View File

@ -8,8 +8,6 @@ import (
"crypto/sha256" "crypto/sha256"
"encoding/json" "encoding/json"
"errors" "errors"
"io"
"net"
"os" "os"
"path" "path"
"regexp" "regexp"
@ -17,7 +15,6 @@ import (
"sync" "sync"
"github.com/bedrock-tool/bedrocktool/locale" "github.com/bedrock-tool/bedrocktool/locale"
"github.com/bedrock-tool/bedrocktool/utils/crypt"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/sandertv/gophertunnel/minecraft" "github.com/sandertv/gophertunnel/minecraft"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
@ -25,7 +22,6 @@ import (
//"github.com/sandertv/gophertunnel/minecraft/gatherings" //"github.com/sandertv/gophertunnel/minecraft/gatherings"
"github.com/sandertv/gophertunnel/minecraft/protocol/login" "github.com/sandertv/gophertunnel/minecraft/protocol/login"
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
"github.com/sandertv/gophertunnel/minecraft/resource" "github.com/sandertv/gophertunnel/minecraft/resource"
) )
@ -67,14 +63,7 @@ func connectServer(ctx context.Context, address string, ClientData *login.Client
serverConn, err = minecraft.Dialer{ serverConn, err = minecraft.Dialer{
TokenSource: GetTokenSource(), TokenSource: GetTokenSource(),
ClientData: cd, ClientData: cd,
PacketFunc: func(header packet.Header, payload []byte, src, dst net.Addr) { PacketFunc: packetFunc,
if Options.Debug {
PacketLogger(header, payload, src, dst)
}
if packetFunc != nil {
packetFunc(header, payload, src, dst)
}
},
DownloadResourcePack: func(id uuid.UUID, version string, current int, total int) bool { DownloadResourcePack: func(id uuid.UUID, version string, current int, total int) bool {
return wantPacks return wantPacks
}, },
@ -84,18 +73,17 @@ func connectServer(ctx context.Context, address string, ClientData *login.Client
} }
logrus.Debug(locale.Loc("connected", nil)) logrus.Debug(locale.Loc("connected", nil))
ClientAddr = serverConn.LocalAddr()
return serverConn, nil return serverConn, nil
} }
func spawnConn(ctx context.Context, clientConn *minecraft.Conn, serverConn *minecraft.Conn) error { func spawnConn(ctx context.Context, clientConn *minecraft.Conn, serverConn *minecraft.Conn, gd minecraft.GameData) error {
wg := sync.WaitGroup{} wg := sync.WaitGroup{}
errs := make(chan error, 2) errs := make(chan error, 2)
if clientConn != nil { if clientConn != nil {
wg.Add(1) wg.Add(1)
go func() { go func() {
defer wg.Done() defer wg.Done()
errs <- clientConn.StartGame(serverConn.GameData()) errs <- clientConn.StartGame(gd)
}() }()
} }
wg.Add(1) wg.Add(1)
@ -119,30 +107,6 @@ func spawnConn(ctx context.Context, clientConn *minecraft.Conn, serverConn *mine
return nil return nil
} }
// get longest line length
func maxLen(lines []string) int {
o := 0
for _, line := range lines {
if o < len(line) {
o = len(line)
}
}
return o
}
// MarginLines makes text centered
func MarginLines(lines []string) string {
ret := ""
max := maxLen(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 // SplitExt splits path to filename and extension
func SplitExt(filename string) (name, ext string) { func SplitExt(filename string) (name, ext string) {
name, ext = path.Base(filename), path.Ext(filename) name, ext = path.Base(filename), path.Ext(filename)
@ -194,49 +158,3 @@ func CfbDecrypt(data []byte, key []byte) []byte {
} }
return data return data
} }
func InitExtraDebug(ctx context.Context) {
if !Options.ExtraDebug {
return
}
Options.Debug = true
var logPlain, logCryptEnc io.WriteCloser = nil, nil
// open plain text log
logPlain, err := os.Create("packets.log")
if err != nil {
logrus.Error(err)
} else {
go func() {
<-ctx.Done()
logPlain.Close()
}()
}
// open gpg log
logCrypt, err := os.Create("packets.log.gpg")
if err != nil {
logrus.Error(err)
} else {
go func() {
<-ctx.Done()
logCrypt.Close()
}()
// encrypter for the log
logCryptEnc, err = crypt.Encer("packets.log", logCrypt)
if err != nil {
logrus.Error(err)
} else {
go func() {
<-ctx.Done()
logCryptEnc.Close()
}()
}
}
FLog = io.MultiWriter(logPlain, logCryptEnc)
if err != nil {
logrus.Error(err)
}
}

View File

@ -2,6 +2,7 @@ package utils
import ( import (
"archive/zip" "archive/zip"
"compress/flate"
"io" "io"
"io/fs" "io/fs"
"os" "os"
@ -36,6 +37,12 @@ func ZipFolder(filename, folder string) error {
logrus.Fatal(err) logrus.Fatal(err)
} }
zw := zip.NewWriter(f) 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 { err = filepath.WalkDir(folder, func(path string, d fs.DirEntry, err error) error {
if !d.Type().IsDir() { if !d.Type().IsDir() {
rel := path[len(folder)+1:] rel := path[len(folder)+1:]