Compare commits
31 Commits
ae496e9238
...
796a846b8e
Author | SHA1 | Date |
---|---|---|
olebeck | 796a846b8e | |
olebeck | 99f7b012bf | |
olebeck | 3b1aa74100 | |
olebeck | 3dadabf706 | |
olebeck | a5f77eca3c | |
olebeck | 2996a752fc | |
olebeck | 085244e656 | |
olebeck | 543e1335ee | |
olebeck | 9a677feeac | |
olebeck | 32bfdfa319 | |
olebeck | bf786b1010 | |
olebeck | e8dae60049 | |
olebeck | 17d86c9ddb | |
olebeck | 4e7cfeb3b4 | |
olebeck | f1cb7df05e | |
olebeck | 9b3a31879a | |
olebeck | 88589933ea | |
olebeck | 29417b600c | |
olebeck | eca888e3f2 | |
olebeck | 4c0dd665f1 | |
olebeck | b7ad339a64 | |
olebeck | 191f1ecd9a | |
olebeck | 1e990d7fcb | |
olebeck | d3a8219d99 | |
olebeck | 94e02d677c | |
olebeck | c5e7146f24 | |
olebeck | cdf7caea5b | |
olebeck | 47fd5cc2ec | |
olebeck | cef4a5dbfe | |
olebeck | 6376195732 | |
olebeck | 1b1bddc17f |
|
@ -28,3 +28,8 @@ keys.db
|
|||
|
||||
packets.log.gpg
|
||||
customdata.json
|
||||
/other-projects/
|
||||
__pycache__/entityflags.cpython-310.pyc
|
||||
gathering.py
|
||||
*.ipynb
|
||||
entityflags.py
|
||||
|
|
9
build.py
9
build.py
|
@ -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}")
|
||||
|
|
|
@ -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
26
go.mod
|
@ -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
79
go.sum
|
@ -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=
|
||||
|
|
|
@ -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()
|
||||
},
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
package handlers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/bedrock-tool/bedrocktool/utils"
|
||||
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type chatLogger struct {
|
||||
Verbose bool
|
||||
fio *os.File
|
||||
}
|
||||
|
||||
func (c *chatLogger) PacketCB(pk packet.Packet, toServer bool, t time.Time) (packet.Packet, error) {
|
||||
if text, ok := pk.(*packet.Text); ok {
|
||||
logLine := text.Message
|
||||
if c.Verbose {
|
||||
logLine += fmt.Sprintf(" (TextType: %d | XUID: %s | PlatformChatID: %s)", text.TextType, text.XUID, text.PlatformChatID)
|
||||
}
|
||||
c.fio.WriteString(fmt.Sprintf("[%s] ", t.Format(time.RFC3339)))
|
||||
logrus.Info(logLine)
|
||||
if toServer {
|
||||
c.fio.WriteString("SENT: ")
|
||||
}
|
||||
c.fio.WriteString(logLine + "\n")
|
||||
}
|
||||
return pk, nil
|
||||
}
|
||||
|
||||
func NewChatLogger() *utils.ProxyHandler {
|
||||
c := &chatLogger{}
|
||||
return &utils.ProxyHandler{
|
||||
Name: "Packet Capturer",
|
||||
PacketCB: c.PacketCB,
|
||||
AddressAndName: func(address, hostname string) error {
|
||||
filename := fmt.Sprintf("%s_%s_chat.log", hostname, time.Now().Format("2006-01-02_15-04-05_Z07"))
|
||||
f, err := os.Create(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.fio = f
|
||||
return nil
|
||||
},
|
||||
OnEnd: func() {
|
||||
c.fio.Close()
|
||||
},
|
||||
}
|
||||
}
|
|
@ -0,0 +1,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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
package seconduser
|
||||
|
||||
import (
|
||||
"github.com/df-mc/dragonfly/server/block/cube"
|
||||
"github.com/df-mc/dragonfly/server/world"
|
||||
"github.com/go-gl/mathgl/mgl64"
|
||||
)
|
||||
|
||||
type entityType struct {
|
||||
name string
|
||||
}
|
||||
|
||||
func (t *entityType) EncodeEntity() string {
|
||||
return t.name
|
||||
}
|
||||
|
||||
func (t *entityType) BBox(e world.Entity) cube.BBox {
|
||||
return cube.BBox{}
|
||||
}
|
||||
|
||||
type serverEntity struct {
|
||||
t world.EntityType
|
||||
pos mgl64.Vec3
|
||||
rot cube.Rotation
|
||||
}
|
||||
|
||||
func (s *serverEntity) Close() error {
|
||||
return nil
|
||||
}
|
||||
func (s *serverEntity) Position() mgl64.Vec3 {
|
||||
return s.pos
|
||||
}
|
||||
func (s *serverEntity) Rotation() cube.Rotation {
|
||||
return s.rot
|
||||
}
|
||||
func (e *serverEntity) World() *world.World {
|
||||
w, _ := world.OfEntity(e)
|
||||
return w
|
||||
}
|
||||
func (s *serverEntity) Type() world.EntityType {
|
||||
return s.t
|
||||
}
|
||||
|
||||
func newServerEntity(typename string) *serverEntity {
|
||||
return &serverEntity{
|
||||
t: &entityType{name: typename},
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
package seconduser
|
||||
|
||||
import "github.com/df-mc/dragonfly/server/session"
|
||||
|
||||
type fwdlistener struct {
|
||||
Conn chan session.Conn
|
||||
}
|
||||
|
||||
func (l *fwdlistener) Accept() (session.Conn, error) {
|
||||
return <-l.Conn, nil
|
||||
}
|
||||
|
||||
func (l *fwdlistener) Disconnect(conn session.Conn, reason string) error {
|
||||
return conn.Close()
|
||||
}
|
||||
|
||||
func (l *fwdlistener) Close() error {
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,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
|
||||
}
|
|
@ -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())
|
||||
}) {
|
||||
}
|
||||
}
|
|
@ -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
|
||||
},
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 432 B |
|
@ -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()
|
||||
}
|
|
@ -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
|
|
@ -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
|
||||
}
|
|
@ -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)
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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() {
|
||||
|
|
Binary file not shown.
|
@ -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.
|
@ -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 |
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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{}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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{}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -8,11 +8,11 @@ import (
|
|||
func InvFromNBT(inv *inventory.Inventory, items []any) {
|
||||
for _, itemData := range items {
|
||||
data, _ := itemData.(map[string]any)
|
||||
it := ReadItem(data, nil)
|
||||
it := Item(data, nil)
|
||||
if it.Empty() {
|
||||
continue
|
||||
}
|
||||
_ = inv.SetItem(int(Map[byte](data, "Slot")), it)
|
||||
_ = inv.SetItem(int(Uint8(data, "Slot")), it)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,89 +0,0 @@
|
|||
package nbtconv
|
||||
|
||||
import (
|
||||
"github.com/df-mc/dragonfly/server/block/cube"
|
||||
"github.com/df-mc/dragonfly/server/item"
|
||||
"github.com/df-mc/dragonfly/server/world"
|
||||
"github.com/go-gl/mathgl/mgl64"
|
||||
)
|
||||
|
||||
// Map reads a value of the type T from the map passed. Map never panics. If the key was not found in the map
|
||||
// or if the value was of a different type, the default value of type T is returned.
|
||||
func Map[T any](m map[string]any, key string) T {
|
||||
v, _ := m[key].(T)
|
||||
return v
|
||||
}
|
||||
|
||||
// MapVec3 converts x, y and z values in an NBT map to an mgl64.Vec3.
|
||||
func MapVec3(x map[string]any, k string) mgl64.Vec3 {
|
||||
if i, ok := x[k].([]any); ok {
|
||||
if len(i) != 3 {
|
||||
return mgl64.Vec3{}
|
||||
}
|
||||
var v mgl64.Vec3
|
||||
for index, f := range i {
|
||||
f32, _ := f.(float32)
|
||||
v[index] = float64(f32)
|
||||
}
|
||||
return v
|
||||
} else if i, ok := x[k].([]float32); ok {
|
||||
if len(i) != 3 {
|
||||
return mgl64.Vec3{}
|
||||
}
|
||||
return mgl64.Vec3{float64(i[0]), float64(i[1]), float64(i[2])}
|
||||
}
|
||||
return mgl64.Vec3{}
|
||||
}
|
||||
|
||||
// Vec3ToFloat32Slice converts an mgl64.Vec3 to a []float32 with 3 elements.
|
||||
func Vec3ToFloat32Slice(x mgl64.Vec3) []float32 {
|
||||
return []float32{float32(x[0]), float32(x[1]), float32(x[2])}
|
||||
}
|
||||
|
||||
// MapPos converts x, y and z values in an NBT map to a cube.Pos.
|
||||
func MapPos(x map[string]any, k string) cube.Pos {
|
||||
if i, ok := x[k].([]any); ok {
|
||||
if len(i) != 3 {
|
||||
return cube.Pos{}
|
||||
}
|
||||
var v cube.Pos
|
||||
for index, f := range i {
|
||||
f32, _ := f.(int32)
|
||||
v[index] = int(f32)
|
||||
}
|
||||
return v
|
||||
} else if i, ok := x[k].([]int32); ok {
|
||||
if len(i) != 3 {
|
||||
return cube.Pos{}
|
||||
}
|
||||
return cube.Pos{int(i[0]), int(i[1]), int(i[2])}
|
||||
}
|
||||
return cube.Pos{}
|
||||
}
|
||||
|
||||
// PosToInt32Slice converts a cube.Pos to a []int32 with 3 elements.
|
||||
func PosToInt32Slice(x cube.Pos) []int32 {
|
||||
return []int32{int32(x[0]), int32(x[1]), int32(x[2])}
|
||||
}
|
||||
|
||||
// MapBlock converts a block's name and properties in a map obtained by decoding NBT to a world.Block.
|
||||
func MapBlock(x map[string]any, k string) world.Block {
|
||||
if m, ok := x[k].(map[string]any); ok {
|
||||
return ReadBlock(m)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MapItem converts an item's name, count, damage (and properties when it is a block) in a map obtained by decoding NBT
|
||||
// to a world.Item.
|
||||
func MapItem(x map[string]any, k string) item.Stack {
|
||||
if m, ok := x[k].(map[string]any); ok {
|
||||
s := readItemStack(m)
|
||||
readDamage(m, &s, true)
|
||||
readEnchantments(m, &s)
|
||||
readDisplay(m, &s)
|
||||
readDragonflyData(m, &s)
|
||||
return s
|
||||
}
|
||||
return item.Stack{}
|
||||
}
|
|
@ -3,78 +3,233 @@ package nbtconv
|
|||
import (
|
||||
"bytes"
|
||||
"encoding/gob"
|
||||
"github.com/df-mc/dragonfly/server/block/cube"
|
||||
"github.com/df-mc/dragonfly/server/item"
|
||||
"github.com/df-mc/dragonfly/server/world"
|
||||
"github.com/go-gl/mathgl/mgl64"
|
||||
"golang.org/x/exp/constraints"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ReadItem decodes the data of an item into an item stack.
|
||||
func ReadItem(data map[string]any, s *item.Stack) item.Stack {
|
||||
disk := s == nil
|
||||
// Bool reads a uint8 value from a map at key k and returns true if it equals 1.
|
||||
func Bool(m map[string]any, k string) bool {
|
||||
return Uint8(m, k) == 1
|
||||
}
|
||||
|
||||
// Uint8 reads a uint8 value from a map at key k.
|
||||
func Uint8(m map[string]any, k string) uint8 {
|
||||
v, _ := m[k].(uint8)
|
||||
return v
|
||||
}
|
||||
|
||||
// String reads a string value from a map at key k.
|
||||
func String(m map[string]any, k string) string {
|
||||
v, _ := m[k].(string)
|
||||
return v
|
||||
}
|
||||
|
||||
// Int16 reads an int16 value from a map at key k.
|
||||
func Int16(m map[string]any, k string) int16 {
|
||||
v, _ := m[k].(int16)
|
||||
return v
|
||||
}
|
||||
|
||||
// Int32 reads an int32 value from a map at key k.
|
||||
func Int32(m map[string]any, k string) int32 {
|
||||
v, _ := m[k].(int32)
|
||||
return v
|
||||
}
|
||||
|
||||
// Int64 reads an int16 value from a map at key k.
|
||||
func Int64(m map[string]any, k string) int64 {
|
||||
v, _ := m[k].(int64)
|
||||
return v
|
||||
}
|
||||
|
||||
// TickDuration reads a uint8/int16/in32 value from a map at key k and converts
|
||||
// it from ticks to a time.Duration.
|
||||
func TickDuration[T constraints.Integer](m map[string]any, k string) time.Duration {
|
||||
var v time.Duration
|
||||
switch any(*new(T)).(type) {
|
||||
case uint8:
|
||||
v = time.Duration(Uint8(m, k))
|
||||
case int16:
|
||||
v = time.Duration(Int16(m, k))
|
||||
case int32:
|
||||
v = time.Duration(Int32(m, k))
|
||||
default:
|
||||
panic("invalid tick duration value type")
|
||||
}
|
||||
return v * time.Millisecond * 50
|
||||
}
|
||||
|
||||
// Float32 reads a float32 value from a map at key k.
|
||||
func Float32(m map[string]any, k string) float32 {
|
||||
v, _ := m[k].(float32)
|
||||
return v
|
||||
}
|
||||
|
||||
// Float64 reads a float64 value from a map at key k.
|
||||
func Float64(m map[string]any, k string) float64 {
|
||||
v, _ := m[k].(float64)
|
||||
return v
|
||||
}
|
||||
|
||||
// Slice reads a []any value from a map at key k.
|
||||
func Slice(m map[string]any, k string) []any {
|
||||
v, _ := m[k].([]any)
|
||||
return v
|
||||
}
|
||||
|
||||
// Vec3 converts x, y and z values in an NBT map to an mgl64.Vec3.
|
||||
func Vec3(x map[string]any, k string) mgl64.Vec3 {
|
||||
if i, ok := x[k].([]any); ok {
|
||||
if len(i) != 3 {
|
||||
return mgl64.Vec3{}
|
||||
}
|
||||
var v mgl64.Vec3
|
||||
for index, f := range i {
|
||||
f32, _ := f.(float32)
|
||||
v[index] = float64(f32)
|
||||
}
|
||||
return v
|
||||
} else if i, ok := x[k].([]float32); ok {
|
||||
if len(i) != 3 {
|
||||
return mgl64.Vec3{}
|
||||
}
|
||||
return mgl64.Vec3{float64(i[0]), float64(i[1]), float64(i[2])}
|
||||
}
|
||||
return mgl64.Vec3{}
|
||||
}
|
||||
|
||||
// Vec3ToFloat32Slice converts an mgl64.Vec3 to a []float32 with 3 elements.
|
||||
func Vec3ToFloat32Slice(x mgl64.Vec3) []float32 {
|
||||
return []float32{float32(x[0]), float32(x[1]), float32(x[2])}
|
||||
}
|
||||
|
||||
// Pos converts x, y and z values in an NBT map to a cube.Pos.
|
||||
func Pos(x map[string]any, k string) cube.Pos {
|
||||
if i, ok := x[k].([]any); ok {
|
||||
if len(i) != 3 {
|
||||
return cube.Pos{}
|
||||
}
|
||||
var v cube.Pos
|
||||
for index, f := range i {
|
||||
f32, _ := f.(int32)
|
||||
v[index] = int(f32)
|
||||
}
|
||||
return v
|
||||
} else if i, ok := x[k].([]int32); ok {
|
||||
if len(i) != 3 {
|
||||
return cube.Pos{}
|
||||
}
|
||||
return cube.Pos{int(i[0]), int(i[1]), int(i[2])}
|
||||
}
|
||||
return cube.Pos{}
|
||||
}
|
||||
|
||||
// PosToInt32Slice converts a cube.Pos to a []int32 with 3 elements.
|
||||
func PosToInt32Slice(x cube.Pos) []int32 {
|
||||
return []int32{int32(x[0]), int32(x[1]), int32(x[2])}
|
||||
}
|
||||
|
||||
// MapItem converts an item's name, count, damage (and properties when it is a block) in a map obtained by decoding NBT
|
||||
// to a world.Item.
|
||||
func MapItem(x map[string]any, k string) item.Stack {
|
||||
if m, ok := x[k].(map[string]any); ok {
|
||||
tag, ok := m["tag"].(map[string]any)
|
||||
if !ok {
|
||||
tag = map[string]any{}
|
||||
}
|
||||
|
||||
s := readItemStack(m, tag)
|
||||
readDamage(tag, &s, true)
|
||||
readEnchantments(tag, &s)
|
||||
readDisplay(tag, &s)
|
||||
readDragonflyData(tag, &s)
|
||||
return s
|
||||
}
|
||||
return item.Stack{}
|
||||
}
|
||||
|
||||
// Item decodes the data of an item into an item stack.
|
||||
func Item(data map[string]any, s *item.Stack) item.Stack {
|
||||
disk, tag := s == nil, data
|
||||
if disk {
|
||||
a := readItemStack(data)
|
||||
t, ok := data["tag"].(map[string]any)
|
||||
if !ok {
|
||||
t = map[string]any{}
|
||||
}
|
||||
tag = t
|
||||
|
||||
a := readItemStack(data, tag)
|
||||
s = &a
|
||||
}
|
||||
readDamage(data, s, disk)
|
||||
readAnvilCost(data, s)
|
||||
readDisplay(data, s)
|
||||
readEnchantments(data, s)
|
||||
readDragonflyData(data, s)
|
||||
|
||||
readAnvilCost(tag, s)
|
||||
readDamage(tag, s, disk)
|
||||
readDisplay(tag, s)
|
||||
readDragonflyData(tag, s)
|
||||
readEnchantments(tag, s)
|
||||
return *s
|
||||
}
|
||||
|
||||
// ReadBlock decodes the data of a block into a world.Block.
|
||||
func ReadBlock(m map[string]any) world.Block {
|
||||
name, _ := m["name"].(string)
|
||||
properties, _ := m["states"].(map[string]any)
|
||||
b, _ := world.BlockByName(name, properties)
|
||||
return b
|
||||
// Block decodes the data of a block into a world.Block.
|
||||
func Block(m map[string]any, k string) world.Block {
|
||||
if mk, ok := m[k].(map[string]any); ok {
|
||||
name, _ := mk["name"].(string)
|
||||
properties, _ := mk["states"].(map[string]any)
|
||||
b, _ := world.BlockByName(name, properties)
|
||||
return b
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// readItemStack reads an item.Stack from the NBT in the map passed.
|
||||
func readItemStack(m map[string]any) item.Stack {
|
||||
func readItemStack(m, t map[string]any) item.Stack {
|
||||
var it world.Item
|
||||
if blockItem, ok := MapBlock(m, "Block").(world.Item); ok {
|
||||
if blockItem, ok := Block(m, "Block").(world.Item); ok {
|
||||
it = blockItem
|
||||
}
|
||||
if v, ok := world.ItemByName(Map[string](m, "Name"), Map[int16](m, "Damage")); ok {
|
||||
if v, ok := world.ItemByName(String(m, "Name"), Int16(m, "Damage")); ok {
|
||||
it = v
|
||||
}
|
||||
if it == nil {
|
||||
return item.Stack{}
|
||||
}
|
||||
if n, ok := it.(world.NBTer); ok {
|
||||
it = n.DecodeNBT(m).(world.Item)
|
||||
it = n.DecodeNBT(t).(world.Item)
|
||||
}
|
||||
return item.NewStack(it, int(Map[byte](m, "Count")))
|
||||
return item.NewStack(it, int(Uint8(m, "Count")))
|
||||
}
|
||||
|
||||
// readDamage reads the damage value stored in the NBT with the Damage tag and saves it to the item.Stack passed.
|
||||
func readDamage(m map[string]any, s *item.Stack, disk bool) {
|
||||
if disk {
|
||||
*s = s.Damage(int(Map[int16](m, "Damage")))
|
||||
*s = s.Damage(int(Int16(m, "Damage")))
|
||||
return
|
||||
}
|
||||
*s = s.Damage(int(Map[int32](m, "Damage")))
|
||||
*s = s.Damage(int(Int32(m, "Damage")))
|
||||
}
|
||||
|
||||
// readAnvilCost ...
|
||||
func readAnvilCost(m map[string]any, s *item.Stack) {
|
||||
*s = s.WithAnvilCost(int(Map[int32](m, "RepairCost")))
|
||||
*s = s.WithAnvilCost(int(Int32(m, "RepairCost")))
|
||||
}
|
||||
|
||||
// readEnchantments reads the enchantments stored in the ench tag of the NBT passed and stores it into an item.Stack.
|
||||
func readEnchantments(m map[string]any, s *item.Stack) {
|
||||
enchantments, ok := m["ench"].([]map[string]any)
|
||||
if !ok {
|
||||
for _, e := range Map[[]any](m, "ench") {
|
||||
for _, e := range Slice(m, "ench") {
|
||||
if v, ok := e.(map[string]any); ok {
|
||||
enchantments = append(enchantments, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, ench := range enchantments {
|
||||
if t, ok := item.EnchantmentByID(int(Map[int16](ench, "id"))); ok {
|
||||
*s = s.WithEnchantments(item.NewEnchantment(t, int(Map[int16](ench, "lvl"))))
|
||||
if t, ok := item.EnchantmentByID(int(Int16(ench, "id"))); ok {
|
||||
*s = s.WithEnchantments(item.NewEnchantment(t, int(Int16(ench, "lvl"))))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,21 +11,27 @@ import (
|
|||
|
||||
// WriteItem encodes an item stack into a map that can be encoded using NBT.
|
||||
func WriteItem(s item.Stack, disk bool) map[string]any {
|
||||
m := make(map[string]any)
|
||||
tag := make(map[string]any)
|
||||
if nbt, ok := s.Item().(world.NBTer); ok {
|
||||
for k, v := range nbt.EncodeNBT() {
|
||||
m[k] = v
|
||||
tag[k] = v
|
||||
}
|
||||
}
|
||||
writeAnvilCost(tag, s)
|
||||
writeDamage(tag, s, disk)
|
||||
writeDisplay(tag, s)
|
||||
writeDragonflyData(tag, s)
|
||||
writeEnchantments(tag, s)
|
||||
|
||||
data := make(map[string]any)
|
||||
if disk {
|
||||
writeItemStack(m, s)
|
||||
writeItemStack(data, tag, s)
|
||||
} else {
|
||||
for k, v := range tag {
|
||||
data[k] = v
|
||||
}
|
||||
}
|
||||
writeDamage(m, s, disk)
|
||||
writeAnvilCost(m, s)
|
||||
writeDisplay(m, s)
|
||||
writeEnchantments(m, s)
|
||||
writeDragonflyData(m, s)
|
||||
return m
|
||||
return data
|
||||
}
|
||||
|
||||
// WriteBlock encodes a world.Block into a map that can be encoded using NBT.
|
||||
|
@ -39,7 +45,7 @@ func WriteBlock(b world.Block) map[string]any {
|
|||
}
|
||||
|
||||
// writeItemStack writes the name, metadata value, count and NBT of an item to a map ready for NBT encoding.
|
||||
func writeItemStack(m map[string]any, s item.Stack) {
|
||||
func writeItemStack(m, t map[string]any, s item.Stack) {
|
||||
m["Name"], m["Damage"] = s.Item().EncodeItem()
|
||||
if b, ok := s.Item().(world.Block); ok {
|
||||
v := map[string]any{}
|
||||
|
@ -47,6 +53,9 @@ func writeItemStack(m map[string]any, s item.Stack) {
|
|||
m["Block"] = v
|
||||
}
|
||||
m["Count"] = byte(s.Count())
|
||||
if len(t) > 0 {
|
||||
m["tag"] = t
|
||||
}
|
||||
}
|
||||
|
||||
// writeBlock writes the name, properties and version of a block to a map ready for NBT encoding.
|
||||
|
|
17
utils/net.go
17
utils/net.go
|
@ -1,8 +1,12 @@
|
|||
package utils
|
||||
|
||||
import "net"
|
||||
import (
|
||||
"net"
|
||||
|
||||
var PrivateIPNetworks = []net.IPNet{
|
||||
"github.com/repeale/fp-go"
|
||||
)
|
||||
|
||||
var privateIPNetworks = []net.IPNet{
|
||||
{
|
||||
IP: net.ParseIP("10.0.0.0"),
|
||||
Mask: net.CIDRMask(8, 32),
|
||||
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
200
utils/proxy.go
200
utils/proxy.go
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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) {
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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:]
|
||||
|
|
Loading…
Reference in New Issue