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
customdata.json
/other-projects/
__pycache__/entityflags.cpython-310.pyc
gathering.py
*.ipynb
entityflags.py

View File

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

View File

@ -9,6 +9,7 @@ import (
"os/signal"
"runtime/debug"
"syscall"
"time"
"github.com/bedrock-tool/bedrocktool/locale"
"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 {
flag.Parse()
utils.InitDNS()
utils.InitExtraDebug(ctx)
subcommands.Execute(ctx)
time.Sleep(50 * time.Millisecond)
cancel()
time.Sleep(50 * time.Millisecond)
return nil
}
func main() {
/*
cf, _ := os.Create("cpu.pprof")
err := pprof.StartCPUProfile(cf)
if err != nil {
logrus.Error(err)
}
defer pprof.StopCPUProfile()
*/
defer func() {
if err := recover(); err != nil {
logrus.Errorf(locale.Loc("fatal_error", nil))
@ -69,14 +81,16 @@ func main() {
logrus.Infof(locale.Loc("bedrocktool_version", locale.Strmap{"Version": utils.Version}))
}
newVersion, err := utils.Updater.UpdateAvailable()
if err != nil {
logrus.Error(err)
}
go func() {
newVersion, err := utils.Updater.UpdateAvailable()
if err != nil {
logrus.Error(err)
}
if newVersion != "" && utils.Version != "" {
logrus.Infof(locale.Loc("update_available", locale.Strmap{"Version": newVersion}))
}
if newVersion != "" && utils.Version != "" {
logrus.Infof(locale.Loc("update_available", locale.Strmap{"Version": newVersion}))
}
}()
ctx, cancel := context.WithCancel(context.Background())
@ -116,7 +130,7 @@ func main() {
logrus.Error("Failed to init UI!")
return
}
err = ui.Start(ctx, cancel)
err := ui.Start(ctx, cancel)
cancel()
if err != nil {
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/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
require (
gioui.org v0.0.0-20221219171716-c455f0f342ef
gioui.org v0.0.0-20230323230841-d7b1c7c33b33
gioui.org/x v0.0.0-20230313161557-05b40af72ed0
github.com/cloudfoundry-attic/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21
github.com/df-mc/dragonfly v0.9.3
github.com/df-mc/dragonfly v0.9.4
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/go-gl/mathgl v1.0.0
github.com/google/subcommands v1.2.0
github.com/google/uuid v1.3.0
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/repeale/fp-go v0.11.1
github.com/sanbornm/go-selfupdate v0.0.0-20210106163404-c9b625feac49
github.com/sandertv/go-raknet v1.12.0
github.com/sandertv/gophertunnel v1.28.1
github.com/shirou/gopsutil/v3 v3.23.2
github.com/sirupsen/logrus v1.9.0
golang.design/x/lockfree v0.0.1
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/text v0.8.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
github.com/benoitkugler/textlayout v0.3.0 // indirect
github.com/brentp/intintmap v0.0.0-20190211203843-30dc0ade9af9 // indirect
github.com/cespare/xxhash v1.1.0 // indirect
github.com/changkun/lockfree v0.0.1 // indirect
github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21 // indirect
github.com/df-mc/atomic v1.10.0 // indirect
github.com/df-mc/worldupgrader v1.0.3 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-text/typesetting v0.0.0-20221214153724-0399769901d5 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
github.com/klauspost/compress v1.15.15 // indirect
github.com/kr/binarydist v0.1.0 // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.17 // 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
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/net v0.8.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=
gioui.org v0.0.0-20221219171716-c455f0f342ef h1:0nxeswOamzSRIjFjRmASp+sWCAFzVt7UCC3HlRHIB6k=
gioui.org v0.0.0-20221219171716-c455f0f342ef/go.mod h1:3lLo7xMHYnnHTrgKNNctBjEKKH3wQCO2Sn7ti5Jy8mU=
gioui.org/cpu v0.0.0-20210808092351-bfe733dd3334/go.mod h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ=
gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2 h1:AGDDxsJE1RpcXTAxPG2B4jrwVUJGFDjINIPi1jtO6pc=
gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2/go.mod h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ=
@ -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/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
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/textlayout v0.3.0 h1:2ehWXEkgb6RUokTjXh1LzdGwG4dRP6X3dqhYYDYhUVk=
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/goleveldb v1.1.9 h1:ihdosZyy5jkQKrxucTQmN90jq/2lUwQnJZjIYIC/9YU=
github.com/df-mc/goleveldb v1.1.9/go.mod h1:+NHCup03Sci5q84APIA21z3iPZCuk6m6ABtg4nANCSk=
github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w=
github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg=
github.com/df-mc/worldupgrader v1.0.3 h1:3nbthy6vfSNQZdqHBR+E5Fh3mCeWmCwLtqrYDiPUG5I=
github.com/df-mc/worldupgrader v1.0.3/go.mod h1:6ybkJ/BV9b0XkcPzcLmvgT9Nv/xgBXdDQTmRhu7b8zQ=
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
github.com/flytam/filenamify v1.1.2 h1:dGlfWU4zrhDlsmvob4IFcfgjG5vIjfo4UwLyec6Wx94=
github.com/flytam/filenamify v1.1.2/go.mod h1:Dzf9kVycwcsBlr2ATg6uxjqiFgKGH+5SKFuhdeP5zu8=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/go-gl/mathgl v1.0.0 h1:t9DznWJlXxxjeeKLIdovCOVJQk/GzDEL7h/h+Ro2B68=
github.com/go-gl/mathgl v1.0.0/go.mod h1:yhpkQzEiH9yPyxDUGzkmgScbaBVlhC06qodikEM0ZwQ=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-text/typesetting v0.0.0-20221214153724-0399769901d5 h1:iOA0HmtpANn48hX2nlDNMu0VVaNza35HJG0WeetBVzQ=
github.com/go-text/typesetting v0.0.0-20221214153724-0399769901d5/go.mod h1:/cmOXaoTiO+lbCwkTZBgCvevJpbFsZ5reXIpEJVh5MI=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@ -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/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/subcommands v1.2.0 h1:vWQspBTo2nEqTUFita5/KeEWlUL8kQObDFbub/EN9oE=
github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
@ -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/kr/binarydist v0.1.0 h1:6kAoLA9FMMnNGSehX0s1PdjbEaACznAv/W219j2uvyo=
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/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.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/miekg/dns v1.1.51 h1:0+Xg7vObnhrz/4ZCZcZh7zPXlmU0aveS2HDBd0m0qSo=
github.com/miekg/dns v1.1.51/go.mod h1:2Z9d3CP1LQWihRZUf29mQ19yDThaI4DAYzte2CaQW5c=
github.com/miekg/dns v1.1.52 h1:Bmlc/qsNNULOe6bpXcUTsuOajd0DzRHwup6D9k1An0c=
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/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/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.3-5/go.mod h1:ODAzVcmM7KvKgPB89hoYndQiVVgTm9FlDXUko1H3YVs=
github.com/olebeck/dragonfly v0.9.3-6 h1:ots1osXxuP6fQg4C8tt3y8xfpc+JNqOarI5zufisKFs=
github.com/olebeck/dragonfly v0.9.3-6/go.mod h1:ODAzVcmM7KvKgPB89hoYndQiVVgTm9FlDXUko1H3YVs=
github.com/olebeck/dragonfly v0.9.3-7 h1:zIMWsox18efEe7R+VmJOTJRePU9x2YN3CE1uzfse1AM=
github.com/olebeck/dragonfly v0.9.3-7/go.mod h1:nnnmYWgSTNQb9x33nBthqN/2vyHlUaijfo+e2y3W5j4=
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/dragonfly v0.9.4-5 h1:ixVX0Fe3wX8FryJiKC5gFVS4KDrKtd1dXJoBkzWhd1c=
github.com/olebeck/dragonfly v0.9.4-5/go.mod h1:k8OQvjmfj+JbrwQf1qHfMETlFHOp0WJLjILN+QVqh+c=
github.com/olebeck/dragonfly v0.9.4-6 h1:Pom7oMbUA/kFu6PCwr3mWtTOSPvgzD2/71+mUsqK42o=
github.com/olebeck/dragonfly v0.9.4-6/go.mod h1:k8OQvjmfj+JbrwQf1qHfMETlFHOp0WJLjILN+QVqh+c=
github.com/olebeck/dragonfly v0.9.4-7 h1:xzSc9U9upx+mxayAHN1MxkD+PStVgqksJ4uls0o3g4w=
github.com/olebeck/dragonfly v0.9.4-7/go.mod h1:k8OQvjmfj+JbrwQf1qHfMETlFHOp0WJLjILN+QVqh+c=
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/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/go.mod h1:ekREo7U9TPHh86kbuPMaWA93NMyWsfVvP/iNT3XhAb8=
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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/repeale/fp-go v0.11.1 h1:Q/e+gNyyHaxKAyfdbBqvip3DxhVWH453R+kthvSr9Mk=
github.com/repeale/fp-go v0.11.1/go.mod h1:4KrwQJB1VRY+06CA+jTc4baZetr6o2PeuqnKr5ybQUc=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/sanbornm/go-selfupdate v0.0.0-20210106163404-c9b625feac49 h1:LuxslTBxJrrNeKfqoywIERWWhH43TgiVAiPEVlhgNBA=
github.com/sanbornm/go-selfupdate v0.0.0-20210106163404-c9b625feac49/go.mod h1:fY313ZGG810aWruFYcyq3coFpHDrWJVoMfSRI81y1r4=
github.com/sandertv/go-raknet v1.12.0 h1:olUzZlIJyX/pgj/mrsLCZYjKLNDsYiWdvQ4NIm3z0DA=
github.com/sandertv/go-raknet v1.12.0/go.mod h1:Gx+WgZBMQ0V2UoouGoJ8Wj6CDrMBQ4SB2F/ggpl5/+Y=
github.com/shirou/gopsutil/v3 v3.23.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/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM=
github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI=
github.com/tklauser/numcpus v0.6.0 h1:kebhY2Qt+3U6RNK7UqpYNA+tJ23IBEGKkB7JQBfDYms=
github.com/tklauser/numcpus v0.6.0/go.mod h1:FEZLMke0lhOUG6w2JadTzp0a+Nl8PF/GFkQ5UVIcaL4=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg=
github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
golang.design/x/lockfree v0.0.1 h1:IHFNwZgM5bnZYWkEbzn5lWHMYr8WsRBdCJ/RBVY0xMM=
@ -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.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/exp v0.0.0-20230304125523-9ff063c70017 h1:3Ea9SZLCB0aRIhSEjM+iaGIlzzeDJdpi579El/YIhEE=
golang.org/x/exp v0.0.0-20230304125523-9ff063c70017/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug=
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/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-20210504121937-7319ad40d33e/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
golang.org/x/image v0.6.0 h1:bR8b5okrPI3g/gyZakLZHeWxAR8Dn5CyxXv1hLH5g/4=
golang.org/x/image v0.6.0/go.mod h1:MXLdDR43H7cDJq5GEGXEVeeNhPgi+YYEQ2pC1byI1x0=
golang.org/x/image v0.5.0 h1:5JMiNunQeQw++mMOz48/ISeNu3Iweh/JaZU8ZLqHRrI=
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.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -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-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.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/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
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-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-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-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-20210615035016-665e8c7367d1/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/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@ -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-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.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k=
golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -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=
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 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/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
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 (
"image/png"
"os"
"runtime/pprof"
"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/world/chunk"
)
@ -13,7 +14,7 @@ import (
func Test(t *testing.T) {
data, _ := os.ReadFile("chunk.bin")
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")
png.Encode(f, i)
f.Close()
@ -21,19 +22,32 @@ func Test(t *testing.T) {
func Benchmark_chunk_decode(b *testing.B) {
data, _ := os.ReadFile("chunk.bin")
cf, _ := os.Create("cpu.pprof")
err := pprof.StartCPUProfile(cf)
if err != nil {
b.Error(err)
}
for i := 0; i < b.N; i++ {
_, _, err := chunk.NetworkDecode(33, data, 6, cube.Range{0, 255}, true, false)
if err != nil {
b.Error(err)
}
}
pprof.StopCPUProfile()
}
func Benchmark_render_chunk(b *testing.B) {
data, _ := os.ReadFile("chunk.bin")
ch, _, _ := chunk.NetworkDecode(33, data, 6, cube.Range{0, 255}, true, false)
for i := 0; i < b.N; i++ {
world.Chunk2Img(ch)
cf, _ := os.Create("cpu.pprof")
err := pprof.StartCPUProfile(cf)
if err != nil {
b.Error(err)
}
for i := 0; i < b.N; i++ {
utils.Chunk2Img(ch, true)
}
pprof.StopCPUProfile()
}

View File

@ -1,4 +1,4 @@
package world
package worlds
import (
"github.com/bedrock-tool/bedrocktool/utils/behaviourpack"
@ -61,16 +61,17 @@ func (e serverEntity) Type() world.EntityType {
return e.EntityType
}
func (w *WorldState) processAddActor(pk *packet.AddActor) {
e, ok := w.entities[pk.EntityRuntimeID]
func (w *worldsHandler) processAddActor(pk *packet.AddActor) {
e, ok := w.worldState.entities[pk.EntityRuntimeID]
if !ok {
e = &entityState{
RuntimeID: pk.EntityRuntimeID,
UniqueID: pk.EntityUniqueID,
EntityType: pk.EntityType,
Inventory: make(map[byte]map[byte]protocol.ItemInstance),
Metadata: make(map[uint32]any),
}
w.entities[pk.EntityRuntimeID] = e
w.worldState.entities[pk.EntityRuntimeID] = e
w.bp.AddEntity(behaviourpack.EntityIn{
Identifier: pk.EntityType,
@ -125,6 +126,9 @@ func entityMetadataToNBT(metadata protocol.EntityMetadata, nbt map[string]any) {
if color2, ok := metadata[protocol.EntityDataKeyColorTwoIndex]; ok {
nbt["Color2"] = color2
}
if skinID, ok := metadata[protocol.EntityDataKeySkinID]; ok {
nbt["SkinID"] = int32(skinID.(int32))
}
if name, ok := metadata[protocol.EntityDataKeyName]; ok {
nbt["CustomName"] = name
@ -137,47 +141,49 @@ func entityMetadataToNBT(metadata protocol.EntityMetadata, nbt map[string]any) {
}
}
if metadata.Flag(protocol.EntityDataKeyFlags, protocol.EntityDataFlagNoAI) {
nbt["IsAutonomous"] = false
}
for k, v := range flagNames {
nbt[v] = metadata.Flag(protocol.EntityDataKeyFlags, k)
}
if _, ok := metadata[protocol.EntityDataKeyFlags]; ok {
if metadata.Flag(protocol.EntityDataKeyFlags, protocol.EntityDataFlagNoAI) {
nbt["IsAutonomous"] = false
}
for k, v := range flagNames {
nbt[v] = metadata.Flag(protocol.EntityDataKeyFlags, k)
}
AlwaysShowName := metadata.Flag(protocol.EntityDataKeyFlags, protocol.EntityDataFlagAlwaysShowName)
if AlwaysShowName {
nbt["CustomNameVisible"] = true
}
AlwaysShowName := metadata.Flag(protocol.EntityDataKeyFlags, protocol.EntityDataFlagAlwaysShowName)
if AlwaysShowName {
nbt["CustomNameVisible"] = true
}
type effect struct {
Id byte
Amplifier byte
Duration int32
DurationEasy int32
DurationNormal int32
DurationHard int32
Ambient bool
ShowParticles bool
DisplayOnScreenTextureAnimation bool
}
type effect struct {
Id byte
Amplifier byte
Duration int32
DurationEasy int32
DurationNormal int32
DurationHard int32
Ambient bool
ShowParticles bool
DisplayOnScreenTextureAnimation bool
}
activeEffects := []effect{}
addEffect := func(id int, showParticles bool) {
activeEffects = append(activeEffects, effect{
Id: byte(id),
Amplifier: 1,
Duration: 10000000,
ShowParticles: false,
})
}
activeEffects := []effect{}
addEffect := func(id int, showParticles bool) {
activeEffects = append(activeEffects, effect{
Id: byte(id),
Amplifier: 1,
Duration: 10000000,
ShowParticles: false,
})
}
invisible := metadata.Flag(protocol.EntityDataKeyFlags, protocol.EntityDataFlagInvisible)
if invisible {
addEffect(packet.EffectInvisibility, false)
}
invisible := metadata.Flag(protocol.EntityDataKeyFlags, protocol.EntityDataFlagInvisible)
if invisible {
addEffect(packet.EffectInvisibility, false)
}
if len(activeEffects) > 0 {
nbt["ActiveEffects"] = activeEffects
if len(activeEffects) > 0 {
nbt["ActiveEffects"] = activeEffects
}
}
}
@ -200,36 +206,42 @@ func (s *entityState) ToServerEntity() serverEntity {
entityMetadataToNBT(s.Metadata, e.EntityType.NBT)
if s.Helmet != nil || s.Chestplate != nil || s.Leggings != nil || s.Boots != nil {
armor := make([]map[string]any, 0, 4)
armor = append(armor, nbtconv.WriteItem(stackToItem(s.Helmet.Stack), true))
armor = append(armor, nbtconv.WriteItem(stackToItem(s.Chestplate.Stack), true))
armor = append(armor, nbtconv.WriteItem(stackToItem(s.Leggings.Stack), true))
armor = append(armor, nbtconv.WriteItem(stackToItem(s.Boots.Stack), true))
e.EntityType.NBT["Armor"] = armor
e.EntityType.NBT["Armor"] = []map[string]any{
nbtconv.WriteItem(stackToItem(s.Helmet.Stack), true),
nbtconv.WriteItem(stackToItem(s.Chestplate.Stack), true),
nbtconv.WriteItem(stackToItem(s.Leggings.Stack), true),
nbtconv.WriteItem(stackToItem(s.Boots.Stack), true),
}
}
return e
}
func (w *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) {
case *packet.AddActor:
w.processAddActor(pk)
case *packet.RemoveActor:
case *packet.SetActorData:
e, ok := w.entities[pk.EntityRuntimeID]
e, ok := w.worldState.entities[pk.EntityRuntimeID]
if ok {
for k, v := range pk.EntityMetadata {
e.Metadata[k] = v
}
e.Metadata = pk.EntityMetadata
w.bp.AddEntity(behaviourpack.EntityIn{
Identifier: e.EntityType,
Attr: nil,
Meta: pk.EntityMetadata,
})
}
case *packet.SetActorMotion:
e, ok := w.entities[pk.EntityRuntimeID]
e, ok := w.worldState.entities[pk.EntityRuntimeID]
if ok {
e.Velocity = pk.Velocity
}
case *packet.MoveActorDelta:
e, ok := w.entities[pk.EntityRuntimeID]
e, ok := w.worldState.entities[pk.EntityRuntimeID]
if ok {
if pk.Flags&packet.MoveActorDeltaFlagHasX != 0 {
e.Position[0] = pk.Position[0]
@ -251,14 +263,14 @@ func (w *WorldState) ProcessEntityPackets(pk packet.Packet) packet.Packet {
//}
}
case *packet.MoveActorAbsolute:
e, ok := w.entities[pk.EntityRuntimeID]
e, ok := w.worldState.entities[pk.EntityRuntimeID]
if ok {
e.Position = pk.Position
e.Pitch = pk.Rotation.X()
e.Yaw = pk.Rotation.Y()
}
case *packet.MobEquipment:
e, ok := w.entities[pk.EntityRuntimeID]
e, ok := w.worldState.entities[pk.EntityRuntimeID]
if ok {
w, ok := e.Inventory[pk.WindowID]
if !ok {
@ -268,7 +280,7 @@ func (w *WorldState) ProcessEntityPackets(pk packet.Packet) packet.Packet {
w[pk.HotBarSlot] = pk.NewItem
}
case *packet.MobArmourEquipment:
e, ok := w.entities[pk.EntityRuntimeID]
e, ok := w.worldState.entities[pk.EntityRuntimeID]
if ok {
e.Helmet = &pk.Helmet
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 (
"image"
@ -10,6 +10,7 @@ import (
"github.com/bedrock-tool/bedrocktool/locale"
"github.com/bedrock-tool/bedrocktool/ui/messages"
"github.com/bedrock-tool/bedrocktool/utils"
"github.com/go-gl/mathgl/mgl32"
"golang.design/x/lockfree"
"github.com/df-mc/dragonfly/server/world/chunk"
@ -64,8 +65,9 @@ func (m *MapUI) GetBounds() (min, max protocol.ChunkPos) {
}
type RenderElem struct {
pos protocol.ChunkPos
ch *chunk.Chunk
pos protocol.ChunkPos
ch *chunk.Chunk
complete bool
}
type MapUI struct {
@ -78,10 +80,10 @@ type MapUI struct {
l sync.RWMutex
ticker *time.Ticker
w *WorldState
w *worldsHandler
}
func NewMapUI(w *WorldState) *MapUI {
func NewMapUI(w *worldsHandler) *MapUI {
m := &MapUI{
img: image.NewRGBA(image.Rect(0, 0, 128, 128)),
zoomLevel: 16,
@ -94,7 +96,7 @@ func NewMapUI(w *WorldState) *MapUI {
}
func (m *MapUI) Start() {
r := m.w.gui.Message("can_show_images", nil)
r := m.w.gui.Message(messages.CanShowImages{})
if r.Ok {
m.showOnGui = true
}
@ -181,14 +183,14 @@ func (m *MapUI) SchedRedraw() {
// Redraw draws chunk images to the map image
func (m *MapUI) Redraw() {
m.l.Lock()
updatedChunks := []protocol.ChunkPos{}
updatedChunks := make([]protocol.ChunkPos, 0, m.renderQueue.Length())
for {
r, ok := m.renderQueue.Dequeue().(*RenderElem)
if !ok {
break
}
if r.ch != nil {
m.renderedChunks[r.pos] = Chunk2Img(r.ch)
m.renderedChunks[r.pos] = utils.Chunk2Img(r.ch, !r.complete)
} else {
m.renderedChunks[r.pos] = black16x16
}
@ -197,11 +199,10 @@ func (m *MapUI) Redraw() {
m.l.Unlock()
middle := protocol.ChunkPos{
int32(m.w.PlayerPos.Position.X()),
int32(m.w.PlayerPos.Position.Z()),
int32(m.w.serverState.PlayerPos.Position.X()),
int32(m.w.serverState.PlayerPos.Position.Z()),
}
// total_width := 32 * math.Ceil(float64(chunks_x)/32)
chunksPerLine := float64(128 / m.zoomLevel)
pxPerBlock := 128 / chunksPerLine / 16 // how many pixels per block
pxSizeChunk := int(math.Floor(pxPerBlock * 16))
@ -223,12 +224,11 @@ func (m *MapUI) Redraw() {
utils.DrawImgScaledPos(m.img, m.renderedChunks[_ch], px, pxSizeChunk)
}
}
ChunkCount := len(m.renderedChunks)
if m.showOnGui {
min, max := m.GetBounds()
m.w.gui.Message(messages.UpdateMap, messages.UpdateMapPayload{
ChunkCount: ChunkCount,
Rotation: m.w.PlayerPos.Yaw,
m.w.gui.Message(messages.UpdateMap{
ChunkCount: len(m.renderedChunks),
Rotation: m.w.serverState.PlayerPos.Yaw,
UpdatedTiles: updatedChunks,
Tiles: m.renderedChunks,
BoundsMin: min,
@ -261,19 +261,34 @@ func (m *MapUI) ToImage() *image.RGBA {
return img2
}
func (m *MapUI) SetChunk(pos protocol.ChunkPos, ch *chunk.Chunk) {
m.renderQueue.Enqueue(&RenderElem{pos, ch})
func (m *MapUI) SetChunk(pos protocol.ChunkPos, ch *chunk.Chunk, complete bool) {
m.renderQueue.Enqueue(&RenderElem{pos, ch, complete})
m.SchedRedraw()
}
func (w *WorldState) ProcessAnimate(pk *packet.Animate) {
func (w *worldsHandler) ProcessAnimate(pk *packet.Animate) {
if pk.ActionType == packet.AnimateActionSwingArm {
w.mapUI.ChangeZoom()
w.proxy.SendPopup(locale.Loc("zoom_level", locale.Strmap{"Level": w.mapUI.zoomLevel}))
}
}
func (w *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) {
case *packet.MovePlayer:
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,
)))
go func() {
<-ctx.Done()
listener.Close()
}()
clientConn, err := listener.Accept()
if err != nil {
return err

View File

@ -1,47 +1,18 @@
package subcommands
import (
"bytes"
"context"
"encoding/binary"
"flag"
"io"
"net"
"os"
"sync"
"time"
"github.com/bedrock-tool/bedrocktool/handlers"
"github.com/bedrock-tool/bedrocktool/locale"
"github.com/bedrock-tool/bedrocktool/utils"
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
"github.com/sirupsen/logrus"
)
func init() {
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 {
ServerAddress string
}
@ -58,31 +29,10 @@ func (c *CaptureCMD) Execute(ctx context.Context, ui utils.UI) error {
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()
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 {
return err
}
return nil
proxy.AddHandler(handlers.NewPacketCapturer())
return proxy.Run(ctx, address, hostname)
}

View File

@ -3,15 +3,10 @@ package subcommands
import (
"context"
"flag"
"fmt"
"os"
"time"
"github.com/bedrock-tool/bedrocktool/handlers"
"github.com/bedrock-tool/bedrocktool/locale"
"github.com/bedrock-tool/bedrocktool/utils"
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
"github.com/sirupsen/logrus"
)
type ChatLogCMD struct {
@ -32,35 +27,12 @@ func (c *ChatLogCMD) Execute(ctx context.Context, ui utils.UI) error {
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()
if err != nil {
return err
}
proxy.PacketCB = func(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)
}
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
proxy.AddHandler(handlers.NewChatLogger())
return proxy.Run(ctx, address, hostname)
}
func init() {

View File

@ -3,53 +3,36 @@ package subcommands
import (
"context"
"flag"
"strings"
seconduser "github.com/bedrock-tool/bedrocktool/handlers/second-user"
"github.com/bedrock-tool/bedrocktool/locale"
"github.com/bedrock-tool/bedrocktool/utils"
)
type DebugProxyCMD struct {
ServerAddress string
Filter string
}
func (*DebugProxyCMD) Name() string { return "debug-proxy" }
func (*DebugProxyCMD) Synopsis() string { return locale.Loc("debug_proxy_synopsis", nil) }
func (c *DebugProxyCMD) SetFlags(f *flag.FlagSet) {
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 {
address, _, err := utils.ServerInput(ctx, c.ServerAddress)
address, hostname, err := utils.ServerInput(ctx, c.ServerAddress)
if err != nil {
return err
}
utils.Options.Debug = true
filters := strings.Split(c.Filter, ",")
if len(filters) > 0 {
for _, v := range filters {
if len(v) == 0 {
continue
}
if string(v[0]) == "*" {
v = v[1:]
}
v = strings.TrimPrefix(v, "packet.")
v = "packet." + v
utils.ExtraVerbose = append(utils.ExtraVerbose, v)
}
}
proxy, err := utils.NewProxy()
if err != nil {
return err
}
err = proxy.Run(ctx, address)
return err
proxy.AddHandler(seconduser.NewSecondUser())
return proxy.Run(ctx, address, hostname)
}
func init() {

View File

@ -3,128 +3,15 @@ package skins
import (
"context"
"flag"
"fmt"
"os"
"path"
"strings"
"time"
"github.com/bedrock-tool/bedrocktool/handlers"
"github.com/bedrock-tool/bedrocktool/locale"
"github.com/bedrock-tool/bedrocktool/ui/messages"
"github.com/bedrock-tool/bedrocktool/utils"
"github.com/google/uuid"
"github.com/sandertv/gophertunnel/minecraft/protocol"
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
"github.com/sirupsen/logrus"
"github.com/sandertv/gophertunnel/minecraft"
)
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 {
ServerAddress string
Filter string
@ -148,41 +35,25 @@ func (c *SkinCMD) Execute(ctx context.Context, ui utils.UI) error {
proxy, _ := utils.NewProxy()
proxy.WithClient = !c.NoProxy
proxy.OnClientConnect = func(hasClient bool) {
ui.Message(messages.SetUIState, messages.UIStateConnecting)
}
proxy.ConnectCB = func(err error) bool {
if err != nil {
return false
}
ui.Message(messages.SetUIState, messages.UIStateMain)
logrus.Info(locale.Loc("ctrl_c_to_exit", nil))
return true
}
outPathBase := fmt.Sprintf("skins/%s", hostname)
os.MkdirAll(outPathBase, 0o755)
s := NewSkinsSession(proxy, hostname, outPathBase)
proxy.PacketCB = func(pk packet.Packet, 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
}
proxy.AddHandler(handlers.NewSkinSaver(func(sa handlers.SkinAdd) {
ui.Message(messages.NewSkin{
PlayerName: sa.PlayerName,
Skin: sa.Skin,
})
}))
proxy.AddHandler(&utils.ProxyHandler{
Name: "Skin CMD",
OnClientConnect: func(conn *minecraft.Conn) {
ui.Message(messages.SetUIState(messages.UIStateConnecting))
},
})
if proxy.WithClient {
ui.Message(messages.SetUIState, messages.UIStateConnect)
ui.Message(messages.SetUIState(messages.UIStateConnect))
} 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
}

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
import (
"bytes"
"context"
"encoding/json"
"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/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/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() {
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{})
}
type WorldCMD struct {
ServerAddress string
Packs bool
EnableVoid bool
SaveImage bool
ExperimentInventory bool
ServerAddress string
Packs bool
EnableVoid bool
SaveEntities bool
SaveInventories bool
SaveImage bool
}
func (*WorldCMD) Name() string { return "worlds" }
@ -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.EnableVoid, "void", true, locale.Loc("enable_void", nil))
f.BoolVar(&c.SaveImage, "image", false, locale.Loc("save_image", nil))
f.BoolVar(&c.ExperimentInventory, "inv", false, locale.Loc("test_block_inv", nil))
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 {
@ -148,467 +46,20 @@ func (c *WorldCMD) Execute(ctx context.Context, ui utils.UI) error {
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.ConnectCB = w.OnConnect
proxy.OnClientConnect = func(hasClient bool) {
w.gui.Message(messages.SetUIState, messages.UIStateConnecting)
}
proxy.PacketCB = func(pk packet.Packet, toServer bool, _ time.Time) (packet.Packet, error) {
forward := true
proxy.AddHandler(worlds.NewWorldsHandler(ctx, ui, worlds.WorldSettings{
VoidGen: c.EnableVoid,
WithPacks: c.Packs,
SaveEntities: c.SaveEntities,
SaveInventories: c.SaveInventories,
SaveImage: c.SaveImage,
}))
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.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)
ui.Message(messages.SetUIState(messages.UIStateConnect))
err = proxy.Run(ctx, serverAddress, hostname)
if err != nil {
return err
}
w.SaveAndReset()
ui.Message(messages.SetUIState(messages.UIStateFinished))
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 {
r := g.router.Handler(name, data)
func (g *GUI) Message(data interface{}) messages.MessageResponse {
r := g.router.Handler(data)
if r.Ok || r.Data != nil {
return r
}
@ -125,8 +125,8 @@ func (g *GUI) Message(name string, data interface{}) messages.MessageResponse {
Data: nil,
}
switch name {
case "can_show_images":
switch data.(type) {
case messages.CanShowImages:
r.Ok = true
}

View File

@ -13,7 +13,7 @@ import (
"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 {
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}
}
func (r *Router) Handler(name string, data interface{}) messages.MessageResponse {
func (r *Router) Handler(data interface{}) messages.MessageResponse {
page, ok := r.pages[r.current]
if ok {
return page.Handler()(name, data)
return page.Handler()(data)
}
return messages.MessageResponse{}
}

View File

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

View File

@ -24,7 +24,7 @@ type Page struct {
State messages.UIState
SkinsList widget.List
l sync.Mutex
Skins []messages.NewSkinPayload
Skins []messages.NewSkin
}
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)
}
func (p *Page) handler(name string, data interface{}) messages.MessageResponse {
func (p *Page) handler(data interface{}) messages.MessageResponse {
r := messages.MessageResponse{
Ok: false,
Data: nil,
}
switch name {
switch m := data.(type) {
case messages.SetUIState:
state := data.(messages.UIState)
p.State = state
p.State = m
p.Router.Invalidate()
r.Ok = true
case messages.Init:
init := data.(messages.InitPayload)
_ = init
r.Ok = true
case messages.NewSkin:
p.l.Lock()
new_skin := data.(messages.NewSkinPayload)
p.Skins = append(p.Skins, new_skin)
r.Ok = true
p.Skins = append(p.Skins, m)
p.l.Unlock()
p.Router.Invalidate()
r.Ok = true
}
return r
}

View File

@ -23,6 +23,7 @@ type Map struct {
center f32.Point
transform f32.Affine2D
grabbed bool
cursor image.Point
MapImage *image.RGBA
BoundsMin protocol.ChunkPos
@ -40,7 +41,7 @@ func (m *Map) HandlePointerEvent(e pointer.Event) {
case pointer.Release:
m.grabbed = false
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.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)
m.imageOp.Add(gtx.Ops)
paint.PaintOp{}.Add(gtx.Ops)
if m.cursor.In(image.Rectangle(gtx.Constraints)) {
if m.grabbed {
pointer.CursorGrabbing.Add(gtx.Ops)
} else {
pointer.CursorGrab.Add(gtx.Ops)
}
}
}
size := gtx.Constraints.Max
@ -90,7 +98,7 @@ func drawTile(img *image.RGBA, min, pos protocol.ChunkPos, tile *image.RGBA) {
), tile, image.Point{}, draw.Src)
}
func (m *Map) Update(u *messages.UpdateMapPayload) {
func (m *Map) Update(u *messages.UpdateMap) {
if m.MapImage == nil {
m.scaleFactor = 1
}

View File

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

View File

@ -28,6 +28,8 @@ func (s *worldSettings) Apply() {
s.worlds.EnableVoid = s.voidGen.Value
s.worlds.SaveImage = s.saveImage.Value
s.worlds.ServerAddress = s.serverAddress.Text()
s.worlds.SaveEntities = true
s.worlds.SaveInventories = true
}
func (s *worldSettings) Layout(gtx layout.Context, th *material.Theme) layout.Dimensions {

View File

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

View File

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

View File

@ -35,24 +35,24 @@ func (bp *BehaviourPack) AddEntity(entity EntityIn) {
return
}
if _, ok := bp.entities[entity.Identifier]; ok {
return
entry, ok := bp.entities[entity.Identifier]
if !ok {
entry = entityBehaviour{
FormatVersion: bp.formatVersion,
MinecraftEntity: MinecraftEntity{
Description: EntityDescription{
Identifier: entity.Identifier,
Spawnable: true,
Summonable: true,
Experimental: true,
},
ComponentGroups: make(map[string]any),
Components: make(map[string]any),
Events: nil,
},
}
}
entry := entityBehaviour{
FormatVersion: bp.formatVersion,
MinecraftEntity: MinecraftEntity{
Description: EntityDescription{
Identifier: entity.Identifier,
Spawnable: true,
Summonable: true,
Experimental: true,
},
ComponentGroups: make(map[string]any),
Components: make(map[string]any),
Events: nil,
},
}
for _, av := range entity.Attr {
switch av.Name {
case "minecraft:health":
@ -72,6 +72,13 @@ func (bp *BehaviourPack) AddEntity(entity EntityIn) {
"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
}

View File

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

View File

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

View File

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

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 (
"bytes"
"encoding/gob"
"github.com/df-mc/dragonfly/server/block/cube"
"github.com/df-mc/dragonfly/server/item"
"github.com/df-mc/dragonfly/server/world"
"github.com/go-gl/mathgl/mgl64"
"golang.org/x/exp/constraints"
"time"
)
// ReadItem decodes the data of an item into an item stack.
func ReadItem(data map[string]any, s *item.Stack) item.Stack {
disk := s == nil
// Bool reads a uint8 value from a map at key k and returns true if it equals 1.
func Bool(m map[string]any, k string) bool {
return Uint8(m, k) == 1
}
// Uint8 reads a uint8 value from a map at key k.
func Uint8(m map[string]any, k string) uint8 {
v, _ := m[k].(uint8)
return v
}
// String reads a string value from a map at key k.
func String(m map[string]any, k string) string {
v, _ := m[k].(string)
return v
}
// Int16 reads an int16 value from a map at key k.
func Int16(m map[string]any, k string) int16 {
v, _ := m[k].(int16)
return v
}
// Int32 reads an int32 value from a map at key k.
func Int32(m map[string]any, k string) int32 {
v, _ := m[k].(int32)
return v
}
// Int64 reads an int16 value from a map at key k.
func Int64(m map[string]any, k string) int64 {
v, _ := m[k].(int64)
return v
}
// TickDuration reads a uint8/int16/in32 value from a map at key k and converts
// it from ticks to a time.Duration.
func TickDuration[T constraints.Integer](m map[string]any, k string) time.Duration {
var v time.Duration
switch any(*new(T)).(type) {
case uint8:
v = time.Duration(Uint8(m, k))
case int16:
v = time.Duration(Int16(m, k))
case int32:
v = time.Duration(Int32(m, k))
default:
panic("invalid tick duration value type")
}
return v * time.Millisecond * 50
}
// Float32 reads a float32 value from a map at key k.
func Float32(m map[string]any, k string) float32 {
v, _ := m[k].(float32)
return v
}
// Float64 reads a float64 value from a map at key k.
func Float64(m map[string]any, k string) float64 {
v, _ := m[k].(float64)
return v
}
// Slice reads a []any value from a map at key k.
func Slice(m map[string]any, k string) []any {
v, _ := m[k].([]any)
return v
}
// Vec3 converts x, y and z values in an NBT map to an mgl64.Vec3.
func Vec3(x map[string]any, k string) mgl64.Vec3 {
if i, ok := x[k].([]any); ok {
if len(i) != 3 {
return mgl64.Vec3{}
}
var v mgl64.Vec3
for index, f := range i {
f32, _ := f.(float32)
v[index] = float64(f32)
}
return v
} else if i, ok := x[k].([]float32); ok {
if len(i) != 3 {
return mgl64.Vec3{}
}
return mgl64.Vec3{float64(i[0]), float64(i[1]), float64(i[2])}
}
return mgl64.Vec3{}
}
// Vec3ToFloat32Slice converts an mgl64.Vec3 to a []float32 with 3 elements.
func Vec3ToFloat32Slice(x mgl64.Vec3) []float32 {
return []float32{float32(x[0]), float32(x[1]), float32(x[2])}
}
// Pos converts x, y and z values in an NBT map to a cube.Pos.
func Pos(x map[string]any, k string) cube.Pos {
if i, ok := x[k].([]any); ok {
if len(i) != 3 {
return cube.Pos{}
}
var v cube.Pos
for index, f := range i {
f32, _ := f.(int32)
v[index] = int(f32)
}
return v
} else if i, ok := x[k].([]int32); ok {
if len(i) != 3 {
return cube.Pos{}
}
return cube.Pos{int(i[0]), int(i[1]), int(i[2])}
}
return cube.Pos{}
}
// PosToInt32Slice converts a cube.Pos to a []int32 with 3 elements.
func PosToInt32Slice(x cube.Pos) []int32 {
return []int32{int32(x[0]), int32(x[1]), int32(x[2])}
}
// MapItem converts an item's name, count, damage (and properties when it is a block) in a map obtained by decoding NBT
// to a world.Item.
func MapItem(x map[string]any, k string) item.Stack {
if m, ok := x[k].(map[string]any); ok {
tag, ok := m["tag"].(map[string]any)
if !ok {
tag = map[string]any{}
}
s := readItemStack(m, tag)
readDamage(tag, &s, true)
readEnchantments(tag, &s)
readDisplay(tag, &s)
readDragonflyData(tag, &s)
return s
}
return item.Stack{}
}
// Item decodes the data of an item into an item stack.
func Item(data map[string]any, s *item.Stack) item.Stack {
disk, tag := s == nil, data
if disk {
a := readItemStack(data)
t, ok := data["tag"].(map[string]any)
if !ok {
t = map[string]any{}
}
tag = t
a := readItemStack(data, tag)
s = &a
}
readDamage(data, s, disk)
readAnvilCost(data, s)
readDisplay(data, s)
readEnchantments(data, s)
readDragonflyData(data, s)
readAnvilCost(tag, s)
readDamage(tag, s, disk)
readDisplay(tag, s)
readDragonflyData(tag, s)
readEnchantments(tag, s)
return *s
}
// ReadBlock decodes the data of a block into a world.Block.
func ReadBlock(m map[string]any) world.Block {
name, _ := m["name"].(string)
properties, _ := m["states"].(map[string]any)
b, _ := world.BlockByName(name, properties)
return b
// Block decodes the data of a block into a world.Block.
func Block(m map[string]any, k string) world.Block {
if mk, ok := m[k].(map[string]any); ok {
name, _ := mk["name"].(string)
properties, _ := mk["states"].(map[string]any)
b, _ := world.BlockByName(name, properties)
return b
}
return nil
}
// readItemStack reads an item.Stack from the NBT in the map passed.
func readItemStack(m map[string]any) item.Stack {
func readItemStack(m, t map[string]any) item.Stack {
var it world.Item
if blockItem, ok := MapBlock(m, "Block").(world.Item); ok {
if blockItem, ok := Block(m, "Block").(world.Item); ok {
it = blockItem
}
if v, ok := world.ItemByName(Map[string](m, "Name"), Map[int16](m, "Damage")); ok {
if v, ok := world.ItemByName(String(m, "Name"), Int16(m, "Damage")); ok {
it = v
}
if it == nil {
return item.Stack{}
}
if n, ok := it.(world.NBTer); ok {
it = n.DecodeNBT(m).(world.Item)
it = n.DecodeNBT(t).(world.Item)
}
return item.NewStack(it, int(Map[byte](m, "Count")))
return item.NewStack(it, int(Uint8(m, "Count")))
}
// readDamage reads the damage value stored in the NBT with the Damage tag and saves it to the item.Stack passed.
func readDamage(m map[string]any, s *item.Stack, disk bool) {
if disk {
*s = s.Damage(int(Map[int16](m, "Damage")))
*s = s.Damage(int(Int16(m, "Damage")))
return
}
*s = s.Damage(int(Map[int32](m, "Damage")))
*s = s.Damage(int(Int32(m, "Damage")))
}
// readAnvilCost ...
func readAnvilCost(m map[string]any, s *item.Stack) {
*s = s.WithAnvilCost(int(Map[int32](m, "RepairCost")))
*s = s.WithAnvilCost(int(Int32(m, "RepairCost")))
}
// readEnchantments reads the enchantments stored in the ench tag of the NBT passed and stores it into an item.Stack.
func readEnchantments(m map[string]any, s *item.Stack) {
enchantments, ok := m["ench"].([]map[string]any)
if !ok {
for _, e := range Map[[]any](m, "ench") {
for _, e := range Slice(m, "ench") {
if v, ok := e.(map[string]any); ok {
enchantments = append(enchantments, v)
}
}
}
for _, ench := range enchantments {
if t, ok := item.EnchantmentByID(int(Map[int16](ench, "id"))); ok {
*s = s.WithEnchantments(item.NewEnchantment(t, int(Map[int16](ench, "lvl"))))
if t, ok := item.EnchantmentByID(int(Int16(ench, "id"))); ok {
*s = s.WithEnchantments(item.NewEnchantment(t, int(Int16(ench, "lvl"))))
}
}
}

View File

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

View File

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

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"
"net"
"os"
"reflect"
"strings"
"sync"
"time"
"github.com/bedrock-tool/bedrocktool/locale"
"github.com/repeale/fp-go"
"github.com/sandertv/gophertunnel/minecraft"
"github.com/sandertv/gophertunnel/minecraft/protocol"
"github.com/sandertv/gophertunnel/minecraft/protocol/login"
@ -23,6 +25,7 @@ import (
var DisconnectReason = "Connection lost"
/*
type dummyProto struct {
id int32
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 {
return []packet.Packet{pk}
}
*/
type (
PacketFunc func(header packet.Header, payload []byte, src, dst net.Addr)
PacketCallback func(pk packet.Packet, toServer bool, timeReceived time.Time) (packet.Packet, error)
ClientConnectCallback func(hasClient bool)
ConnectCallback func(err error) bool
IngameCommand struct {
PacketFunc func(header packet.Header, payload []byte, src, dst net.Addr)
IngameCommand struct {
Exec func(cmdline []string) bool
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 {
Server *minecraft.Conn
Client *minecraft.Conn
Listener *minecraft.Listener
commands map[string]IngameCommand
Server *minecraft.Conn
Client *minecraft.Conn
clientAddr net.Addr
Listener *minecraft.Listener
AlwaysGetPacks bool
WithClient bool
IgnoreDisconnect bool
CustomClientData *login.ClientData
// called for every packet
PacketFunc PacketFunc
// called after client connected
OnClientConnect ClientConnectCallback
// called after game started
ConnectCB ConnectCallback
// called on every packet after login
PacketCB PacketCallback
commands map[string]IngameCommand
handlers []*ProxyHandler
}
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) {
switch _pk := pk.(type) {
case *packet.CommandRequest:
@ -205,7 +231,7 @@ func (p *ProxyContext) CommandHandlerPacketCB(pk packet.Packet, toServer bool, _
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
if toServer {
c1 = p.Client
@ -225,10 +251,17 @@ func (p *ProxyContext) proxyLoop(ctx context.Context, toServer bool, packetCBs [
return err
}
for _, packetCB := range packetCBs {
pk, err = packetCB(pk, toServer, time.Now())
if err != nil {
return err
pkName := reflect.TypeOf(pk).String()
for _, handler := range p.handlers {
if handler.PacketCB != nil {
pk, err = handler.PacketCB(pk, toServer, time.Now())
if err != nil {
return err
}
if pk == nil {
logrus.Tracef("Dropped Packet: %s", pkName)
break
}
}
}
@ -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!") {
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
if Options.Preload {
logrus.Info(locale.Loc("preloading_packs", nil))
var serverConn *minecraft.Conn
serverConn, err = connectServer(ctx, serverAddress, nil, true, nil)
serverConn, err := connectServer(ctx, serverAddress, nil, true, func(header packet.Header, payload []byte, src, dst net.Addr) {})
if err != nil {
err = fmt.Errorf(locale.Loc("failed_to_connect", locale.Strmap{"Address": serverAddress, "Err": err}))
return
return fmt.Errorf(locale.Loc("failed_to_connect", locale.Strmap{"Address": serverAddress, "Err": err}))
}
serverConn.Close()
packs = serverConn.ResourcePacks()
@ -277,9 +327,7 @@ func (p *ProxyContext) Run(ctx context.Context, serverAddress string) (err error
if err != nil {
return err
}
go func() {
<-ctx.Done()
defer func() {
if p.Client != nil {
p.Listener.Disconnect(p.Client, DisconnectReason)
}
@ -298,51 +346,81 @@ func (p *ProxyContext) Run(ctx context.Context, serverAddress string) (err error
cdp = &cd
}
if p.OnClientConnect != nil {
p.OnClientConnect(p.WithClient)
for _, handler := range p.handlers {
if handler.OnClientConnect == nil {
continue
}
handler.OnClientConnect(p.Client)
}
if p.CustomClientData != nil {
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 p.ConnectCB != nil {
if p.ConnectCB(err) {
for _, handler := range p.handlers {
if handler.ConnectCB == nil {
continue
}
ignore := handler.ConnectCB(err)
if ignore {
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
}
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
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}))
return err
}
if p.ConnectCB != nil {
if !p.ConnectCB(nil) {
for _, handler := range p.handlers {
if handler.ConnectCB == nil {
continue
}
if !handler.ConnectCB(nil) {
return errors.New("Cancelled")
}
}
// append self to handlers for commands
p.handlers = append(p.handlers, &ProxyHandler{
Name: "Commands",
PacketCB: p.CommandHandlerPacketCB,
})
wg := sync.WaitGroup{}
var cbs []PacketCallback
cbs = append(cbs, p.CommandHandlerPacketCB)
if p.PacketCB != nil {
cbs = append(cbs, p.PacketCB)
}
// server to client
wg.Add(1)
go func() {
defer wg.Done()
if err := p.proxyLoop(ctx, false, cbs); err != nil {
if err := p.proxyLoop(ctx, false); err != nil {
logrus.Error(err)
return
}
@ -353,13 +431,39 @@ func (p *ProxyContext) Run(ctx context.Context, serverAddress string) (err error
wg.Add(1)
go func() {
defer wg.Done()
if err := p.proxyLoop(ctx, true, cbs); err != nil {
if err := p.proxyLoop(ctx, true); err != nil {
logrus.Error(err)
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()
for _, handler := range p.handlers {
if handler.OnEnd != nil {
handler.OnEnd()
}
}
return err
}

View File

@ -63,6 +63,13 @@ func createReplayConnection(ctx context.Context, filename string, proxy *ProxyCo
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()
gameStarted := false
@ -77,7 +84,7 @@ func createReplayConnection(ctx context.Context, filename string, proxy *ProxyCo
offset, _ := f.Seek(0, io.SeekCurrent)
if offset == totalSize {
logrus.Info("Reached End")
return nil
break
}
binary.Read(f, binary.LittleEndian, &magic)
@ -96,7 +103,7 @@ func createReplayConnection(ctx context.Context, filename string, proxy *ProxyCo
n, err := f.Read(payload)
if err != nil {
logrus.Error(err)
return nil
break
}
if n != int(packetLength) {
return fmt.Errorf("truncated %d", i)
@ -123,13 +130,28 @@ func createReplayConnection(ctx context.Context, filename string, proxy *ProxyCo
b := protocol.NewWriter(f, 0)
pk.Marshal(b)
if Options.Debug {
PacketLogger(packet.Header{PacketID: pk.ID()}, f.Bytes(), &net.UDPAddr{}, &net.UDPAddr{})
hdr := packet.Header{PacketID: pk.ID()}
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 proxy.PacketCB != nil {
proxy.PacketCB(pk, toServer, timeReceived)
for _, handler := range proxy.handlers {
if handler.PacketCB != nil {
handler.PacketCB(pk, toServer, timeReceived)
}
}
} else {
switch pk := pk.(type) {
@ -164,11 +186,19 @@ func createReplayConnection(ctx context.Context, filename string, proxy *ProxyCo
DisablePlayerInteractions: pk.DisablePlayerInteractions,
})
gameStarted = true
if proxy.ConnectCB != nil {
proxy.ConnectCB(nil)
for _, handler := range proxy.handlers {
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 (
"crypto/sha256"
"encoding/json"
"errors"
"fmt"
@ -23,17 +22,13 @@ type SkinGeometry struct {
Textureheight int `json:"textureheight"`
VisibleBoundsWidth float64 `json:"visible_bounds_width"`
VisibleBoundsHeight float64 `json:"visible_bounds_height"`
VisibleBoundsOffset []float64 `json:"visible_bounds_offset"`
VisibleBoundsOffset []float64 `json:"visible_bounds_offset,omitempty"`
Bones []any `json:"bones"`
}
func (skin *Skin) Hash() uuid.UUID {
h := sha256.New()
h.Write(skin.SkinData)
h.Write(skin.SkinGeometry)
h.Write(skin.CapeData)
h.Write([]byte(skin.SkinID))
return uuid.NewSHA1(uuid.NameSpaceURL, h.Sum(nil))
h := append(skin.CapeData, append(skin.SkinData, skin.SkinGeometry...)...)
return uuid.NewSHA1(uuid.NameSpaceURL, h)
}
func (skin *Skin) getGeometry() (*SkinGeometry, string, error) {

View File

@ -1,4 +1,4 @@
package skins
package utils
import (
"encoding/json"
@ -6,18 +6,33 @@ import (
"os"
"path"
"github.com/bedrock-tool/bedrocktool/utils"
"github.com/google/uuid"
"github.com/sandertv/gophertunnel/minecraft/protocol"
"github.com/sandertv/gophertunnel/minecraft/resource"
"github.com/sirupsen/logrus"
)
type SkinMeta struct {
SkinID string
PlayFabID string
PremiumSkin bool
PersonaSkin bool
CapeID string
SkinColour string
ArmSize string
Trusted bool
PersonaPieces []protocol.PersonaPiece
}
type _skinWithIndex struct {
i int
skin *Skin
}
func (s _skinWithIndex) Name(name string) string {
if s.i == 1 {
return name
}
return fmt.Sprintf("%s-%d", name, s.i)
}
@ -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
}
}

View File

@ -8,6 +8,8 @@ import (
"runtime"
"github.com/sanbornm/go-selfupdate/selfupdate"
"github.com/shirou/gopsutil/v3/cpu"
"github.com/shirou/gopsutil/v3/mem"
)
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
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)
if err != nil {

View File

@ -8,8 +8,6 @@ import (
"crypto/sha256"
"encoding/json"
"errors"
"io"
"net"
"os"
"path"
"regexp"
@ -17,7 +15,6 @@ import (
"sync"
"github.com/bedrock-tool/bedrocktool/locale"
"github.com/bedrock-tool/bedrocktool/utils/crypt"
"github.com/google/uuid"
"github.com/sandertv/gophertunnel/minecraft"
"github.com/sirupsen/logrus"
@ -25,7 +22,6 @@ import (
//"github.com/sandertv/gophertunnel/minecraft/gatherings"
"github.com/sandertv/gophertunnel/minecraft/protocol/login"
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
"github.com/sandertv/gophertunnel/minecraft/resource"
)
@ -67,14 +63,7 @@ func connectServer(ctx context.Context, address string, ClientData *login.Client
serverConn, err = minecraft.Dialer{
TokenSource: GetTokenSource(),
ClientData: cd,
PacketFunc: func(header packet.Header, payload []byte, src, dst net.Addr) {
if Options.Debug {
PacketLogger(header, payload, src, dst)
}
if packetFunc != nil {
packetFunc(header, payload, src, dst)
}
},
PacketFunc: packetFunc,
DownloadResourcePack: func(id uuid.UUID, version string, current int, total int) bool {
return wantPacks
},
@ -84,18 +73,17 @@ func connectServer(ctx context.Context, address string, ClientData *login.Client
}
logrus.Debug(locale.Loc("connected", nil))
ClientAddr = serverConn.LocalAddr()
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{}
errs := make(chan error, 2)
if clientConn != nil {
wg.Add(1)
go func() {
defer wg.Done()
errs <- clientConn.StartGame(serverConn.GameData())
errs <- clientConn.StartGame(gd)
}()
}
wg.Add(1)
@ -119,30 +107,6 @@ func spawnConn(ctx context.Context, clientConn *minecraft.Conn, serverConn *mine
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
func SplitExt(filename string) (name, ext string) {
name, ext = path.Base(filename), path.Ext(filename)
@ -194,49 +158,3 @@ func CfbDecrypt(data []byte, key []byte) []byte {
}
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 (
"archive/zip"
"compress/flate"
"io"
"io/fs"
"os"
@ -36,6 +37,12 @@ func ZipFolder(filename, folder string) error {
logrus.Fatal(err)
}
zw := zip.NewWriter(f)
// Register a custom Deflate compressor.
zw.RegisterCompressor(zip.Deflate, func(out io.Writer) (io.WriteCloser, error) {
return flate.NewWriter(out, flate.NoCompression)
})
err = filepath.WalkDir(folder, func(path string, d fs.DirEntry, err error) error {
if !d.Type().IsDir() {
rel := path[len(folder)+1:]