initial commit

This commit is contained in:
AkmalFairuz
2024-12-31 20:57:21 +07:00
commit 0de5415f05
72 changed files with 20340 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
.idea

4
config.toml Normal file
View File

@@ -0,0 +1,4 @@
[Connection]
LocalAddress = "0.0.0.0:19132"
RemoteAddress = "play.venitymc.com:19132"

31
go.mod Normal file
View File

@@ -0,0 +1,31 @@
module github.com/akmalfairuz/legacy-version
go 1.23.3
require (
github.com/brentp/intintmap v0.0.0-20190211203843-30dc0ade9af9 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/df-mc/dragonfly v0.9.20-0.20241229163702-cc7e4ee0e3ce // indirect
github.com/df-mc/goleveldb v1.1.9 // indirect
github.com/df-mc/worldupgrader v1.0.18 // indirect
github.com/go-gl/mathgl v1.2.0 // indirect
github.com/go-jose/go-jose/v3 v3.0.3 // indirect
github.com/go-jose/go-jose/v4 v4.0.4 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/klauspost/compress v1.17.11 // indirect
github.com/muhammadmuzzammil1998/jsonc v1.0.0 // indirect
github.com/pelletier/go-toml v1.9.5 // indirect
github.com/rogpeppe/go-internal v1.13.1 // indirect
github.com/samber/lo v1.47.0 // indirect
github.com/sandertv/go-raknet v1.14.2 // indirect
github.com/sandertv/gophertunnel v1.43.1 // indirect
github.com/segmentio/fasthash v1.0.3 // indirect
golang.org/x/crypto v0.31.0 // indirect
golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 // indirect
golang.org/x/image v0.21.0 // indirect
golang.org/x/mod v0.22.0 // indirect
golang.org/x/net v0.33.0 // indirect
golang.org/x/oauth2 v0.24.0 // indirect
golang.org/x/text v0.21.0 // indirect
)

124
go.sum Normal file
View File

@@ -0,0 +1,124 @@
github.com/brentp/intintmap v0.0.0-20190211203843-30dc0ade9af9 h1:/G0ghZwrhou0Wq21qc1vXXMm/t/aKWkALWwITptKbE0=
github.com/brentp/intintmap v0.0.0-20190211203843-30dc0ade9af9/go.mod h1:TOk10ahXejq9wkEaym3KPRNeuR/h5Jx+s8QRWIa2oTM=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/df-mc/dragonfly v0.9.20-0.20241229163702-cc7e4ee0e3ce h1:mhBn/ezojzACPHeg+Y9NnU2bbYCozx27RgpfWUPw4i0=
github.com/df-mc/dragonfly v0.9.20-0.20241229163702-cc7e4ee0e3ce/go.mod h1:hH1eU9lmucLNLehzxXzOUOmHJQLz3DLpQMUqIKcz8YI=
github.com/df-mc/goleveldb v1.1.9 h1:ihdosZyy5jkQKrxucTQmN90jq/2lUwQnJZjIYIC/9YU=
github.com/df-mc/goleveldb v1.1.9/go.mod h1:+NHCup03Sci5q84APIA21z3iPZCuk6m6ABtg4nANCSk=
github.com/df-mc/worldupgrader v1.0.18 h1:Q34X9ID/hGuDyj9oiq+dpyjOaJdNxhVmVUepf/EPYDA=
github.com/df-mc/worldupgrader v1.0.18/go.mod h1:tsSOLTRm9mpG7VHvYpAjjZrkRHWmSbKZAm9bOLNnlDk=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/go-gl/mathgl v1.1.0 h1:0lzZ+rntPX3/oGrDzYGdowSLC2ky8Osirvf5uAwfIEA=
github.com/go-gl/mathgl v1.1.0/go.mod h1:yhpkQzEiH9yPyxDUGzkmgScbaBVlhC06qodikEM0ZwQ=
github.com/go-gl/mathgl v1.2.0 h1:v2eOj/y1B2afDxF6URV1qCYmo1KW08lAMtTbOn3KXCY=
github.com/go-gl/mathgl v1.2.0/go.mod h1:pf9+b5J3LFP7iZ4XXaVzZrCle0Q/vNpB/vDe5+3ulRE=
github.com/go-jose/go-jose/v3 v3.0.3 h1:fFKWeig/irsp7XD2zBxvnmA/XaRWp5V3CBsZXJF7G7k=
github.com/go-jose/go-jose/v3 v3.0.3/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ=
github.com/go-jose/go-jose/v4 v4.0.4 h1:VsjPI33J0SB9vQM6PLmNjoHqMQNGPiZ0rHL7Ni7Q6/E=
github.com/go-jose/go-jose/v4 v4.0.4/go.mod h1:NKb5HO1EZccyMpiZNbdUw/14tiXNyUJh188dfnMCAfc=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
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.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
github.com/muhammadmuzzammil1998/jsonc v1.0.0 h1:8o5gBQn4ZA3NBA9DlTujCj2a4w0tqWrPVjDwhzkgTIs=
github.com/muhammadmuzzammil1998/jsonc v1.0.0/go.mod h1:saF2fIVw4banK0H4+/EuqfFLpRnoy5S+ECwTOCcRcSU=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc=
github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU=
github.com/sandertv/go-raknet v1.14.2 h1:UZLyHn5yQU2Dq2GVq/LlxwAUikaq4q4AA1rl/Pf3AXQ=
github.com/sandertv/go-raknet v1.14.2/go.mod h1:/yysjwfCXm2+2OY8mBazLzcxJ3irnylKCyG3FLgUPVU=
github.com/sandertv/gophertunnel v1.43.0 h1:zoB0nngGXokBlj+ge4ISvbInEd/1xbrIlclC/POCTgc=
github.com/sandertv/gophertunnel v1.43.0/go.mod h1:krvLSeRUNQ2iEYJNEgzrKtWO8W5ybZxN5lFfSCkHoNk=
github.com/sandertv/gophertunnel v1.43.1-0.20241229203958-5af8736ae319 h1:yxDqIvO9BkUFtK2SpnngPDnPQ9hnhq3IGZ6qiO2rFHg=
github.com/sandertv/gophertunnel v1.43.1-0.20241229203958-5af8736ae319/go.mod h1:XuEJo+ARim+NKiD90Z56sQRcDtCOErz26e2bt3LEd9I=
github.com/sandertv/gophertunnel v1.43.1 h1:wY6Fy8dRMKtpZUQzCR35o9k05135vOJZhrz0GF6OXFI=
github.com/sandertv/gophertunnel v1.43.1/go.mod h1:XuEJo+ARim+NKiD90Z56sQRcDtCOErz26e2bt3LEd9I=
github.com/segmentio/fasthash v1.0.3 h1:EI9+KE1EwvMLBWwjpRDc+fEM+prwxDYbslddQGtrmhM=
github.com/segmentio/fasthash v1.0.3/go.mod h1:waKX8l2N8yckOgmSsXJi7x1ZfdKZ4x7KRMzBtS3oedY=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 h1:1UoZQm6f0P/ZO0w1Ri+f+ifG/gXhegadRdwBIXEFWDo=
golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c=
golang.org/x/image v0.0.0-20190321063152-3fc05d484e9f/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.21.0 h1:c5qV36ajHpdj4Qi0GnE0jUc/yuo33OLFaa0d+crTD5s=
golang.org/x/image v0.21.0/go.mod h1:vUbsLavqK/W303ZroQQVKQ+Af3Yl6Uz1Ppu5J/cLz78=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs=
golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE=
golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
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-20201119102817-f84b799fce68/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=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
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.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

53
internal/block_states.go Normal file
View File

@@ -0,0 +1,53 @@
package internal
import (
"fmt"
"github.com/df-mc/worldupgrader/blockupgrader"
"sort"
"strings"
"unsafe"
)
// StateHash is a struct that may be used as a map key for block states. It contains the name of the block state
// and an encoded version of the properties.
type StateHash struct {
Name, Properties string
}
// HashState produces a hash for the block properties held by the blockState.
func HashState(state blockupgrader.BlockState) StateHash {
if state.Properties == nil {
return StateHash{Name: state.Name}
}
keys := make([]string, 0, len(state.Properties))
for k := range state.Properties {
keys = append(keys, k)
}
sort.Slice(keys, func(i, j int) bool {
return keys[i] < keys[j]
})
var b strings.Builder
for _, k := range keys {
switch v := state.Properties[k].(type) {
case bool:
if v {
b.WriteByte(1)
} else {
b.WriteByte(0)
}
case uint8:
b.WriteByte(v)
case int32:
a := *(*[4]byte)(unsafe.Pointer(&v))
b.Write(a[:])
case string:
b.WriteString(v)
default:
// If block encoding is broken, we want to find out as soon as possible. This saves a lot of time
// debugging in-game.
panic(fmt.Sprintf("invalid block property type %T for property %v", v, k))
}
}
return StateHash{Name: state.Name, Properties: b.String()}
}

119
internal/chunk/chunk.go Normal file
View File

@@ -0,0 +1,119 @@
package chunk
import (
"github.com/df-mc/dragonfly/server/block/cube"
"sync"
)
// Chunk is a segment in the world with a size of 16x16x256 blocks. A chunk contains multiple sub chunks
// and stores other information such as biomes.
// It is not safe to call methods on Chunk simultaneously from multiple goroutines.
type Chunk struct {
sync.Mutex
// r holds the (vertical) range of the Chunk. It includes both the minimum and maximum coordinates.
r cube.Range
// air is the runtime ID of air.
air uint32
// sub holds all sub chunks part of the chunk. The pointers held by the array are nil if no sub chunk is
// allocated at the indices.
sub []*SubChunk
// biomes is an array of biome IDs. There is one biome ID for every column in the chunk.
biomes []*PalettedStorage
}
// New initialises a new chunk and returns it, so that it may be used.
func New(air uint32, r cube.Range) *Chunk {
n := (r.Height() >> 4) + 1
sub, biomes := make([]*SubChunk, n), make([]*PalettedStorage, n)
for i := 0; i < n; i++ {
sub[i] = NewSubChunk(air)
biomes[i] = emptyStorage(0)
}
return &Chunk{r: r, air: air, sub: sub, biomes: biomes}
}
// Range returns the cube.Range of the Chunk as passed to New.
func (chunk *Chunk) Range() cube.Range {
return chunk.r
}
// Sub returns a list of all sub chunks present in the chunk.
func (chunk *Chunk) Sub() []*SubChunk {
return chunk.sub
}
// Block returns the runtime ID of the block at a given x, y and z in a chunk at the given layer. If no
// sub chunk exists at the given y, the block is assumed to be air.
func (chunk *Chunk) Block(x uint8, y int16, z uint8, layer uint8) uint32 {
sub := chunk.subChunk(y)
if sub.Empty() || uint8(len(sub.storages)) <= layer {
return chunk.air
}
return sub.storages[layer].At(x, uint8(y), z)
}
// SetBlock sets the runtime ID of a block at a given x, y and z in a chunk at the given layer. If no
// SubChunk exists at the given y, a new SubChunk is created and the block is set.
func (chunk *Chunk) SetBlock(x uint8, y int16, z uint8, layer uint8, block uint32) {
sub := chunk.sub[chunk.subIndex(y)]
if uint8(len(sub.storages)) <= layer && block == chunk.air {
// Air was set at n layer, but there were less than n layers, so there already was air there.
// Don't do anything with this, just return.
return
}
sub.Layer(layer).Set(x, uint8(y), z, block)
}
// BiomeSub returns a list of all biome sub chunks present in the chunk.
func (chunk *Chunk) BiomeSub() []*PalettedStorage {
return chunk.biomes
}
// Biome returns the biome ID at a specific column in the chunk.
func (chunk *Chunk) Biome(x uint8, y int16, z uint8) uint32 {
return chunk.biomes[chunk.subIndex(y)].At(x, uint8(y), z)
}
// SetBiome sets the biome ID at a specific column in the chunk.
func (chunk *Chunk) SetBiome(x uint8, y int16, z uint8, biome uint32) {
chunk.biomes[chunk.subIndex(y)].Set(x, uint8(y), z, biome)
}
// HighestBlock iterates from the highest non-empty sub chunk downwards to find the Y value of the highest
// non-air block at an x and z. If no blocks are present in the column, 0 is returned.
func (chunk *Chunk) HighestBlock(x, z uint8) int16 {
for index := int16(len(chunk.sub) - 1); index >= 0; index-- {
if sub := chunk.sub[index]; !sub.Empty() {
for y := 15; y >= 0; y-- {
if rid := sub.storages[0].At(x, uint8(y), z); rid != chunk.air {
return int16(y) | chunk.subY(index)
}
}
}
}
return int16(chunk.r[0])
}
// Compact compacts the chunk as much as possible, getting rid of any sub chunks that are empty, and compacts
// all storages in the sub chunks to occupy as little space as possible.
// Compact should be called right before the chunk is saved in order to optimise the storage space.
func (chunk *Chunk) Compact() {
for i := range chunk.sub {
chunk.sub[i].compact()
}
}
// subChunk finds the correct SubChunk in the Chunk by a Y value.
func (chunk *Chunk) subChunk(y int16) *SubChunk {
return chunk.sub[chunk.subIndex(y)]
}
// subIndex returns the sub chunk Y index matching the y value passed.
func (chunk *Chunk) subIndex(y int16) int16 {
return (y - int16(chunk.r[0])) >> 4
}
// subY returns the sub chunk Y value matching the index passed.
func (chunk *Chunk) subY(index int16) int16 {
return (index << 4) + int16(chunk.r[0])
}

145
internal/chunk/decode.go Normal file
View File

@@ -0,0 +1,145 @@
package chunk
import (
"bytes"
"fmt"
"github.com/df-mc/dragonfly/server/block/cube"
)
// NetworkDecode decodes the network serialised data passed into a Chunk if successful. If not, the chunk
// returned is nil and the error non-nil.
// The sub chunk count passed must be that found in the LevelChunk packet.
// noinspection GoUnusedExportedFunction
func NetworkDecode(air uint32, buf *bytes.Buffer, count int, oldFormat bool, r cube.Range, pse Encoding, pe PaletteEncoding) (*Chunk, error) {
var (
c = New(air, r)
err error
)
for i := 0; i < count; i++ {
index := uint8(i)
if oldFormat {
index += 4
}
c.sub[index], err = DecodeSubChunk(air, r, buf, &index, NetworkEncoding, pse, pe)
if err != nil {
return nil, err
}
}
if oldFormat {
// Read the old biomes.
biomes := make([]byte, 256)
if _, err := buf.Read(biomes); err != nil {
return nil, fmt.Errorf("error reading biomes: %w", err)
}
// Make our 2D biomes 3D.
for x := 0; x < 16; x++ {
for z := 0; z < 16; z++ {
id := biomes[(x&15)|(z&15)<<4]
for y := r.Min(); y <= r.Max(); y++ {
c.SetBiome(uint8(x), int16(y), uint8(z), uint32(id))
}
}
}
} else {
var last *PalettedStorage
for i := 0; i < len(c.sub); i++ {
b, err := decodePalettedStorage(buf, NetworkEncoding, pse, BiomePaletteEncoding)
if err != nil {
return nil, err
}
if b == nil {
// b == nil means this paletted storage had the flag pointing to the previous one. It basically means we should
// inherit whatever palette we decoded last.
if i == 0 {
// This should never happen and there is no way to handle this.
return nil, fmt.Errorf("first biome storage pointed to previous one")
}
b = last
} else {
last = b
}
c.biomes[i] = b
}
}
return c, nil
}
// DecodeSubChunk decodes a SubChunk from a bytes.Buffer. The Encoding passed defines how the block storages of the
// SubChunk are decoded.
func DecodeSubChunk(air uint32, r cube.Range, buf *bytes.Buffer, index *byte, e Encoding, pse Encoding, pe PaletteEncoding) (*SubChunk, error) {
ver, err := buf.ReadByte()
if err != nil {
return nil, fmt.Errorf("error reading version: %w", err)
}
sub := NewSubChunk(air)
switch ver {
default:
return nil, fmt.Errorf("unknown sub chunk version %v: can't decode", ver)
case 1:
// Version 1 only has one layer for each sub chunk, but uses the format with palettes.
storage, err := decodePalettedStorage(buf, e, pse, pe)
if err != nil {
return nil, err
}
sub.storages = append(sub.storages, storage)
case 8, 9:
// Version 8 allows up to 256 layers for one sub chunk.
storageCount, err := buf.ReadByte()
if err != nil {
return nil, fmt.Errorf("error reading storage count: %w", err)
}
if ver == 9 {
uIndex, err := buf.ReadByte()
if err != nil {
return nil, fmt.Errorf("error reading subchunk index: %w", err)
}
// The index as written here isn't the actual index of the subchunk within the chunk. Rather, it is the Y
// value of the subchunk. This means that we need to translate it to an index.
*index = uint8(int8(uIndex) - int8(r[0]>>4))
}
sub.storages = make([]*PalettedStorage, storageCount)
for i := byte(0); i < storageCount; i++ {
sub.storages[i], err = decodePalettedStorage(buf, e, pse, pe)
if err != nil {
return nil, err
}
}
}
return sub, nil
}
// decodePalettedStorage decodes a PalettedStorage from a bytes.Buffer. The Encoding passed is used to read either a
// network or disk block storage.
func decodePalettedStorage(buf *bytes.Buffer, e Encoding, pse Encoding, pe PaletteEncoding) (*PalettedStorage, error) {
blockSize, err := buf.ReadByte()
if err != nil {
return nil, fmt.Errorf("error reading block size: %w", err)
}
if e == NetworkEncoding && blockSize&1 != 1 {
e = pse
}
blockSize >>= 1
if blockSize == 0x7f {
return nil, nil
}
size := paletteSize(blockSize)
uint32Count := size.uint32s()
uint32s := make([]uint32, uint32Count)
byteCount := uint32Count * 4
data := buf.Next(byteCount)
if len(data) != byteCount {
return nil, fmt.Errorf("cannot read paletted storage (size=%v) %T: not enough block data present: expected %v bytes, got %v", blockSize, pe, byteCount, len(data))
}
for i := 0; i < uint32Count; i++ {
// Explicitly don't use the binary package to greatly improve performance of reading the uint32s.
uint32s[i] = uint32(data[i*4]) | uint32(data[i*4+1])<<8 | uint32(data[i*4+2])<<16 | uint32(data[i*4+3])<<24
}
p, err := e.DecodePalette(buf, paletteSize(blockSize), pe)
return newPalettedStorage(uint32s, p), err
}

92
internal/chunk/encode.go Normal file
View File

@@ -0,0 +1,92 @@
package chunk
import (
"bytes"
"github.com/df-mc/dragonfly/server/block/cube"
"sync"
)
// pool is used to pool byte buffers used for encoding chunks.
var pool = sync.Pool{
New: func() any {
return bytes.NewBuffer(make([]byte, 0, 1024))
},
}
func NetworkEncode(air uint32, c *Chunk, oldFormat bool, pe PaletteEncoding) ([]byte, error) {
buf := pool.Get().(*bytes.Buffer)
for i := 0; i < len(c.sub); i++ {
_, _ = buf.Write(EncodeSubChunk(c.sub[i], NetworkEncoding, pe, SubChunkVersion8, c.r, i))
}
if oldFormat {
biomes := make([]byte, 256)
// Make our 3D biomes 2D.
for x := uint8(0); x < 16; x++ {
for z := uint8(0); z < 16; z++ {
biomes[(x&15)|(z&15)<<4] = byte(c.Biome(x, c.HighestBlock(x, z), z))
}
}
_, _ = buf.Write(biomes)
} else {
_, _ = buf.Write(EncodeBiomes(c, NetworkEncoding))
}
return buf.Bytes(), nil
}
// EncodeSubChunk encodes a sub-chunk from a chunk into bytes. An Encoding may be passed to encode either for network or
// disk purposed, the most notable difference being that the network encoding generally uses varints and no NBT.
func EncodeSubChunk(s *SubChunk, e Encoding, pe PaletteEncoding, subChunkVer subChunkVersion, r cube.Range, ind int) []byte {
buf := pool.Get().(*bytes.Buffer)
defer func() {
buf.Reset()
pool.Put(buf)
}()
subChunkVer.EncodeHeader(buf, s, r, ind)
for _, storage := range s.storages {
encodePalettedStorage(buf, storage, nil, e, pe)
}
sub := make([]byte, buf.Len())
_, _ = buf.Read(sub)
return sub
}
// EncodeBiomes encodes the biomes of a chunk into bytes. An Encoding may be passed to encode either for network or
// disk purposed, the most notable difference being that the network encoding generally uses varints and no NBT.
func EncodeBiomes(c *Chunk, e Encoding) []byte {
buf := pool.Get().(*bytes.Buffer)
defer func() {
buf.Reset()
pool.Put(buf)
}()
var previous *PalettedStorage
for _, b := range c.biomes {
encodePalettedStorage(buf, b, previous, e, BiomePaletteEncoding)
previous = b
}
biomes := make([]byte, buf.Len())
_, _ = buf.Read(biomes)
return biomes
}
// encodePalettedStorage encodes a PalettedStorage into a bytes.Buffer. The Encoding passed is used to write the Palette
// of the PalettedStorage.
func encodePalettedStorage(buf *bytes.Buffer, storage, previous *PalettedStorage, e Encoding, pe PaletteEncoding) {
if storage.Equal(previous) {
_, _ = buf.Write([]byte{0x7f<<1 | e.Network()})
return
}
b := make([]byte, len(storage.indices)*4+1)
b[0] = byte(storage.bitsPerIndex<<1) | e.Network()
for i, v := range storage.indices {
// Explicitly don't use the binary package to greatly improve performance of writing the uint32s.
b[i*4+1], b[i*4+2], b[i*4+3], b[i*4+4] = byte(v), byte(v>>8), byte(v>>16), byte(v>>24)
}
_, _ = buf.Write(b)
e.EncodePalette(buf, storage.palette, pe)
}

193
internal/chunk/encoding.go Normal file
View File

@@ -0,0 +1,193 @@
package chunk
import (
"bytes"
"encoding/binary"
"fmt"
"github.com/akmalfairuz/legacy-version/mapping"
"strings"
"github.com/df-mc/dragonfly/server/block/cube"
"github.com/df-mc/worldupgrader/blockupgrader"
"github.com/sandertv/gophertunnel/minecraft/nbt"
"github.com/sandertv/gophertunnel/minecraft/protocol"
)
const (
// SubChunkVersion is the current version of the written sub chunks, specifying the format they are
// written on disk and over network.
SubChunkVersion = 9
)
type (
// Encoding is an encoding type used for Chunk encoding. Implementations of this interface are DiskEncoding and
// NetworkEncoding, which can be used to encode a Chunk to an intermediate disk or network representation respectively.
Encoding interface {
EncodePalette(buf *bytes.Buffer, p *Palette, e PaletteEncoding)
DecodePalette(buf *bytes.Buffer, blockSize paletteSize, e PaletteEncoding) (*Palette, error)
Network() byte
}
// PaletteEncoding is an encoding type used for Chunk encoding. It is used to encode different types of palettes
// (for example, blocks or biomes) differently.
PaletteEncoding interface {
Encode(buf *bytes.Buffer, v uint32)
Decode(buf *bytes.Buffer) (uint32, error)
}
// Encoding is an encoding type used for Chunk encoding. Implementations of this interface are DiskEncoding and
// NetworkEncoding, which can be used to encode a Chunk to an intermediate disk or network representation respectively.
subChunkVersion interface {
EncodeHeader(buf *bytes.Buffer, s *SubChunk, r cube.Range, ind int)
}
)
var (
// SubChunkVersion9 subChunkVersion9
SubChunkVersion9 subChunkVersion9
// SubChunkVersion8 subChunkVersion8
SubChunkVersion8 subChunkVersion8
// NetworkEncoding is the Encoding used for sending a Chunk over network. It does not use NBT and writes varints.
NetworkEncoding networkEncoding
// BiomePaletteEncoding is the paletteEncoding used for encoding a palette of biomes.
BiomePaletteEncoding biomePaletteEncoding
)
// networkEncoding implements the Chunk encoding for sending over network.
type networkEncoding struct{}
func (networkEncoding) Network() byte { return 1 }
func (networkEncoding) EncodePalette(buf *bytes.Buffer, p *Palette, _ PaletteEncoding) {
if p.size != 0 {
_ = protocol.WriteVarint32(buf, int32(p.Len()))
}
for _, val := range p.values {
_ = protocol.WriteVarint32(buf, int32(val))
}
}
func (networkEncoding) DecodePalette(buf *bytes.Buffer, blockSize paletteSize, _ PaletteEncoding) (*Palette, error) {
var paletteCount int32 = 1
if blockSize != 0 {
if err := protocol.Varint32(buf, &paletteCount); err != nil {
return nil, fmt.Errorf("error reading palette entry count: %w", err)
}
if paletteCount <= 0 {
return nil, fmt.Errorf("invalid palette entry count %v", paletteCount)
}
}
blocks, temp := make([]uint32, paletteCount), int32(0)
for i := int32(0); i < paletteCount; i++ {
if err := protocol.Varint32(buf, &temp); err != nil {
return nil, fmt.Errorf("error decoding palette entry: %w", err)
}
blocks[i] = uint32(temp)
}
return &Palette{values: blocks, size: blockSize}, nil
}
// biomePaletteEncoding implements the encoding of biome palettes to disk.
type biomePaletteEncoding struct{}
func (biomePaletteEncoding) Encode(buf *bytes.Buffer, v uint32) {
_ = binary.Write(buf, binary.LittleEndian, v)
}
func (biomePaletteEncoding) Decode(buf *bytes.Buffer) (uint32, error) {
var v uint32
return v, binary.Read(buf, binary.LittleEndian, &v)
}
// BlockPaletteEncoding implements the encoding of block palettes to disk.
type BlockPaletteEncoding struct {
block mapping.Block
version int32
}
// NewBlockPaletteEncoding returns a new BlockPaletteEncoding using the block and version passed.
func NewBlockPaletteEncoding(block mapping.Block, version int32) BlockPaletteEncoding {
return BlockPaletteEncoding{block: block, version: version}
}
func (b BlockPaletteEncoding) Encode(buf *bytes.Buffer, v uint32) {
// Get the block state registered with the runtime IDs we have in the palette of the block storage
// as we need the name and data value to store.
state, _ := b.block.RuntimeIDToState(v)
_ = nbt.NewEncoderWithEncoding(buf, nbt.LittleEndian).Encode(blockupgrader.BlockState{Name: state.Name, Properties: state.Properties, Version: b.version})
}
func (b BlockPaletteEncoding) Decode(buf *bytes.Buffer) (uint32, error) {
var e blockupgrader.BlockState
if err := nbt.NewDecoderWithEncoding(buf, nbt.LittleEndian).Decode(&e); err != nil {
return 0, fmt.Errorf("error decoding block palette entry: %w", err)
}
v, ok := b.block.StateToRuntimeID(e)
if !ok {
return 0, fmt.Errorf("cannot get runtime ID of block state %v{%+v}", e.Name, e.Properties)
}
return v, nil
}
// NewNetworkPersistentEncoding returns a new NetworkPersistentEncoding using the block and version passed.
func NewNetworkPersistentEncoding(block mapping.Block, version int32) NetworkPersistentEncoding {
return NetworkPersistentEncoding{block: block, version: version}
}
// NetworkPersistentEncoding implements the Chunk encoding for sending over network with a persistent palette.
type NetworkPersistentEncoding struct {
block mapping.Block
version int32
}
func (n NetworkPersistentEncoding) Network() byte { return 1 }
func (n NetworkPersistentEncoding) EncodePalette(buf *bytes.Buffer, p *Palette, _ PaletteEncoding) {
if p.size != 0 {
_ = protocol.WriteVarint32(buf, int32(p.Len()))
}
enc := nbt.NewEncoderWithEncoding(buf, nbt.NetworkLittleEndian)
for _, val := range p.values {
state, _ := n.block.RuntimeIDToState(val)
_ = enc.Encode(blockupgrader.BlockState{Name: strings.TrimPrefix("minecraft:", state.Name), Properties: state.Properties, Version: n.version})
}
}
func (n NetworkPersistentEncoding) DecodePalette(buf *bytes.Buffer, blockSize paletteSize, _ PaletteEncoding) (*Palette, error) {
var paletteCount int32 = 1
if blockSize != 0 {
err := protocol.Varint32(buf, &paletteCount)
if err != nil {
panic(err)
}
if paletteCount <= 0 {
return nil, fmt.Errorf("invalid palette entry count %v", paletteCount)
}
}
blocks := make([]blockupgrader.BlockState, paletteCount)
dec := nbt.NewDecoderWithEncoding(buf, nbt.NetworkLittleEndian)
for i := int32(0); i < paletteCount; i++ {
if err := dec.Decode(&blocks[i]); err != nil {
return nil, fmt.Errorf("error decoding block state: %w", err)
}
}
var ok bool
palette, temp := newPalette(blockSize, make([]uint32, paletteCount)), uint32(0)
for i, b := range blocks {
temp, ok = n.block.StateToRuntimeID(blockupgrader.BlockState{Name: "minecraft:" + b.Name, Properties: b.Properties, Version: n.version})
if !ok {
return nil, fmt.Errorf("cannot get runtime ID of block state %v{%+v}", b.Name, b.Properties)
}
palette.values[i] = temp
}
return palette, nil
}
type subChunkVersion8 struct{}
func (subChunkVersion8) EncodeHeader(buf *bytes.Buffer, s *SubChunk, _ cube.Range, _ int) {
_, _ = buf.Write([]byte{8, byte(len(s.storages))})
}
type subChunkVersion9 struct{}
func (subChunkVersion9) EncodeHeader(buf *bytes.Buffer, s *SubChunk, r cube.Range, ind int) {
_, _ = buf.Write([]byte{SubChunkVersion, byte(len(s.storages)), uint8(ind + (r[0] >> 4))})
}

131
internal/chunk/palette.go Normal file
View File

@@ -0,0 +1,131 @@
package chunk
import (
"math"
)
// paletteSize is the size of a palette. It indicates the amount of bits occupied per value stored.
type paletteSize byte
// Palette is a palette of values that every PalettedStorage has. Storages hold 'pointers' to indices
// in this palette.
type Palette struct {
last uint32
lastIndex int16
size paletteSize
// values is a map of values. A PalettedStorage points to the index to this value.
values []uint32
}
// newPalette returns a new Palette with size and a slice of added values.
func newPalette(size paletteSize, values []uint32) *Palette {
return &Palette{size: size, values: values, last: math.MaxUint32}
}
// Len returns the amount of unique values in the Palette.
func (palette *Palette) Len() int {
return len(palette.values)
}
// Add adds a values to the Palette. It does not first check if the value was already set in the Palette.
// The index at which the value was added is returned. Another bool is returned indicating if the Palette
// was resized as a result of adding the value.
func (palette *Palette) Add(v uint32) (index int16, resize bool) {
i := int16(len(palette.values))
palette.values = append(palette.values, v)
if palette.needsResize() {
palette.increaseSize()
return i, true
}
return i, false
}
// Replace calls the function passed for each value present in the Palette. The value returned by the
// function replaces the value present at the index of the value passed.
func (palette *Palette) Replace(f func(v uint32) uint32) {
// Reset last runtime ID as it now has a different offset.
palette.last = math.MaxUint32
for index, v := range palette.values {
palette.values[index] = f(v)
}
}
// Index loops through the values of the Palette and looks for the index of the given value. If the value could
// not be found, -1 is returned.
func (palette *Palette) Index(runtimeID uint32) int16 {
if runtimeID == palette.last {
// Fast path out.
return palette.lastIndex
}
// Slow path in a separate function allows for inlining the fast path.
return palette.indexSlow(runtimeID)
}
// indexSlow searches the index of a value in the Palette's values by iterating through the Palette's values.
func (palette *Palette) indexSlow(runtimeID uint32) int16 {
l := len(palette.values)
for i := 0; i < l; i++ {
if palette.values[i] == runtimeID {
palette.last = runtimeID
v := int16(i)
palette.lastIndex = v
return v
}
}
return -1
}
// Value returns the value in the Palette at a specific index.
func (palette *Palette) Value(i uint16) uint32 {
return palette.values[i]
}
// needsResize checks if the Palette, and with it the holding PalettedStorage, needs to be resized to a bigger
// size.
func (palette *Palette) needsResize() bool {
return len(palette.values) > (1 << palette.size)
}
var sizes = [...]paletteSize{0, 1, 2, 3, 4, 5, 6, 8, 16}
var offsets = [...]int{0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 8: 7, 16: 8}
// increaseSize increases the size of the Palette to the next palette size.
func (palette *Palette) increaseSize() {
palette.size = sizes[offsets[palette.size]+1]
}
// padded returns true if the Palette size is 3, 5 or 6.
func (p paletteSize) padded() bool {
return p == 3 || p == 5 || p == 6
}
// paletteSizeFor finds a suitable paletteSize for the amount of values passed n.
func paletteSizeFor(n int) paletteSize {
for _, size := range sizes {
if n <= (1 << size) {
return size
}
}
// Should never happen.
return 0
}
// uint32s returns the amount of uint32s needed to represent a storage with this palette size.
func (p paletteSize) uint32s() (n int) {
uint32Count := 0
if p != 0 {
// indicesPerUint32 is the amount of indices that may be stored in a single uint32.
indicesPerUint32 := 32 / int(p)
// uint32Count is the amount of uint32s required to store all indices: 4096 indices need to be stored in
// total.
uint32Count = 4096 / indicesPerUint32
}
if p.padded() {
// We've got one of the padded sizes, so the storage has another uint32 to be able to store
// every index.
uint32Count++
}
return uint32Count
}

View File

@@ -0,0 +1,200 @@
package chunk
import (
"bytes"
"reflect"
"unsafe"
)
const (
// uint32ByteSize is the amount of bytes in a uint32.
uint32ByteSize = 4
// uint32BitSize is the amount of bits in a uint32.
uint32BitSize = uint32ByteSize * 8
)
// PalettedStorage is a storage of 4096 blocks encoded in a variable amount of uint32s, storages may have values
// with a bit size per block of 0, 1, 2, 3, 4, 5, 6, 8 or 16 bits.
// 3 of these formats have additional padding in every uint32 and an additional uint32 at the end, to cater
// for the blocks that don't fit. This padding is present when the storage has a block size of 3, 5 or 6
// bytes.
// Methods on PalettedStorage must not be called simultaneously from multiple goroutines.
type PalettedStorage struct {
// bitsPerIndex is the amount of bits required to store one block. The number increases as the block
// storage holds more unique block states.
bitsPerIndex uint16
// filledBitsPerIndex returns the amount of blocks that are actually filled per uint32.
filledBitsPerIndex uint16
// indexMask is the equivalent of 1 << bitsPerIndex - 1.
indexMask uint32
// indicesStart holds an unsafe.Pointer to the first byte in the indices slice below.
indicesStart unsafe.Pointer
// Palette holds all block runtime IDs that the indices in the indices slice point to. These runtime IDs
// point to block states.
palette *Palette
// indices contains all indices in the PalettedStorage. This slice has a variable size, but may not be changed
// unless the whole PalettedStorage is resized, including the Palette.
indices []uint32
}
// newPalettedStorage creates a new block storage using the uint32 slice as the indices and the palette passed.
// The bits per block are calculated using the length of the uint32 slice.
func newPalettedStorage(indices []uint32, palette *Palette) *PalettedStorage {
var (
bitsPerIndex = uint16(len(indices) / uint32BitSize / uint32ByteSize)
indexMask = (uint32(1) << bitsPerIndex) - 1
indicesStart = (unsafe.Pointer)((*reflect.SliceHeader)(unsafe.Pointer(&indices)).Data)
filledBitsPerIndex uint16
)
if bitsPerIndex != 0 {
filledBitsPerIndex = uint32BitSize / bitsPerIndex * bitsPerIndex
}
return &PalettedStorage{filledBitsPerIndex: filledBitsPerIndex, indexMask: indexMask, indicesStart: indicesStart, bitsPerIndex: bitsPerIndex, indices: indices, palette: palette}
}
// emptyStorage creates a PalettedStorage filled completely with a value v.
func emptyStorage(v uint32) *PalettedStorage {
return newPalettedStorage([]uint32{}, newPalette(0, []uint32{v}))
}
// Palette returns the Palette of the PalettedStorage.
func (storage *PalettedStorage) Palette() *Palette {
return storage.palette
}
// At returns the value of the PalettedStorage at a given x, y and z.
func (storage *PalettedStorage) At(x, y, z byte) uint32 {
return storage.palette.Value(storage.paletteIndex(x&15, y&15, z&15))
}
// Set sets a value at a specific x, y and z. The Palette and PalettedStorage are expanded
// automatically to make space for the value, should that be needed.
func (storage *PalettedStorage) Set(x, y, z byte, v uint32) {
index := storage.palette.Index(v)
if index == -1 {
// The runtime ID was not yet available in the palette. We add it, then check if the block storage
// needs to be resized for the palette pointers to fit.
index = storage.addNew(v)
}
storage.setPaletteIndex(x&15, y&15, z&15, uint16(index))
}
// Equal checks if two PalettedStorages are equal value wise. False is returned
// if either of the storages are nil.
func (storage *PalettedStorage) Equal(other *PalettedStorage) bool {
if storage == nil || other == nil {
return false
}
if len(storage.indices) == 0 || len(other.indices) == 0 || storage.palette.values[0] == 0 || other.palette.values[0] == 0 {
return false
}
indicesA := unsafe.Slice((*byte)(unsafe.Pointer(&storage.indices[0])), len(storage.indices)*4)
indicesB := unsafe.Slice((*byte)(unsafe.Pointer(&other.indices[0])), len(other.indices)*4)
if !bytes.Equal(indicesA, indicesB) {
return false
}
paletteA := unsafe.Slice((*byte)(unsafe.Pointer(&storage.palette.values[0])), len(storage.palette.values)*4)
paletteB := unsafe.Slice((*byte)(unsafe.Pointer(&other.palette.values[0])), len(other.palette.values)*4)
return bytes.Equal(paletteA, paletteB)
}
// addNew adds a new value to the PalettedStorage's Palette and returns its index. If needed, the storage is resized.
func (storage *PalettedStorage) addNew(v uint32) int16 {
index, resize := storage.palette.Add(v)
if resize {
storage.resize(storage.palette.size)
}
return index
}
// paletteIndex looks up the Palette index at a given x, y and z value in the PalettedStorage. This palette
// index is not the value at this offset, but merely an index in the Palette pointing to a value.
func (storage *PalettedStorage) paletteIndex(x, y, z byte) uint16 {
if storage.bitsPerIndex == 0 {
// Unfortunately our default logic cannot deal with 0 bits per index, meaning we'll have to special case
// this. This comes with a little performance hit, but it seems to be the only way to go. An alternative would
// be not to have 0 bits per block storages in memory, but that would cause a strongly increased memory usage
// by biomes.
return 0
}
offset := ((uint16(x) << 8) | (uint16(z) << 4) | uint16(y)) * storage.bitsPerIndex
uint32Offset, bitOffset := offset/storage.filledBitsPerIndex, offset%storage.filledBitsPerIndex
w := *(*uint32)(unsafe.Pointer(uintptr(storage.indicesStart) + uintptr(uint32Offset<<2)))
return uint16((w >> bitOffset) & storage.indexMask)
}
// setPaletteIndex sets the palette index at a given x, y and z to paletteIndex. This index should point
// to a value in the PalettedStorage's Palette.
func (storage *PalettedStorage) setPaletteIndex(x, y, z byte, i uint16) {
if storage.bitsPerIndex == 0 {
return
}
offset := ((uint16(x) << 8) | (uint16(z) << 4) | uint16(y)) * storage.bitsPerIndex
uint32Offset, bitOffset := offset/storage.filledBitsPerIndex, offset%storage.filledBitsPerIndex
ptr := (*uint32)(unsafe.Pointer(uintptr(storage.indicesStart) + uintptr(uint32Offset<<2)))
*ptr = (*ptr &^ (storage.indexMask << bitOffset)) | (uint32(i) << bitOffset)
}
// resize changes the size of a PalettedStorage to newPaletteSize. A new PalettedStorage is constructed,
// and all values available in the current storage are set in their appropriate locations in the
// new storage.
func (storage *PalettedStorage) resize(newPaletteSize paletteSize) {
if newPaletteSize == paletteSize(storage.bitsPerIndex) {
return // Don't resize if the size is already equal.
}
// Construct a new storage and set all values in there manually. We can't easily do this in a better
// way, because all values will be at a different index with a different length.
newStorage := newPalettedStorage(make([]uint32, newPaletteSize.uint32s()), storage.palette)
for x := byte(0); x < 16; x++ {
for y := byte(0); y < 16; y++ {
for z := byte(0); z < 16; z++ {
newStorage.setPaletteIndex(x, y, z, storage.paletteIndex(x, y, z))
}
}
}
// Set the new storage.
*storage = *newStorage
}
// compact clears unused indexes in the palette by scanning for usages in the PalettedStorage. This is a
// relatively heavy task which should only happen right before the sub chunk holding this PalettedStorage is
// saved to disk. compact also shrinks the palette size if possible.
func (storage *PalettedStorage) compact() {
usedIndices := make([]bool, storage.palette.Len())
for x := byte(0); x < 16; x++ {
for y := byte(0); y < 16; y++ {
for z := byte(0); z < 16; z++ {
usedIndices[storage.paletteIndex(x, y, z)] = true
}
}
}
newRuntimeIDs := make([]uint32, 0, len(usedIndices))
conversion := make([]uint16, len(usedIndices))
for index, set := range usedIndices {
if set {
conversion[index] = uint16(len(newRuntimeIDs))
newRuntimeIDs = append(newRuntimeIDs, storage.palette.values[index])
}
}
// Construct a new storage and set all values in there manually. We can't easily do this in a better
// way, because all values will be at a different index with a different length.
size := paletteSizeFor(len(newRuntimeIDs))
newStorage := newPalettedStorage(make([]uint32, size.uint32s()), newPalette(size, newRuntimeIDs))
for x := byte(0); x < 16; x++ {
for y := byte(0); y < 16; y++ {
for z := byte(0); z < 16; z++ {
// Replace all usages of the old palette indexes with the new indexes using the map we
// produced earlier.
newStorage.setPaletteIndex(x, y, z, conversion[storage.paletteIndex(x, y, z)])
}
}
}
*storage = *newStorage
}

View File

@@ -0,0 +1,66 @@
package chunk
// SubChunk is a cube of blocks located in a chunk. It has a size of 16x16x16 blocks and forms part of a stack
// that forms a Chunk.
type SubChunk struct {
air uint32
storages []*PalettedStorage
blockLight []uint8
skyLight []uint8
}
// NewSubChunk creates a new sub chunk. All sub chunks should be created through this function
func NewSubChunk(air uint32) *SubChunk {
return &SubChunk{air: air}
}
// Empty checks if the SubChunk is considered empty. This is the case if the SubChunk has 0 block storages or if it has
// a single one that is completely filled with air.
func (sub *SubChunk) Empty() bool {
return len(sub.storages) == 0 || (len(sub.storages) == 1 && len(sub.storages[0].palette.values) == 1 && sub.storages[0].palette.values[0] == sub.air)
}
// Layer returns a certain block storage/layer from a sub chunk. If no storage at the layer exists, the layer
// is created, as well as all layers between the current highest layer and the new highest layer.
func (sub *SubChunk) Layer(layer uint8) *PalettedStorage {
for uint8(len(sub.storages)) <= layer {
// Keep appending to storages until the requested layer is achieved. Makes working with new layers
// much easier.
sub.storages = append(sub.storages, emptyStorage(sub.air))
}
return sub.storages[layer]
}
// Layers returns all layers in the sub chunk. This method may also return an empty slice.
func (sub *SubChunk) Layers() []*PalettedStorage {
return sub.storages
}
// Block returns the runtime ID of the block located at the given X, Y and Z. X, Y and Z must be in a
// range of 0-15.
func (sub *SubChunk) Block(x, y, z byte, layer uint8) uint32 {
if uint8(len(sub.storages)) <= layer {
return sub.air
}
return sub.storages[layer].At(x, y, z)
}
// SetBlock sets the given block runtime ID at the given X, Y and Z. X, Y and Z must be in a range of 0-15.
func (sub *SubChunk) SetBlock(x, y, z byte, layer uint8, block uint32) {
sub.Layer(layer).Set(x, y, z, block)
}
// Compact cleans the garbage from all block storages that sub chunk contains, so that they may be
// cleanly written to a database.
func (sub *SubChunk) compact() {
newStorages := make([]*PalettedStorage, 0, len(sub.storages))
for _, storage := range sub.storages {
storage.compact()
if len(storage.palette.values) == 1 && storage.palette.values[0] == sub.air {
// If the palette has only air in it, it means the storage is empty, so we can ignore it.
continue
}
newStorages = append(newStorages, storage)
}
sub.storages = newStorages
}

View File

@@ -0,0 +1,481 @@
{
"minecraft:acacia_button": "minecraft:acacia_button",
"minecraft:acacia_fence_gate": "minecraft:acacia_fence_gate",
"minecraft:acacia_pressure_plate": "minecraft:acacia_pressure_plate",
"minecraft:acacia_stairs": "minecraft:acacia_stairs",
"minecraft:acacia_standing_sign": "minecraft:acacia_standing_sign",
"minecraft:acacia_trapdoor": "minecraft:acacia_trapdoor",
"minecraft:acacia_wall_sign": "minecraft:acacia_wall_sign",
"minecraft:activator_rail": "minecraft:activator_rail",
"minecraft:andesite_stairs": "minecraft:andesite_stairs",
"minecraft:anvil": "minecraft:anvil",
"minecraft:bamboo": "minecraft:bamboo",
"minecraft:bamboo_sapling": "minecraft:bamboo_sapling",
"minecraft:barrel": "minecraft:barrel",
"minecraft:barrier": "minecraft:barrier",
"minecraft:beacon": "minecraft:beacon",
"minecraft:bedrock": "minecraft:bedrock",
"minecraft:bell": "minecraft:bell",
"minecraft:birch_button": "minecraft:birch_button",
"minecraft:birch_fence_gate": "minecraft:birch_fence_gate",
"minecraft:birch_pressure_plate": "minecraft:birch_pressure_plate",
"minecraft:birch_stairs": "minecraft:birch_stairs",
"minecraft:birch_standing_sign": "minecraft:birch_standing_sign",
"minecraft:birch_trapdoor": "minecraft:birch_trapdoor",
"minecraft:birch_wall_sign": "minecraft:birch_wall_sign",
"minecraft:black_glazed_terracotta": "minecraft:black_glazed_terracotta",
"minecraft:blast_furnace": "minecraft:blast_furnace",
"minecraft:blue_glazed_terracotta": "minecraft:blue_glazed_terracotta",
"minecraft:blue_ice": "minecraft:blue_ice",
"minecraft:bone_block": "minecraft:bone_block",
"minecraft:bookshelf": "minecraft:bookshelf",
"minecraft:brewingstandblock": "minecraft:brewing_stand",
"minecraft:brick_block": "minecraft:brick_block",
"minecraft:brick_stairs": "minecraft:brick_stairs",
"minecraft:brown_glazed_terracotta": "minecraft:brown_glazed_terracotta",
"minecraft:brown_mushroom": "minecraft:brown_mushroom",
"minecraft:brown_mushroom_block": "minecraft:brown_mushroom_block",
"minecraft:bubble_column": "minecraft:bubble_column",
"minecraft:cactus": "minecraft:cactus",
"minecraft:carpet": "minecraft:carpet",
"minecraft:carrots": "minecraft:carrots",
"minecraft:cartography_table": "minecraft:cartography_table",
"minecraft:carved_pumpkin": "minecraft:carved_pumpkin",
"minecraft:chain_command_block": "minecraft:chain_command_block",
"minecraft:chemical_heat": "minecraft:chemical_heat",
"minecraft:chemistry_table": "minecraft:chemistry_table",
"minecraft:chest": "minecraft:chest",
"minecraft:chorus_flower": "minecraft:chorus_flower",
"minecraft:chorus_plant": "minecraft:chorus_plant",
"minecraft:clay": "minecraft:clay",
"minecraft:coal_block": "minecraft:coal_block",
"minecraft:coal_ore": "minecraft:coal_ore",
"minecraft:cobblestone": "minecraft:cobblestone",
"minecraft:cobblestone_wall": "minecraft:cobblestone_wall",
"minecraft:cocoa": "minecraft:cocoa",
"minecraft:colored_torch_bp": "minecraft:colored_torch_bp",
"minecraft:colored_torch_rg": "minecraft:colored_torch_rg",
"minecraft:command_block": "minecraft:command_block",
"minecraft:composter": "minecraft:composter",
"minecraft:concrete": "minecraft:concrete",
"minecraft:concrete_powder": "minecraft:concretePowder",
"minecraft:conduit": "minecraft:conduit",
"minecraft:coral": "minecraft:coral",
"minecraft:coral_block": "minecraft:coral_block",
"minecraft:coral_fan": "minecraft:coral_fan",
"minecraft:coral_fan_dead": "minecraft:coral_fan_dead",
"minecraft:coral_fan_hang": "minecraft:coral_fan_hang",
"minecraft:coral_fan_hang2": "minecraft:coral_fan_hang2",
"minecraft:coral_fan_hang3": "minecraft:coral_fan_hang3",
"minecraft:crafting_table": "minecraft:crafting_table",
"minecraft:cyan_glazed_terracotta": "minecraft:cyan_glazed_terracotta",
"minecraft:dark_oak_button": "minecraft:dark_oak_button",
"minecraft:dark_oak_fence_gate": "minecraft:dark_oak_fence_gate",
"minecraft:dark_oak_pressure_plate": "minecraft:dark_oak_pressure_plate",
"minecraft:dark_oak_stairs": "minecraft:dark_oak_stairs",
"minecraft:dark_oak_trapdoor": "minecraft:dark_oak_trapdoor",
"minecraft:dark_prismarine_stairs": "minecraft:dark_prismarine_stairs",
"minecraft:darkoak_standing_sign": "minecraft:darkoak_standing_sign",
"minecraft:darkoak_wall_sign": "minecraft:darkoak_wall_sign",
"minecraft:daylight_detector": "minecraft:daylight_detector",
"minecraft:daylight_detector_inverted": "minecraft:daylight_detector_inverted",
"minecraft:deadbush": "minecraft:deadbush",
"minecraft:detector_rail": "minecraft:detector_rail",
"minecraft:diamond_block": "minecraft:diamond_block",
"minecraft:diamond_ore": "minecraft:diamond_ore",
"minecraft:diorite_stairs": "minecraft:diorite_stairs",
"minecraft:dirt": "minecraft:dirt",
"minecraft:dispenser": "minecraft:dispenser",
"minecraft:double_plant": "minecraft:double_plant",
"minecraft:double_stone_slab": "minecraft:stone_slab",
"minecraft:double_stone_slab2": "minecraft:stone_slab2",
"minecraft:double_stone_slab3": "minecraft:stone_slab3",
"minecraft:double_stone_slab4": "minecraft:stone_slab4",
"minecraft:double_wooden_slab": "minecraft:double_wooden_slab",
"minecraft:dragon_egg": "minecraft:dragon_egg",
"minecraft:dried_kelp_block": "minecraft:dried_kelp_block",
"minecraft:dropper": "minecraft:dropper",
"minecraft:element_0": "minecraft:element_0",
"minecraft:element_1": "minecraft:element_1",
"minecraft:element_10": "minecraft:element_10",
"minecraft:element_100": "minecraft:element_100",
"minecraft:element_101": "minecraft:element_101",
"minecraft:element_102": "minecraft:element_102",
"minecraft:element_103": "minecraft:element_103",
"minecraft:element_104": "minecraft:element_104",
"minecraft:element_105": "minecraft:element_105",
"minecraft:element_106": "minecraft:element_106",
"minecraft:element_107": "minecraft:element_107",
"minecraft:element_108": "minecraft:element_108",
"minecraft:element_109": "minecraft:element_109",
"minecraft:element_11": "minecraft:element_11",
"minecraft:element_110": "minecraft:element_110",
"minecraft:element_111": "minecraft:element_111",
"minecraft:element_112": "minecraft:element_112",
"minecraft:element_113": "minecraft:element_113",
"minecraft:element_114": "minecraft:element_114",
"minecraft:element_115": "minecraft:element_115",
"minecraft:element_116": "minecraft:element_116",
"minecraft:element_117": "minecraft:element_117",
"minecraft:element_118": "minecraft:element_118",
"minecraft:element_12": "minecraft:element_12",
"minecraft:element_13": "minecraft:element_13",
"minecraft:element_14": "minecraft:element_14",
"minecraft:element_15": "minecraft:element_15",
"minecraft:element_16": "minecraft:element_16",
"minecraft:element_17": "minecraft:element_17",
"minecraft:element_18": "minecraft:element_18",
"minecraft:element_19": "minecraft:element_19",
"minecraft:element_2": "minecraft:element_2",
"minecraft:element_20": "minecraft:element_20",
"minecraft:element_21": "minecraft:element_21",
"minecraft:element_22": "minecraft:element_22",
"minecraft:element_23": "minecraft:element_23",
"minecraft:element_24": "minecraft:element_24",
"minecraft:element_25": "minecraft:element_25",
"minecraft:element_26": "minecraft:element_26",
"minecraft:element_27": "minecraft:element_27",
"minecraft:element_28": "minecraft:element_28",
"minecraft:element_29": "minecraft:element_29",
"minecraft:element_3": "minecraft:element_3",
"minecraft:element_30": "minecraft:element_30",
"minecraft:element_31": "minecraft:element_31",
"minecraft:element_32": "minecraft:element_32",
"minecraft:element_33": "minecraft:element_33",
"minecraft:element_34": "minecraft:element_34",
"minecraft:element_35": "minecraft:element_35",
"minecraft:element_36": "minecraft:element_36",
"minecraft:element_37": "minecraft:element_37",
"minecraft:element_38": "minecraft:element_38",
"minecraft:element_39": "minecraft:element_39",
"minecraft:element_4": "minecraft:element_4",
"minecraft:element_40": "minecraft:element_40",
"minecraft:element_41": "minecraft:element_41",
"minecraft:element_42": "minecraft:element_42",
"minecraft:element_43": "minecraft:element_43",
"minecraft:element_44": "minecraft:element_44",
"minecraft:element_45": "minecraft:element_45",
"minecraft:element_46": "minecraft:element_46",
"minecraft:element_47": "minecraft:element_47",
"minecraft:element_48": "minecraft:element_48",
"minecraft:element_49": "minecraft:element_49",
"minecraft:element_5": "minecraft:element_5",
"minecraft:element_50": "minecraft:element_50",
"minecraft:element_51": "minecraft:element_51",
"minecraft:element_52": "minecraft:element_52",
"minecraft:element_53": "minecraft:element_53",
"minecraft:element_54": "minecraft:element_54",
"minecraft:element_55": "minecraft:element_55",
"minecraft:element_56": "minecraft:element_56",
"minecraft:element_57": "minecraft:element_57",
"minecraft:element_58": "minecraft:element_58",
"minecraft:element_59": "minecraft:element_59",
"minecraft:element_6": "minecraft:element_6",
"minecraft:element_60": "minecraft:element_60",
"minecraft:element_61": "minecraft:element_61",
"minecraft:element_62": "minecraft:element_62",
"minecraft:element_63": "minecraft:element_63",
"minecraft:element_64": "minecraft:element_64",
"minecraft:element_65": "minecraft:element_65",
"minecraft:element_66": "minecraft:element_66",
"minecraft:element_67": "minecraft:element_67",
"minecraft:element_68": "minecraft:element_68",
"minecraft:element_69": "minecraft:element_69",
"minecraft:element_7": "minecraft:element_7",
"minecraft:element_70": "minecraft:element_70",
"minecraft:element_71": "minecraft:element_71",
"minecraft:element_72": "minecraft:element_72",
"minecraft:element_73": "minecraft:element_73",
"minecraft:element_74": "minecraft:element_74",
"minecraft:element_75": "minecraft:element_75",
"minecraft:element_76": "minecraft:element_76",
"minecraft:element_77": "minecraft:element_77",
"minecraft:element_78": "minecraft:element_78",
"minecraft:element_79": "minecraft:element_79",
"minecraft:element_8": "minecraft:element_8",
"minecraft:element_80": "minecraft:element_80",
"minecraft:element_81": "minecraft:element_81",
"minecraft:element_82": "minecraft:element_82",
"minecraft:element_83": "minecraft:element_83",
"minecraft:element_84": "minecraft:element_84",
"minecraft:element_85": "minecraft:element_85",
"minecraft:element_86": "minecraft:element_86",
"minecraft:element_87": "minecraft:element_87",
"minecraft:element_88": "minecraft:element_88",
"minecraft:element_89": "minecraft:element_89",
"minecraft:element_9": "minecraft:element_9",
"minecraft:element_90": "minecraft:element_90",
"minecraft:element_91": "minecraft:element_91",
"minecraft:element_92": "minecraft:element_92",
"minecraft:element_93": "minecraft:element_93",
"minecraft:element_94": "minecraft:element_94",
"minecraft:element_95": "minecraft:element_95",
"minecraft:element_96": "minecraft:element_96",
"minecraft:element_97": "minecraft:element_97",
"minecraft:element_98": "minecraft:element_98",
"minecraft:element_99": "minecraft:element_99",
"minecraft:emerald_block": "minecraft:emerald_block",
"minecraft:emerald_ore": "minecraft:emerald_ore",
"minecraft:enchanting_table": "minecraft:enchanting_table",
"minecraft:end_brick_stairs": "minecraft:end_brick_stairs",
"minecraft:end_bricks": "minecraft:end_bricks",
"minecraft:end_gateway": "minecraft:end_gateway",
"minecraft:end_portal": "minecraft:end_portal",
"minecraft:end_portal_frame": "minecraft:end_portal_frame",
"minecraft:end_rod": "minecraft:end_rod",
"minecraft:end_stone": "minecraft:end_stone",
"minecraft:ender_chest": "minecraft:ender_chest",
"minecraft:farmland": "minecraft:farmland",
"minecraft:fence": "minecraft:fence",
"minecraft:fence_gate": "minecraft:fence_gate",
"minecraft:fire": "minecraft:fire",
"minecraft:fletching_table": "minecraft:fletching_table",
"minecraft:flowing_lava": "minecraft:flowing_lava",
"minecraft:flowing_water": "minecraft:flowing_water",
"minecraft:frosted_ice": "minecraft:frosted_ice",
"minecraft:furnace": "minecraft:furnace",
"minecraft:glass": "minecraft:glass",
"minecraft:glass_pane": "minecraft:glass_pane",
"minecraft:glazedterracotta.black": "minecraft:black_glazed_terracotta",
"minecraft:glazedterracotta.blue": "minecraft:blue_glazed_terracotta",
"minecraft:glazedterracotta.brown": "minecraft:brown_glazed_terracotta",
"minecraft:glazedterracotta.cyan": "minecraft:cyan_glazed_terracotta",
"minecraft:glazedterracotta.gray": "minecraft:gray_glazed_terracotta",
"minecraft:glazedterracotta.green": "minecraft:green_glazed_terracotta",
"minecraft:glazedterracotta.light_blue": "minecraft:light_blue_glazed_terracotta",
"minecraft:glazedterracotta.lime": "minecraft:lime_glazed_terracotta",
"minecraft:glazedterracotta.magenta": "minecraft:magenta_glazed_terracotta",
"minecraft:glazedterracotta.orange": "minecraft:orange_glazed_terracotta",
"minecraft:glazedterracotta.pink": "minecraft:pink_glazed_terracotta",
"minecraft:glazedterracotta.purple": "minecraft:purple_glazed_terracotta",
"minecraft:glazedterracotta.red": "minecraft:red_glazed_terracotta",
"minecraft:glazedterracotta.silver": "minecraft:silver_glazed_terracotta",
"minecraft:glazedterracotta.white": "minecraft:white_glazed_terracotta",
"minecraft:glazedterracotta.yellow": "minecraft:yellow_glazed_terracotta",
"minecraft:glowingobsidian": "minecraft:glowingobsidian",
"minecraft:glowstone": "minecraft:glowstone",
"minecraft:gold_block": "minecraft:gold_block",
"minecraft:gold_ore": "minecraft:gold_ore",
"minecraft:golden_rail": "minecraft:golden_rail",
"minecraft:granite_stairs": "minecraft:granite_stairs",
"minecraft:grass": "minecraft:grass",
"minecraft:grass_path": "minecraft:grass_path",
"minecraft:gravel": "minecraft:gravel",
"minecraft:gray_glazed_terracotta": "minecraft:gray_glazed_terracotta",
"minecraft:green_glazed_terracotta": "minecraft:green_glazed_terracotta",
"minecraft:grindstone": "minecraft:grindstone",
"minecraft:hard_glass": "minecraft:hard_glass",
"minecraft:hard_glass_pane": "minecraft:hard_glass_pane",
"minecraft:hard_stained_glass": "minecraft:hard_stained_glass",
"minecraft:hard_stained_glass_pane": "minecraft:hard_stained_glass_pane",
"minecraft:hardened_clay": "minecraft:hardened_clay",
"minecraft:hay_block": "minecraft:hay_block",
"minecraft:heavy_weighted_pressure_plate": "minecraft:heavy_weighted_pressure_plate",
"minecraft:ice": "minecraft:ice",
"minecraft:info_update": "minecraft:info_update",
"minecraft:info_update2": "minecraft:info_update2",
"minecraft:invisiblebedrock": "minecraft:invisibleBedrock",
"minecraft:iron_bars": "minecraft:iron_bars",
"minecraft:iron_block": "minecraft:iron_block",
"minecraft:iron_ore": "minecraft:iron_ore",
"minecraft:iron_trapdoor": "minecraft:iron_trapdoor",
"minecraft:item.acacia_door": "minecraft:acacia_door",
"minecraft:item.bed": "minecraft:bed",
"minecraft:item.beetroot": "minecraft:beetroot",
"minecraft:item.birch_door": "minecraft:birch_door",
"minecraft:item.cake": "minecraft:cake",
"minecraft:item.campfire": "minecraft:campfire",
"minecraft:item.cauldron": "minecraft:cauldron",
"minecraft:item.dark_oak_door": "minecraft:dark_oak_door",
"minecraft:item.flower_pot": "minecraft:flower_pot",
"minecraft:item.frame": "minecraft:frame",
"minecraft:item.hopper": "minecraft:hopper",
"minecraft:item.iron_door": "minecraft:iron_door",
"minecraft:item.jungle_door": "minecraft:jungle_door",
"minecraft:item.kelp": "minecraft:kelp",
"minecraft:item.nether_wart": "minecraft:nether_wart",
"minecraft:item.reeds": "minecraft:reeds",
"minecraft:item.skull": "minecraft:skull",
"minecraft:item.spruce_door": "minecraft:spruce_door",
"minecraft:item.wheat": "minecraft:wheat",
"minecraft:item.wooden_door": "minecraft:wooden_door",
"minecraft:jigsaw": "minecraft:jigsaw",
"minecraft:jukebox": "minecraft:jukebox",
"minecraft:jungle_button": "minecraft:jungle_button",
"minecraft:jungle_fence_gate": "minecraft:jungle_fence_gate",
"minecraft:jungle_pressure_plate": "minecraft:jungle_pressure_plate",
"minecraft:jungle_stairs": "minecraft:jungle_stairs",
"minecraft:jungle_standing_sign": "minecraft:jungle_standing_sign",
"minecraft:jungle_trapdoor": "minecraft:jungle_trapdoor",
"minecraft:jungle_wall_sign": "minecraft:jungle_wall_sign",
"minecraft:ladder": "minecraft:ladder",
"minecraft:lantern": "minecraft:lantern",
"minecraft:lapis_block": "minecraft:lapis_block",
"minecraft:lapis_ore": "minecraft:lapis_ore",
"minecraft:lava": "minecraft:lava",
"minecraft:lava_cauldron": "minecraft:lava_cauldron",
"minecraft:leaves": "minecraft:leaves",
"minecraft:leaves2": "minecraft:leaves2",
"minecraft:lectern": "minecraft:lectern",
"minecraft:lever": "minecraft:lever",
"minecraft:light_blue_glazed_terracotta": "minecraft:light_blue_glazed_terracotta",
"minecraft:light_weighted_pressure_plate": "minecraft:light_weighted_pressure_plate",
"minecraft:lime_glazed_terracotta": "minecraft:lime_glazed_terracotta",
"minecraft:lit_blast_furnace": "minecraft:lit_blast_furnace",
"minecraft:lit_furnace": "minecraft:lit_furnace",
"minecraft:lit_pumpkin": "minecraft:lit_pumpkin",
"minecraft:lit_redstone_lamp": "minecraft:lit_redstone_lamp",
"minecraft:lit_redstone_ore": "minecraft:lit_redstone_ore",
"minecraft:lit_smoker": "minecraft:lit_smoker",
"minecraft:log": "minecraft:log",
"minecraft:log2": "minecraft:log2",
"minecraft:loom": "minecraft:loom",
"minecraft:magenta_glazed_terracotta": "minecraft:magenta_glazed_terracotta",
"minecraft:magma": "minecraft:magma",
"minecraft:melon_block": "minecraft:melon_block",
"minecraft:melon_stem": "minecraft:melon_stem",
"minecraft:mob_spawner": "minecraft:mob_spawner",
"minecraft:monster_egg": "minecraft:monster_egg",
"minecraft:mossy_cobblestone": "minecraft:mossy_cobblestone",
"minecraft:mossy_cobblestone_stairs": "minecraft:mossy_cobblestone_stairs",
"minecraft:mossy_stone_brick_stairs": "minecraft:mossy_stone_brick_stairs",
"minecraft:movingblock": "minecraft:movingBlock",
"minecraft:mycelium": "minecraft:mycelium",
"minecraft:nether_brick": "minecraft:nether_brick",
"minecraft:nether_brick_fence": "minecraft:nether_brick_fence",
"minecraft:nether_brick_stairs": "minecraft:nether_brick_stairs",
"minecraft:nether_wart_block": "minecraft:nether_wart_block",
"minecraft:netherrack": "minecraft:netherrack",
"minecraft:netherreactor": "minecraft:netherreactor",
"minecraft:normal_stone_stairs": "minecraft:normal_stone_stairs",
"minecraft:noteblock": "minecraft:noteblock",
"minecraft:oak_stairs": "minecraft:oak_stairs",
"minecraft:observer": "minecraft:observer",
"minecraft:obsidian": "minecraft:obsidian",
"minecraft:orange_glazed_terracotta": "minecraft:orange_glazed_terracotta",
"minecraft:packed_ice": "minecraft:packed_ice",
"minecraft:pink_glazed_terracotta": "minecraft:pink_glazed_terracotta",
"minecraft:piston": "minecraft:piston",
"minecraft:pistonarmcollision": "minecraft:pistonArmCollision",
"minecraft:planks": "minecraft:planks",
"minecraft:podzol": "minecraft:podzol",
"minecraft:polished_andesite_stairs": "minecraft:polished_andesite_stairs",
"minecraft:polished_diorite_stairs": "minecraft:polished_diorite_stairs",
"minecraft:polished_granite_stairs": "minecraft:polished_granite_stairs",
"minecraft:portal": "minecraft:portal",
"minecraft:potatoes": "minecraft:potatoes",
"minecraft:powered_comparator": "minecraft:powered_comparator",
"minecraft:powered_repeater": "minecraft:powered_repeater",
"minecraft:prismarine": "minecraft:prismarine",
"minecraft:prismarine_bricks_stairs": "minecraft:prismarine_bricks_stairs",
"minecraft:prismarine_stairs": "minecraft:prismarine_stairs",
"minecraft:pumpkin": "minecraft:pumpkin",
"minecraft:pumpkin_stem": "minecraft:pumpkin_stem",
"minecraft:purple_glazed_terracotta": "minecraft:purple_glazed_terracotta",
"minecraft:purpur_block": "minecraft:purpur_block",
"minecraft:purpur_stairs": "minecraft:purpur_stairs",
"minecraft:quartz_block": "minecraft:quartz_block",
"minecraft:quartz_ore": "minecraft:quartz_ore",
"minecraft:quartz_stairs": "minecraft:quartz_stairs",
"minecraft:rail": "minecraft:rail",
"minecraft:real_double_stone_slab": "minecraft:double_stone_slab",
"minecraft:real_double_stone_slab2": "minecraft:double_stone_slab2",
"minecraft:real_double_stone_slab3": "minecraft:double_stone_slab3",
"minecraft:real_double_stone_slab4": "minecraft:double_stone_slab4",
"minecraft:red_flower": "minecraft:red_flower",
"minecraft:red_glazed_terracotta": "minecraft:red_glazed_terracotta",
"minecraft:red_mushroom": "minecraft:red_mushroom",
"minecraft:red_mushroom_block": "minecraft:red_mushroom_block",
"minecraft:red_nether_brick": "minecraft:red_nether_brick",
"minecraft:red_nether_brick_stairs": "minecraft:red_nether_brick_stairs",
"minecraft:red_sandstone": "minecraft:red_sandstone",
"minecraft:red_sandstone_stairs": "minecraft:red_sandstone_stairs",
"minecraft:redstone_block": "minecraft:redstone_block",
"minecraft:redstone_lamp": "minecraft:redstone_lamp",
"minecraft:redstone_ore": "minecraft:redstone_ore",
"minecraft:redstone_torch": "minecraft:redstone_torch",
"minecraft:redstone_wire": "minecraft:redstone_wire",
"minecraft:repeating_command_block": "minecraft:repeating_command_block",
"minecraft:reserved6": "minecraft:reserved6",
"minecraft:sand": "minecraft:sand",
"minecraft:sandstone": "minecraft:sandstone",
"minecraft:sandstone_stairs": "minecraft:sandstone_stairs",
"minecraft:sapling": "minecraft:sapling",
"minecraft:scaffolding": "minecraft:scaffolding",
"minecraft:sea_pickle": "minecraft:sea_pickle",
"minecraft:seagrass": "minecraft:seagrass",
"minecraft:sealantern": "minecraft:seaLantern",
"minecraft:shulker_box": "minecraft:shulker_box",
"minecraft:silver_glazed_terracotta": "minecraft:silver_glazed_terracotta",
"minecraft:slime": "minecraft:slime",
"minecraft:smithing_table": "minecraft:smithing_table",
"minecraft:smoker": "minecraft:smoker",
"minecraft:smooth_quartz_stairs": "minecraft:smooth_quartz_stairs",
"minecraft:smooth_red_sandstone_stairs": "minecraft:smooth_red_sandstone_stairs",
"minecraft:smooth_sandstone_stairs": "minecraft:smooth_sandstone_stairs",
"minecraft:smooth_stone": "minecraft:smooth_stone",
"minecraft:snow": "minecraft:snow",
"minecraft:snow_layer": "minecraft:snow_layer",
"minecraft:soul_sand": "minecraft:soul_sand",
"minecraft:sponge": "minecraft:sponge",
"minecraft:spruce_button": "minecraft:spruce_button",
"minecraft:spruce_fence_gate": "minecraft:spruce_fence_gate",
"minecraft:spruce_pressure_plate": "minecraft:spruce_pressure_plate",
"minecraft:spruce_stairs": "minecraft:spruce_stairs",
"minecraft:spruce_standing_sign": "minecraft:spruce_standing_sign",
"minecraft:spruce_trapdoor": "minecraft:spruce_trapdoor",
"minecraft:spruce_wall_sign": "minecraft:spruce_wall_sign",
"minecraft:stained_glass": "minecraft:stained_glass",
"minecraft:stained_glass_pane": "minecraft:stained_glass_pane",
"minecraft:stained_hardened_clay": "minecraft:stained_hardened_clay",
"minecraft:standing_banner": "minecraft:standing_banner",
"minecraft:standing_sign": "minecraft:standing_sign",
"minecraft:sticky_piston": "minecraft:sticky_piston",
"minecraft:stone": "minecraft:stone",
"minecraft:stone_brick_stairs": "minecraft:stone_brick_stairs",
"minecraft:stone_button": "minecraft:stone_button",
"minecraft:stone_pressure_plate": "minecraft:stone_pressure_plate",
"minecraft:stone_slab": "minecraft:stone_slab",
"minecraft:stone_slab2": "minecraft:stone_slab2",
"minecraft:stone_slab3": "minecraft:stone_slab3",
"minecraft:stone_slab4": "minecraft:stone_slab4",
"minecraft:stone_stairs": "minecraft:stone_stairs",
"minecraft:stonebrick": "minecraft:stonebrick",
"minecraft:stonecutter": "minecraft:stonecutter",
"minecraft:stonecutter_block": "minecraft:stonecutter_block",
"minecraft:stripped_acacia_log": "minecraft:stripped_acacia_log",
"minecraft:stripped_birch_log": "minecraft:stripped_birch_log",
"minecraft:stripped_dark_oak_log": "minecraft:stripped_dark_oak_log",
"minecraft:stripped_jungle_log": "minecraft:stripped_jungle_log",
"minecraft:stripped_oak_log": "minecraft:stripped_oak_log",
"minecraft:stripped_spruce_log": "minecraft:stripped_spruce_log",
"minecraft:structure_block": "minecraft:structure_block",
"minecraft:sweet_berry_bush": "minecraft:sweet_berry_bush",
"minecraft:tallgrass": "minecraft:tallgrass",
"minecraft:tnt": "minecraft:tnt",
"minecraft:torch": "minecraft:torch",
"minecraft:trapdoor": "minecraft:trapdoor",
"minecraft:trapped_chest": "minecraft:trapped_chest",
"minecraft:tripwire": "minecraft:tripWire",
"minecraft:tripwire_hook": "minecraft:tripwire_hook",
"minecraft:turtle_egg": "minecraft:turtle_egg",
"minecraft:underwater_torch": "minecraft:underwater_torch",
"minecraft:undyed_shulker_box": "minecraft:undyed_shulker_box",
"minecraft:unlit_redstone_torch": "minecraft:unlit_redstone_torch",
"minecraft:unpowered_comparator": "minecraft:unpowered_comparator",
"minecraft:unpowered_repeater": "minecraft:unpowered_repeater",
"minecraft:vine": "minecraft:vine",
"minecraft:wall_banner": "minecraft:wall_banner",
"minecraft:wall_sign": "minecraft:wall_sign",
"minecraft:water": "minecraft:water",
"minecraft:waterlily": "minecraft:waterlily",
"minecraft:web": "minecraft:web",
"minecraft:white_glazed_terracotta": "minecraft:white_glazed_terracotta",
"minecraft:wood": "minecraft:wood",
"minecraft:wooden_button": "minecraft:wooden_button",
"minecraft:wooden_pressure_plate": "minecraft:wooden_pressure_plate",
"minecraft:wooden_slab": "minecraft:wooden_slab",
"minecraft:wool": "minecraft:wool",
"minecraft:yellow_flower": "minecraft:yellow_flower",
"minecraft:yellow_glazed_terracotta": "minecraft:yellow_glazed_terracotta"
}

Binary file not shown.

110
internal/item/registry.go Normal file
View File

@@ -0,0 +1,110 @@
package item
import (
"bytes"
"embed"
"encoding/json"
"github.com/df-mc/worldupgrader/blockupgrader"
"github.com/sandertv/gophertunnel/minecraft/nbt"
"github.com/sandertv/gophertunnel/minecraft/protocol"
"regexp"
"sort"
"strconv"
)
var (
//go:embed schemas/*.json
schemasFS embed.FS
//go:embed 1.12.0_item_id_to_block_id_map.json
rawItemToBlockIdMap []byte
//go:embed 1.12.0_to_1.18.10_blockstate_map.bin
rawblockStateMap []byte
// schemaIDs is a list of ids from all registered schemas.
schemaIDs []int
// schemas is a map of all registered item upgrade schemas.
schemas map[uint16]schema
// itemToBlockIdMap is a list of all registered item upgrade schemas.
itemToBlockIdMap map[string]string
// blockStateMap is a list of all legacy block states mapped to an upgraded version of it.
blockStateMap map[stateHash]blockupgrader.BlockState
filenameRegex = regexp.MustCompile(`(\d{4})_[\w.]+\.json`)
)
// stateHash is a struct that may be used as a map key for block states.
type stateHash struct {
// Name is the name of the block. It looks like 'minecraft:stone'.
Name string
// Metadata is the metadata value of the block. A lot of blocks only have 0 as data value, but some blocks
// carry specific variants or properties encoded in the metadata.
Metadata uint32
}
// init ...
func init() {
schemas = make(map[uint16]schema)
files, err := schemasFS.ReadDir("schemas")
if err != nil {
panic(err)
}
for _, f := range files {
if f.IsDir() {
continue
}
subMatches := filenameRegex.FindStringSubmatch(f.Name())
if subMatches == nil {
continue
}
id, err := strconv.Atoi(subMatches[1])
if err != nil {
continue
}
buf, err := schemasFS.ReadFile("schemas/" + f.Name())
if err != nil {
panic(err)
}
var s schema
if err = json.Unmarshal(buf, &s); err != nil {
panic(err)
}
schemas[uint16(id)] = s
}
schemaIDs = make([]int, 0, len(schemas))
for k := range schemas {
schemaIDs = append(schemaIDs, int(k))
}
sort.Sort(sort.Reverse(sort.IntSlice(schemaIDs)))
if err = json.Unmarshal(rawItemToBlockIdMap, &itemToBlockIdMap); err != nil {
panic(err)
}
blockStateMap = make(map[stateHash]blockupgrader.BlockState)
buf := protocol.NewReader(bytes.NewBuffer(rawblockStateMap), 0, false)
var length uint32
buf.Varuint32(&length)
for i := uint32(0); i < length; i++ {
var legacyStringId string
buf.String(&legacyStringId)
var pairs uint32
buf.Varuint32(&pairs)
for y := uint32(0); y < pairs; y++ {
var meta uint32
buf.Varuint32(&meta)
var blockStateRaw map[string]any
buf.NBT(&blockStateRaw, nbt.LittleEndian)
latestBlockState := blockupgrader.Upgrade(blockupgrader.BlockState{
Name: blockStateRaw["name"].(string),
Properties: blockStateRaw["states"].(map[string]any),
Version: blockStateRaw["version"].(int32),
})
blockStateMap[stateHash{
Name: legacyStringId,
Metadata: meta,
}] = latestBlockState
}
}
}

7
internal/item/schema.go Normal file
View File

@@ -0,0 +1,7 @@
package item
// schema represents the schema for loading item upgrade data from a JSON file.
type schema struct {
RenamedIDs map[string]string `json:"renamedIds,omitempty"`
RemappedMetas map[string]map[uint32]string `json:"remappedMetas,omitempty"`
}

View File

@@ -0,0 +1,10 @@
{
"renamedIds": {
"minecraft:nametag": "minecraft:name_tag",
"minecraft:prismarineshard": "minecraft:prismarine_shard",
"minecraft:stone_slab": "minecraft:double_stone_slab",
"minecraft:stone_slab2": "minecraft:double_stone_slab2",
"minecraft:stone_slab3": "minecraft:double_stone_slab3",
"minecraft:stone_slab4": "minecraft:double_stone_slab4"
}
}

View File

@@ -0,0 +1,20 @@
{
"renamedIds": {
"minecraft:glazedterracotta.black": "minecraft:black_glazed_terracotta",
"minecraft:glazedterracotta.blue": "minecraft:blue_glazed_terracotta",
"minecraft:glazedterracotta.brown": "minecraft:brown_glazed_terracotta",
"minecraft:glazedterracotta.cyan": "minecraft:cyan_glazed_terracotta",
"minecraft:glazedterracotta.gray": "minecraft:gray_glazed_terracotta",
"minecraft:glazedterracotta.green": "minecraft:green_glazed_terracotta",
"minecraft:glazedterracotta.light_blue": "minecraft:light_blue_glazed_terracotta",
"minecraft:glazedterracotta.lime": "minecraft:lime_glazed_terracotta",
"minecraft:glazedterracotta.magenta": "minecraft:magenta_glazed_terracotta",
"minecraft:glazedterracotta.orange": "minecraft:orange_glazed_terracotta",
"minecraft:glazedterracotta.pink": "minecraft:pink_glazed_terracotta",
"minecraft:glazedterracotta.purple": "minecraft:purple_glazed_terracotta",
"minecraft:glazedterracotta.red": "minecraft:red_glazed_terracotta",
"minecraft:glazedterracotta.silver": "minecraft:silver_glazed_terracotta",
"minecraft:glazedterracotta.white": "minecraft:white_glazed_terracotta",
"minecraft:glazedterracotta.yellow": "minecraft:yellow_glazed_terracotta"
}
}

View File

@@ -0,0 +1,165 @@
{
"remappedMetas": {
"minecraft:banner_pattern": {
"0": "minecraft:creeper_banner_pattern",
"1": "minecraft:skull_banner_pattern",
"2": "minecraft:flower_banner_pattern",
"3": "minecraft:mojang_banner_pattern",
"4": "minecraft:field_masoned_banner_pattern",
"5": "minecraft:bordure_indented_banner_pattern",
"6": "minecraft:piglin_banner_pattern"
},
"minecraft:boat": {
"0": "minecraft:oak_boat",
"1": "minecraft:spruce_boat",
"2": "minecraft:birch_boat",
"3": "minecraft:jungle_boat",
"4": "minecraft:acacia_boat",
"5": "minecraft:dark_oak_boat"
},
"minecraft:bucket": {
"1": "minecraft:milk_bucket",
"10": "minecraft:lava_bucket",
"2": "minecraft:cod_bucket",
"3": "minecraft:salmon_bucket",
"4": "minecraft:tropical_fish_bucket",
"5": "minecraft:pufferfish_bucket",
"8": "minecraft:water_bucket"
},
"minecraft:coal": {
"1": "minecraft:charcoal"
},
"minecraft:dye": {
"0": "minecraft:ink_sac",
"1": "minecraft:red_dye",
"10": "minecraft:lime_dye",
"11": "minecraft:yellow_dye",
"12": "minecraft:light_blue_dye",
"13": "minecraft:magenta_dye",
"14": "minecraft:orange_dye",
"15": "minecraft:bone_meal",
"16": "minecraft:black_dye",
"17": "minecraft:brown_dye",
"18": "minecraft:blue_dye",
"19": "minecraft:white_dye",
"2": "minecraft:green_dye",
"3": "minecraft:cocoa_beans",
"4": "minecraft:lapis_lazuli",
"5": "minecraft:purple_dye",
"6": "minecraft:cyan_dye",
"7": "minecraft:light_gray_dye",
"8": "minecraft:gray_dye",
"9": "minecraft:pink_dye"
},
"minecraft:spawn_egg": {
"10": "minecraft:chicken_spawn_egg",
"104": "minecraft:evoker_spawn_egg",
"105": "minecraft:vex_spawn_egg",
"108": "minecraft:pufferfish_spawn_egg",
"109": "minecraft:salmon_spawn_egg",
"11": "minecraft:cow_spawn_egg",
"110": "minecraft:drowned_spawn_egg",
"111": "minecraft:tropical_fish_spawn_egg",
"112": "minecraft:cod_spawn_egg",
"113": "minecraft:panda_spawn_egg",
"114": "minecraft:pillager_spawn_egg",
"115": "minecraft:villager_spawn_egg",
"116": "minecraft:zombie_villager_spawn_egg",
"118": "minecraft:wandering_trader_spawn_egg",
"12": "minecraft:pig_spawn_egg",
"121": "minecraft:fox_spawn_egg",
"122": "minecraft:bee_spawn_egg",
"123": "minecraft:piglin_spawn_egg",
"124": "minecraft:hoglin_spawn_egg",
"125": "minecraft:strider_spawn_egg",
"126": "minecraft:zoglin_spawn_egg",
"127": "minecraft:piglin_brute_spawn_egg",
"13": "minecraft:sheep_spawn_egg",
"14": "minecraft:wolf_spawn_egg",
"15": "minecraft:villager_spawn_egg",
"16": "minecraft:mooshroom_spawn_egg",
"17": "minecraft:squid_spawn_egg",
"18": "minecraft:rabbit_spawn_egg",
"19": "minecraft:bat_spawn_egg",
"22": "minecraft:ocelot_spawn_egg",
"23": "minecraft:horse_spawn_egg",
"24": "minecraft:donkey_spawn_egg",
"25": "minecraft:mule_spawn_egg",
"26": "minecraft:skeleton_horse_spawn_egg",
"27": "minecraft:zombie_horse_spawn_egg",
"28": "minecraft:polar_bear_spawn_egg",
"29": "minecraft:llama_spawn_egg",
"30": "minecraft:parrot_spawn_egg",
"31": "minecraft:dolphin_spawn_egg",
"32": "minecraft:zombie_spawn_egg",
"33": "minecraft:creeper_spawn_egg",
"34": "minecraft:skeleton_spawn_egg",
"35": "minecraft:spider_spawn_egg",
"36": "minecraft:zombie_pigman_spawn_egg",
"37": "minecraft:slime_spawn_egg",
"38": "minecraft:enderman_spawn_egg",
"39": "minecraft:silverfish_spawn_egg",
"40": "minecraft:cave_spider_spawn_egg",
"41": "minecraft:ghast_spawn_egg",
"42": "minecraft:magma_cube_spawn_egg",
"43": "minecraft:blaze_spawn_egg",
"44": "minecraft:zombie_villager_spawn_egg",
"45": "minecraft:witch_spawn_egg",
"46": "minecraft:stray_spawn_egg",
"47": "minecraft:husk_spawn_egg",
"48": "minecraft:wither_skeleton_spawn_egg",
"49": "minecraft:guardian_spawn_egg",
"50": "minecraft:elder_guardian_spawn_egg",
"51": "minecraft:npc_spawn_egg",
"54": "minecraft:shulker_spawn_egg",
"55": "minecraft:endermite_spawn_egg",
"56": "minecraft:agent_spawn_egg",
"57": "minecraft:vindicator_spawn_egg",
"58": "minecraft:phantom_spawn_egg",
"59": "minecraft:ravager_spawn_egg",
"74": "minecraft:turtle_spawn_egg",
"75": "minecraft:cat_spawn_egg"
}
},
"renamedIds": {
"minecraft:appleenchanted": "minecraft:enchanted_golden_apple",
"minecraft:carrotonastick": "minecraft:carrot_on_a_stick",
"minecraft:chorus_fruit_popped": "minecraft:popped_chorus_fruit",
"minecraft:clownfish": "minecraft:tropical_fish",
"minecraft:cooked_fish": "minecraft:cooked_cod",
"minecraft:darkoak_sign": "minecraft:dark_oak_sign",
"minecraft:emptymap": "minecraft:empty_map",
"minecraft:fireball": "minecraft:fire_charge",
"minecraft:fireworks": "minecraft:firework_rocket",
"minecraft:fireworkscharge": "minecraft:firework_star",
"minecraft:fish": "minecraft:cod",
"minecraft:horsearmordiamond": "minecraft:diamond_horse_armor",
"minecraft:horsearmorgold": "minecraft:golden_horse_armor",
"minecraft:horsearmoriron": "minecraft:iron_horse_armor",
"minecraft:horsearmorleather": "minecraft:leather_horse_armor",
"minecraft:lodestonecompass": "minecraft:lodestone_compass",
"minecraft:map": "minecraft:filled_map",
"minecraft:melon": "minecraft:melon_slice",
"minecraft:muttoncooked": "minecraft:cooked_mutton",
"minecraft:muttonraw": "minecraft:mutton",
"minecraft:netherstar": "minecraft:nether_star",
"minecraft:record_11": "minecraft:music_disc_11",
"minecraft:record_13": "minecraft:music_disc_13",
"minecraft:record_blocks": "minecraft:music_disc_blocks",
"minecraft:record_cat": "minecraft:music_disc_cat",
"minecraft:record_chirp": "minecraft:music_disc_chirp",
"minecraft:record_far": "minecraft:music_disc_far",
"minecraft:record_mall": "minecraft:music_disc_mall",
"minecraft:record_mellohi": "minecraft:music_disc_mellohi",
"minecraft:record_pigstep": "minecraft:music_disc_pigstep",
"minecraft:record_stal": "minecraft:music_disc_stal",
"minecraft:record_strad": "minecraft:music_disc_strad",
"minecraft:record_wait": "minecraft:music_disc_wait",
"minecraft:record_ward": "minecraft:music_disc_ward",
"minecraft:reeds": "minecraft:sugar_cane",
"minecraft:sign": "minecraft:oak_sign",
"minecraft:speckled_melon": "minecraft:glistering_melon_slice",
"minecraft:totem": "minecraft:totem_of_undying",
"minecraft:turtle_shell_piece": "minecraft:scute"
}
}

View File

@@ -0,0 +1,7 @@
{
"remappedMetas": {
"minecraft:spawn_egg": {
"128": "minecraft:goat_spawn_egg"
}
}
}

View File

@@ -0,0 +1,12 @@
{
"remappedMetas": {
"minecraft:bucket": {
"11": "minecraft:powder_snow_bucket",
"12": "minecraft:axolotl_bucket"
},
"minecraft:spawn_egg": {
"129": "minecraft:glow_squid_spawn_egg",
"130": "minecraft:axolotl_spawn_egg"
}
}
}

View File

@@ -0,0 +1,5 @@
{
"renamedIds": {
"minecraft:record_otherside": "minecraft:music_disc_otherside"
}
}

View File

@@ -0,0 +1,16 @@
{
"remappedMetas": {
"minecraft:banner_pattern": {
"7": "minecraft:globe_banner_pattern"
},
"minecraft:bucket": {
"13": "minecraft:tadpole_bucket"
},
"minecraft:spawn_egg": {
"132": "minecraft:frog_spawn_egg",
"133": "minecraft:tadpole_spawn_egg",
"134": "minecraft:allay_spawn_egg",
"135": "minecraft:firefly_spawn_egg"
}
}
}

View File

@@ -0,0 +1,23 @@
{
"remappedMetas": {
"minecraft:chest_boat": {
"0": "minecraft:oak_chest_boat",
"1": "minecraft:spruce_chest_boat",
"2": "minecraft:birch_chest_boat",
"3": "minecraft:jungle_chest_boat",
"4": "minecraft:acacia_chest_boat",
"5": "minecraft:dark_oak_chest_boat"
},
"minecraft:spawn_egg": {
"131": "minecraft:warden_spawn_egg"
}
},
"renamedIds": {
"minecraft:concretepowder": "minecraft:concrete_powder",
"minecraft:invisiblebedrock": "minecraft:invisible_bedrock",
"minecraft:movingblock": "minecraft:moving_block",
"minecraft:pistonarmcollision": "minecraft:piston_arm_collision",
"minecraft:sealantern": "minecraft:sea_lantern",
"minecraft:stickypistonarmcollision": "minecraft:sticky_piston_arm_collision"
}
}

View File

@@ -0,0 +1,25 @@
{
"remappedMetas": {
"minecraft:boat": {
"6": "minecraft:mangrove_boat"
},
"minecraft:chest_boat": {
"6": "minecraft:mangrove_chest_boat"
}
},
"renamedIds": {
"minecraft:double_stone_slab": "minecraft:stone_block_slab",
"minecraft:double_stone_slab2": "minecraft:stone_block_slab2",
"minecraft:double_stone_slab3": "minecraft:stone_block_slab3",
"minecraft:double_stone_slab4": "minecraft:stone_block_slab4",
"minecraft:real_double_stone_slab": "minecraft:double_stone_block_slab",
"minecraft:real_double_stone_slab2": "minecraft:double_stone_block_slab2",
"minecraft:real_double_stone_slab3": "minecraft:double_stone_block_slab3",
"minecraft:real_double_stone_slab4": "minecraft:double_stone_block_slab4",
"minecraft:record_5": "minecraft:music_disc_5",
"minecraft:stone_slab": "minecraft:stone_block_slab",
"minecraft:stone_slab2": "minecraft:stone_block_slab2",
"minecraft:stone_slab3": "minecraft:stone_block_slab3",
"minecraft:stone_slab4": "minecraft:stone_block_slab4"
}
}

View File

@@ -0,0 +1,37 @@
{
"remappedMetas": {
"minecraft:boat": {
"7": "minecraft:bamboo_raft"
},
"minecraft:chest_boat": {
"7": "minecraft:bamboo_chest_raft"
},
"minecraft:spawn_egg": {
"138": "minecraft:camel_spawn_egg",
"139": "minecraft:sniffer_spawn_egg",
"157": "minecraft:trader_llama_spawn_egg",
"20": "minecraft:iron_golem_spawn_egg",
"21": "minecraft:snow_golem_spawn_egg",
"52": "minecraft:wither_spawn_egg",
"53": "minecraft:ender_dragon_spawn_egg"
},
"minecraft:wool": {
"0": "minecraft:white_wool",
"1": "minecraft:orange_wool",
"10": "minecraft:purple_wool",
"11": "minecraft:blue_wool",
"12": "minecraft:brown_wool",
"13": "minecraft:green_wool",
"14": "minecraft:red_wool",
"15": "minecraft:black_wool",
"2": "minecraft:magenta_wool",
"3": "minecraft:light_blue_wool",
"4": "minecraft:yellow_wool",
"5": "minecraft:lime_wool",
"6": "minecraft:pink_wool",
"7": "minecraft:gray_wool",
"8": "minecraft:light_gray_wool",
"9": "minecraft:cyan_wool"
}
}
}

View File

@@ -0,0 +1,36 @@
{
"remappedMetas": {
"minecraft:boat": {
"8": "minecraft:cherry_boat"
},
"minecraft:chest_boat": {
"8": "minecraft:cherry_chest_boat"
},
"minecraft:fence": {
"0": "minecraft:oak_fence",
"1": "minecraft:spruce_fence",
"2": "minecraft:birch_fence",
"3": "minecraft:jungle_fence",
"4": "minecraft:acacia_fence",
"5": "minecraft:dark_oak_fence"
},
"minecraft:log": {
"0": "minecraft:oak_log",
"1": "minecraft:spruce_log",
"10": "minecraft:birch_log",
"11": "minecraft:jungle_log",
"2": "minecraft:birch_log",
"3": "minecraft:jungle_log",
"5": "minecraft:spruce_log",
"6": "minecraft:birch_log",
"7": "minecraft:jungle_log",
"9": "minecraft:spruce_log"
},
"minecraft:log2": {
"0": "minecraft:acacia_log",
"1": "minecraft:dark_oak_log",
"5": "minecraft:dark_oak_log",
"9": "minecraft:dark_oak_log"
}
}
}

View File

@@ -0,0 +1,37 @@
{
"remappedMetas": {
"minecraft:carpet": {
"0": "minecraft:white_carpet",
"1": "minecraft:orange_carpet",
"10": "minecraft:purple_carpet",
"11": "minecraft:blue_carpet",
"12": "minecraft:brown_carpet",
"13": "minecraft:green_carpet",
"14": "minecraft:red_carpet",
"15": "minecraft:black_carpet",
"2": "minecraft:magenta_carpet",
"3": "minecraft:light_blue_carpet",
"4": "minecraft:yellow_carpet",
"5": "minecraft:lime_carpet",
"6": "minecraft:pink_carpet",
"7": "minecraft:gray_carpet",
"8": "minecraft:light_gray_carpet",
"9": "minecraft:cyan_carpet"
},
"minecraft:coral": {
"0": "minecraft:tube_coral",
"1": "minecraft:brain_coral",
"10": "minecraft:dead_bubble_coral",
"11": "minecraft:dead_fire_coral",
"12": "minecraft:dead_horn_coral",
"2": "minecraft:bubble_coral",
"3": "minecraft:fire_coral",
"4": "minecraft:horn_coral",
"8": "minecraft:dead_tube_coral",
"9": "minecraft:dead_brain_coral"
}
},
"renamedIds": {
"minecraft:record_relic": "minecraft:music_disc_relic"
}
}

View File

@@ -0,0 +1,40 @@
{
"remappedMetas": {
"minecraft:concrete": {
"0": "minecraft:white_concrete",
"1": "minecraft:orange_concrete",
"10": "minecraft:purple_concrete",
"11": "minecraft:blue_concrete",
"12": "minecraft:brown_concrete",
"13": "minecraft:green_concrete",
"14": "minecraft:red_concrete",
"15": "minecraft:black_concrete",
"2": "minecraft:magenta_concrete",
"3": "minecraft:light_blue_concrete",
"4": "minecraft:yellow_concrete",
"5": "minecraft:lime_concrete",
"6": "minecraft:pink_concrete",
"7": "minecraft:gray_concrete",
"8": "minecraft:light_gray_concrete",
"9": "minecraft:cyan_concrete"
},
"minecraft:shulker_box": {
"0": "minecraft:white_shulker_box",
"1": "minecraft:orange_shulker_box",
"10": "minecraft:purple_shulker_box",
"11": "minecraft:blue_shulker_box",
"12": "minecraft:brown_shulker_box",
"13": "minecraft:green_shulker_box",
"14": "minecraft:red_shulker_box",
"15": "minecraft:black_shulker_box",
"2": "minecraft:magenta_shulker_box",
"3": "minecraft:light_blue_shulker_box",
"4": "minecraft:yellow_shulker_box",
"5": "minecraft:lime_shulker_box",
"6": "minecraft:pink_shulker_box",
"7": "minecraft:gray_shulker_box",
"8": "minecraft:light_gray_shulker_box",
"9": "minecraft:cyan_shulker_box"
}
}
}

View File

@@ -0,0 +1,40 @@
{
"remappedMetas": {
"minecraft:stained_glass": {
"0": "minecraft:white_stained_glass",
"1": "minecraft:orange_stained_glass",
"10": "minecraft:purple_stained_glass",
"11": "minecraft:blue_stained_glass",
"12": "minecraft:brown_stained_glass",
"13": "minecraft:green_stained_glass",
"14": "minecraft:red_stained_glass",
"15": "minecraft:black_stained_glass",
"2": "minecraft:magenta_stained_glass",
"3": "minecraft:light_blue_stained_glass",
"4": "minecraft:yellow_stained_glass",
"5": "minecraft:lime_stained_glass",
"6": "minecraft:pink_stained_glass",
"7": "minecraft:gray_stained_glass",
"8": "minecraft:light_gray_stained_glass",
"9": "minecraft:cyan_stained_glass"
},
"minecraft:stained_glass_pane": {
"0": "minecraft:white_stained_glass_pane",
"1": "minecraft:orange_stained_glass_pane",
"10": "minecraft:purple_stained_glass_pane",
"11": "minecraft:blue_stained_glass_pane",
"12": "minecraft:brown_stained_glass_pane",
"13": "minecraft:green_stained_glass_pane",
"14": "minecraft:red_stained_glass_pane",
"15": "minecraft:black_stained_glass_pane",
"2": "minecraft:magenta_stained_glass_pane",
"3": "minecraft:light_blue_stained_glass_pane",
"4": "minecraft:yellow_stained_glass_pane",
"5": "minecraft:lime_stained_glass_pane",
"6": "minecraft:pink_stained_glass_pane",
"7": "minecraft:gray_stained_glass_pane",
"8": "minecraft:light_gray_stained_glass_pane",
"9": "minecraft:cyan_stained_glass_pane"
}
}
}

View File

@@ -0,0 +1,40 @@
{
"remappedMetas": {
"minecraft:concrete_powder": {
"0": "minecraft:white_concrete_powder",
"1": "minecraft:orange_concrete_powder",
"10": "minecraft:purple_concrete_powder",
"11": "minecraft:blue_concrete_powder",
"12": "minecraft:brown_concrete_powder",
"13": "minecraft:green_concrete_powder",
"14": "minecraft:red_concrete_powder",
"15": "minecraft:black_concrete_powder",
"2": "minecraft:magenta_concrete_powder",
"3": "minecraft:light_blue_concrete_powder",
"4": "minecraft:yellow_concrete_powder",
"5": "minecraft:lime_concrete_powder",
"6": "minecraft:pink_concrete_powder",
"7": "minecraft:gray_concrete_powder",
"8": "minecraft:light_gray_concrete_powder",
"9": "minecraft:cyan_concrete_powder"
},
"minecraft:stained_hardened_clay": {
"0": "minecraft:white_terracotta",
"1": "minecraft:orange_terracotta",
"10": "minecraft:purple_terracotta",
"11": "minecraft:blue_terracotta",
"12": "minecraft:brown_terracotta",
"13": "minecraft:green_terracotta",
"14": "minecraft:red_terracotta",
"15": "minecraft:black_terracotta",
"2": "minecraft:magenta_terracotta",
"3": "minecraft:light_blue_terracotta",
"4": "minecraft:yellow_terracotta",
"5": "minecraft:lime_terracotta",
"6": "minecraft:pink_terracotta",
"7": "minecraft:gray_terracotta",
"8": "minecraft:light_gray_terracotta",
"9": "minecraft:cyan_terracotta"
}
}
}

View File

@@ -0,0 +1,20 @@
{
"remappedMetas": {
"minecraft:planks": {
"0": "minecraft:oak_planks",
"1": "minecraft:spruce_planks",
"2": "minecraft:birch_planks",
"3": "minecraft:jungle_planks",
"4": "minecraft:acacia_planks",
"5": "minecraft:dark_oak_planks"
},
"minecraft:stone": {
"1": "minecraft:granite",
"2": "minecraft:polished_granite",
"3": "minecraft:diorite",
"4": "minecraft:polished_diorite",
"5": "minecraft:andesite",
"6": "minecraft:polished_andesite"
}
}
}

View File

@@ -0,0 +1,47 @@
{
"remappedMetas": {
"minecraft:hard_stained_glass": {
"0": "minecraft:hard_white_stained_glass",
"1": "minecraft:hard_orange_stained_glass",
"10": "minecraft:hard_purple_stained_glass",
"11": "minecraft:hard_blue_stained_glass",
"12": "minecraft:hard_brown_stained_glass",
"13": "minecraft:hard_green_stained_glass",
"14": "minecraft:hard_red_stained_glass",
"15": "minecraft:hard_black_stained_glass",
"2": "minecraft:hard_magenta_stained_glass",
"3": "minecraft:hard_light_blue_stained_glass",
"4": "minecraft:hard_yellow_stained_glass",
"5": "minecraft:hard_lime_stained_glass",
"6": "minecraft:hard_pink_stained_glass",
"7": "minecraft:hard_gray_stained_glass",
"8": "minecraft:hard_light_gray_stained_glass",
"9": "minecraft:hard_cyan_stained_glass"
},
"minecraft:hard_stained_glass_pane": {
"0": "minecraft:hard_white_stained_glass_pane",
"1": "minecraft:hard_orange_stained_glass_pane",
"10": "minecraft:hard_purple_stained_glass_pane",
"11": "minecraft:hard_blue_stained_glass_pane",
"12": "minecraft:hard_brown_stained_glass_pane",
"13": "minecraft:hard_green_stained_glass_pane",
"14": "minecraft:hard_red_stained_glass_pane",
"15": "minecraft:hard_black_stained_glass_pane",
"2": "minecraft:hard_magenta_stained_glass_pane",
"3": "minecraft:hard_light_blue_stained_glass_pane",
"4": "minecraft:hard_yellow_stained_glass_pane",
"5": "minecraft:hard_lime_stained_glass_pane",
"6": "minecraft:hard_pink_stained_glass_pane",
"7": "minecraft:hard_gray_stained_glass_pane",
"8": "minecraft:hard_light_gray_stained_glass_pane",
"9": "minecraft:hard_cyan_stained_glass_pane"
},
"minecraft:spawn_egg": {
"140": "minecraft:breeze_spawn_egg",
"142": "minecraft:armadillo_spawn_egg"
}
},
"renamedIds": {
"minecraft:scute": "minecraft:turtle_scute"
}
}

View File

@@ -0,0 +1,42 @@
{
"remappedMetas": {
"minecraft:leaves": {
"0": "minecraft:oak_leaves",
"1": "minecraft:spruce_leaves",
"2": "minecraft:birch_leaves",
"3": "minecraft:jungle_leaves"
},
"minecraft:leaves2": {
"0": "minecraft:acacia_leaves",
"1": "minecraft:dark_oak_leaves"
},
"minecraft:spawn_egg": {
"144": "minecraft:bogged_spawn_egg"
},
"minecraft:wood": {
"0": "minecraft:oak_wood",
"1": "minecraft:spruce_wood",
"10": "minecraft:stripped_birch_wood",
"11": "minecraft:stripped_jungle_wood",
"12": "minecraft:stripped_acacia_wood",
"13": "minecraft:stripped_dark_oak_wood",
"2": "minecraft:birch_wood",
"3": "minecraft:jungle_wood",
"4": "minecraft:acacia_wood",
"5": "minecraft:dark_oak_wood",
"8": "minecraft:stripped_oak_wood",
"9": "minecraft:stripped_spruce_wood"
},
"minecraft:wooden_slab": {
"0": "minecraft:oak_slab",
"1": "minecraft:spruce_slab",
"2": "minecraft:birch_slab",
"3": "minecraft:jungle_slab",
"4": "minecraft:acacia_slab",
"5": "minecraft:dark_oak_slab"
}
},
"renamedIds": {
"minecraft:grass": "minecraft:grass_block"
}
}

View File

@@ -0,0 +1,39 @@
{
"remappedMetas": {
"minecraft:coral_fan": {
"0": "minecraft:tube_coral_fan",
"1": "minecraft:brain_coral_fan",
"2": "minecraft:bubble_coral_fan",
"3": "minecraft:fire_coral_fan",
"4": "minecraft:horn_coral_fan"
},
"minecraft:coral_fan_dead": {
"0": "minecraft:dead_tube_coral_fan",
"1": "minecraft:dead_brain_coral_fan",
"2": "minecraft:dead_bubble_coral_fan",
"3": "minecraft:dead_fire_coral_fan",
"4": "minecraft:dead_horn_coral_fan"
},
"minecraft:red_flower": {
"0": "minecraft:poppy",
"1": "minecraft:blue_orchid",
"10": "minecraft:lily_of_the_valley",
"2": "minecraft:allium",
"3": "minecraft:azure_bluet",
"4": "minecraft:red_tulip",
"5": "minecraft:orange_tulip",
"6": "minecraft:white_tulip",
"7": "minecraft:pink_tulip",
"8": "minecraft:oxeye_daisy",
"9": "minecraft:cornflower"
},
"minecraft:sapling": {
"0": "minecraft:oak_sapling",
"1": "minecraft:spruce_sapling",
"2": "minecraft:birch_sapling",
"3": "minecraft:jungle_sapling",
"4": "minecraft:acacia_sapling",
"5": "minecraft:dark_oak_sapling"
}
}
}

View File

@@ -0,0 +1,44 @@
{
"remappedMetas": {
"minecraft:coral_block": {
"0": "minecraft:tube_coral_block",
"1": "minecraft:brain_coral_block",
"10": "minecraft:dead_bubble_coral_block",
"11": "minecraft:dead_fire_coral_block",
"12": "minecraft:dead_horn_coral_block",
"2": "minecraft:bubble_coral_block",
"3": "minecraft:fire_coral_block",
"4": "minecraft:horn_coral_block",
"8": "minecraft:dead_tube_coral_block",
"9": "minecraft:dead_brain_coral_block"
},
"minecraft:double_plant": {
"0": "minecraft:sunflower",
"1": "minecraft:lilac",
"2": "minecraft:tall_grass",
"3": "minecraft:large_fern",
"4": "minecraft:rose_bush",
"5": "minecraft:peony"
},
"minecraft:stone_block_slab": {
"0": "minecraft:smooth_stone_slab",
"1": "minecraft:sandstone_slab",
"2": "minecraft:petrified_oak_slab",
"3": "minecraft:cobblestone_slab",
"4": "minecraft:brick_slab",
"5": "minecraft:stone_brick_slab",
"6": "minecraft:quartz_slab",
"7": "minecraft:nether_brick_slab"
},
"minecraft:tallgrass": {
"0": "minecraft:short_grass",
"2": "minecraft:fern",
"3": "minecraft:fern"
}
},
"renamedIds": {
"minecraft:record_creator": "minecraft:music_disc_creator",
"minecraft:record_creator_music_box": "minecraft:music_disc_creator_music_box",
"minecraft:record_precipice": "minecraft:music_disc_precipice"
}
}

View File

@@ -0,0 +1,138 @@
{
"remappedMetas": {
"minecraft:anvil": {
"10": "minecraft:damaged_anvil",
"11": "minecraft:damaged_anvil",
"4": "minecraft:chipped_anvil",
"5": "minecraft:chipped_anvil",
"6": "minecraft:chipped_anvil",
"7": "minecraft:chipped_anvil",
"8": "minecraft:damaged_anvil",
"9": "minecraft:damaged_anvil"
},
"minecraft:dirt": {
"1": "minecraft:coarse_dirt"
},
"minecraft:double_stone_block_slab": {
"0": "minecraft:smooth_stone_double_slab",
"1": "minecraft:sandstone_double_slab",
"2": "minecraft:petrified_oak_double_slab",
"3": "minecraft:cobblestone_double_slab",
"4": "minecraft:brick_double_slab",
"5": "minecraft:stone_brick_double_slab",
"6": "minecraft:quartz_double_slab",
"7": "minecraft:nether_brick_double_slab"
},
"minecraft:double_stone_block_slab2": {
"0": "minecraft:red_sandstone_double_slab",
"1": "minecraft:purpur_double_slab",
"2": "minecraft:prismarine_double_slab",
"3": "minecraft:dark_prismarine_double_slab",
"4": "minecraft:prismarine_brick_double_slab",
"5": "minecraft:mossy_cobblestone_double_slab",
"6": "minecraft:smooth_sandstone_double_slab",
"7": "minecraft:red_nether_brick_double_slab"
},
"minecraft:double_stone_block_slab3": {
"0": "minecraft:end_stone_brick_double_slab",
"1": "minecraft:smooth_red_sandstone_double_slab",
"2": "minecraft:polished_andesite_double_slab",
"3": "minecraft:andesite_double_slab",
"4": "minecraft:diorite_double_slab",
"5": "minecraft:polished_diorite_double_slab",
"6": "minecraft:granite_double_slab",
"7": "minecraft:polished_granite_double_slab"
},
"minecraft:double_stone_block_slab4": {
"0": "minecraft:mossy_stone_brick_double_slab",
"1": "minecraft:smooth_quartz_double_slab",
"2": "minecraft:normal_stone_double_slab",
"3": "minecraft:cut_sandstone_double_slab",
"4": "minecraft:cut_red_sandstone_double_slab"
},
"minecraft:light_block": {
"0": "minecraft:light_block_0",
"1": "minecraft:light_block_1",
"10": "minecraft:light_block_10",
"11": "minecraft:light_block_11",
"12": "minecraft:light_block_12",
"13": "minecraft:light_block_13",
"14": "minecraft:light_block_14",
"15": "minecraft:light_block_15",
"2": "minecraft:light_block_2",
"3": "minecraft:light_block_3",
"4": "minecraft:light_block_4",
"5": "minecraft:light_block_5",
"6": "minecraft:light_block_6",
"7": "minecraft:light_block_7",
"8": "minecraft:light_block_8",
"9": "minecraft:light_block_9"
},
"minecraft:monster_egg": {
"0": "minecraft:infested_stone",
"1": "minecraft:infested_cobblestone",
"2": "minecraft:infested_stone_bricks",
"3": "minecraft:infested_mossy_stone_bricks",
"4": "minecraft:infested_cracked_stone_bricks",
"5": "minecraft:infested_chiseled_stone_bricks"
},
"minecraft:prismarine": {
"1": "minecraft:dark_prismarine",
"2": "minecraft:prismarine_bricks"
},
"minecraft:quartz_block": {
"1": "minecraft:chiseled_quartz_block",
"2": "minecraft:quartz_pillar",
"3": "minecraft:smooth_quartz"
},
"minecraft:red_sandstone": {
"1": "minecraft:chiseled_red_sandstone",
"2": "minecraft:cut_red_sandstone",
"3": "minecraft:smooth_red_sandstone"
},
"minecraft:sand": {
"1": "minecraft:red_sand"
},
"minecraft:sandstone": {
"1": "minecraft:chiseled_sandstone",
"2": "minecraft:cut_sandstone",
"3": "minecraft:smooth_sandstone"
},
"minecraft:stone_block_slab2": {
"0": "minecraft:red_sandstone_slab",
"1": "minecraft:purpur_slab",
"2": "minecraft:prismarine_slab",
"3": "minecraft:dark_prismarine_slab",
"4": "minecraft:prismarine_brick_slab",
"5": "minecraft:mossy_cobblestone_slab",
"6": "minecraft:smooth_sandstone_slab",
"7": "minecraft:red_nether_brick_slab"
},
"minecraft:stone_block_slab3": {
"0": "minecraft:end_stone_brick_slab",
"1": "minecraft:smooth_red_sandstone_slab",
"2": "minecraft:polished_andesite_slab",
"3": "minecraft:andesite_slab",
"4": "minecraft:diorite_slab",
"5": "minecraft:polished_diorite_slab",
"6": "minecraft:granite_slab",
"7": "minecraft:polished_granite_slab"
},
"minecraft:stone_block_slab4": {
"0": "minecraft:mossy_stone_brick_slab",
"1": "minecraft:smooth_quartz_slab",
"2": "minecraft:normal_stone_slab",
"3": "minecraft:cut_sandstone_slab",
"4": "minecraft:cut_red_sandstone_slab"
},
"minecraft:stonebrick": {
"0": "minecraft:stone_bricks",
"1": "minecraft:mossy_stone_bricks",
"2": "minecraft:cracked_stone_bricks",
"3": "minecraft:chiseled_stone_bricks"
}
},
"renamedIds": {
"minecraft:yellow_flower": "minecraft:dandelion"
}
}

View File

@@ -0,0 +1,52 @@
{
"remappedMetas": {
"minecraft:chemistry_table": {
"0": "minecraft:compound_creator"
},
"minecraft:cobblestone_wall": {
"1": "minecraft:mossy_cobblestone_wall",
"10": "minecraft:end_stone_brick_wall",
"11": "minecraft:prismarine_wall",
"12": "minecraft:red_sandstone_wall",
"13": "minecraft:red_nether_brick_wall",
"2": "minecraft:granite_wall",
"3": "minecraft:diorite_wall",
"4": "minecraft:andesite_wall",
"5": "minecraft:sandstone_wall",
"6": "minecraft:brick_wall",
"7": "minecraft:stone_brick_wall",
"8": "minecraft:mossy_stone_brick_wall",
"9": "minecraft:nether_brick_wall"
},
"minecraft:colored_torch_bp": {
"0": "minecraft:colored_torch_blue",
"10": "minecraft:colored_torch_purple",
"11": "minecraft:colored_torch_purple",
"12": "minecraft:colored_torch_purple",
"13": "minecraft:colored_torch_purple",
"14": "minecraft:colored_torch_purple",
"15": "minecraft:colored_torch_purple",
"8": "minecraft:colored_torch_purple",
"9": "minecraft:colored_torch_purple"
},
"minecraft:colored_torch_rg": {
"0": "minecraft:colored_torch_red",
"10": "minecraft:colored_torch_green",
"11": "minecraft:colored_torch_green",
"12": "minecraft:colored_torch_green",
"13": "minecraft:colored_torch_green",
"14": "minecraft:colored_torch_green",
"15": "minecraft:colored_torch_green",
"8": "minecraft:colored_torch_green",
"9": "minecraft:colored_torch_green"
},
"minecraft:purpur_block": {
"1": "minecraft:deprecated_purpur_block_1",
"2": "minecraft:purpur_pillar",
"3": "minecraft:deprecated_purpur_block_2"
},
"minecraft:sponge": {
"1": "minecraft:wet_sponge"
}
}
}

View File

@@ -0,0 +1,13 @@
{
"remappedMetas": {
"minecraft:skull": {
"0": "minecraft:skeleton_skull",
"1": "minecraft:wither_skeleton_skull",
"2": "minecraft:zombie_head",
"3": "minecraft:player_head",
"4": "minecraft:creeper_head",
"5": "minecraft:dragon_head",
"6": "minecraft:piglin_head"
}
}
}

109
internal/item/upgrader.go Normal file
View File

@@ -0,0 +1,109 @@
package item
import (
"github.com/df-mc/worldupgrader/blockupgrader"
)
// Item ...
type Item struct {
Name string
Metadata uint32
Version uint16
}
// Upgrade upgrades the given item using the registered item upgrade schemas.
func Upgrade(item Item, ver uint16) Item {
version := item.Version
for id, s := range schemas {
if version > id || id > ver {
continue
}
name := item.Name
metadata := item.Metadata
nameRenamed := false
metadataRemapped := false
if metadataCombinations, ok := s.RemappedMetas[name]; ok {
name, metadataRemapped = metadataCombinations[item.Metadata]
metadata = 0
} else if name, nameRenamed = s.RenamedIDs[item.Name]; !nameRenamed {
name = item.Name
}
if nameRenamed || metadataRemapped {
item = Item{
Name: name,
Metadata: metadata,
Version: id,
}
}
}
return item
}
// Downgrade downgrades the given item using the registered item upgrade schemas.
func Downgrade(item Item, ver uint16) Item {
for i, id := range schemaIDs {
s := schemas[uint16(id)]
if uint16(id) > item.Version {
continue
}
name := item.Name
metadata := item.Metadata
nameRenamed := false
metadataRemapped := false
for oldName, newName := range s.RenamedIDs {
if newName == name {
name = oldName
nameRenamed = true
}
}
if !nameRenamed {
for oldName, m := range s.RemappedMetas {
for meta, newName := range m {
if newName == name {
name = oldName
metadata = meta
nameRenamed = true
metadataRemapped = true
}
}
if metadataRemapped {
break
}
}
}
if ver > item.Version || i == len(schemaIDs)-1 {
break
}
return Downgrade(Item{
Name: name,
Metadata: metadata,
Version: uint16(schemaIDs[i+1]),
}, ver)
}
return item
}
func BlockStateFromItemName(itemName string, metadata uint32) (blockupgrader.BlockState, bool) {
blockId, ok := itemToBlockIdMap[itemName]
if !ok {
return blockupgrader.BlockState{}, false
}
blockState, ok := blockStateMap[stateHash{
Name: blockId,
Metadata: metadata,
}]
return blockState, ok
}
func BlockStateFromItem(item Item) (blockupgrader.BlockState, bool) {
return BlockStateFromItemName(item.Name, item.Metadata)
}

41
internal/iterator.go Normal file
View File

@@ -0,0 +1,41 @@
package internal
type Iterator[T any] struct {
index int
values []T
}
func NewIterator[T any](values []T) *Iterator[T] {
return &Iterator[T]{values: values}
}
func (i *Iterator[T]) HasNext() bool {
if i.index < len(i.values) {
return true
}
return false
}
func (i *Iterator[T]) HasPrevious() bool {
if i.index > 0 {
return true
}
return false
}
func (i *Iterator[T]) Next() T {
if i.HasNext() {
value := i.values[i.index]
i.index++
return value
}
panic("no more elements")
}
func (i *Iterator[T]) Previous() T {
if i.HasPrevious() {
i.index--
return i.values[i.index]
}
panic("no more elements")
}

22
legacyver/bitset.go Normal file
View File

@@ -0,0 +1,22 @@
package legacyver
import "github.com/sandertv/gophertunnel/minecraft/protocol"
func fitBitset(b protocol.Bitset, size int) protocol.Bitset {
if b.Len() != size {
ret := protocol.NewBitset(size)
copySize := b.Len()
if copySize > size {
copySize = size
}
for i := 0; i < copySize; i++ {
if b.Load(i) {
ret.Set(i)
}
}
return ret
}
return b
}

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,340 @@
package legacyver
import (
"bytes"
_ "embed"
"github.com/akmalfairuz/legacy-version/internal/chunk"
"github.com/akmalfairuz/legacy-version/mapping"
"github.com/df-mc/dragonfly/server/block/cube"
"github.com/df-mc/dragonfly/server/world"
"github.com/sandertv/gophertunnel/minecraft"
"github.com/sandertv/gophertunnel/minecraft/nbt"
"github.com/sandertv/gophertunnel/minecraft/protocol"
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
)
const (
BlockVersionLatest int32 = (1 << 24) | (21 << 16) | (50 << 8) | 29
)
var (
//go:embed block_states_766.nbt
latestBlockStateData []byte
// latestBlockMapping is the BlockMapping used for translating blocks between versions.
latestBlockMapping = mapping.NewBlockMapping(latestBlockStateData)
// LatestNetworkPersistentEncoding is the Encoding used for sending a Chunk over network. It uses NBT, unlike NetworkEncoding.
LatestNetworkPersistentEncoding = chunk.NewNetworkPersistentEncoding(latestBlockMapping, BlockVersionLatest)
// LatestBlockPaletteEncoding is the paletteEncoding used for encoding a palette of block states encoded as NBT.
LatestBlockPaletteEncoding = chunk.NewBlockPaletteEncoding(latestBlockMapping, BlockVersionLatest)
)
type BlockTranslator interface {
// DowngradeBlockPackets downgrades the input block packets to legacy block packets.
DowngradeBlockPackets([]packet.Packet, *minecraft.Conn) (result []packet.Packet)
// UpgradeBlockPackets upgrades the input block packets to the latest block packets.
UpgradeBlockPackets([]packet.Packet, *minecraft.Conn) (result []packet.Packet)
}
type DefaultBlockTranslator struct {
mapping mapping.Block
latest mapping.Block
pse chunk.Encoding
pe chunk.PaletteEncoding
oldFormat bool
}
func NewBlockTranslator(mapping mapping.Block, latestMapping mapping.Block, pse chunk.Encoding, pe chunk.PaletteEncoding, oldFormat bool) *DefaultBlockTranslator {
return &DefaultBlockTranslator{mapping: mapping, latest: latestMapping, pse: pse, pe: pe, oldFormat: oldFormat}
}
func (t *DefaultBlockTranslator) DowngradeBlockPackets(pks []packet.Packet, conn *minecraft.Conn) (result []packet.Packet) {
for _, pk := range pks {
switch pk := pk.(type) {
case *packet.LevelChunk:
count := int(pk.SubChunkCount)
if count == protocol.SubChunkRequestModeLimitless || count == protocol.SubChunkRequestModeLimited {
break
}
buf := bytes.NewBuffer(pk.RawPayload)
writeBuf := bytes.NewBuffer(nil)
if !pk.CacheEnabled {
c, err := chunk.NetworkDecode(t.latest.Air(), buf, count, false, world.Overworld.Range(), LatestNetworkPersistentEncoding, LatestBlockPaletteEncoding)
if err != nil {
//fmt.Println(err)
break
}
c = t.DowngradeChunk(c)
payload, err := chunk.NetworkEncode(t.mapping.Air(), c, t.oldFormat, t.pe)
if err != nil {
//fmt.Println(err)
break
}
writeBuf.Write(payload)
pk.SubChunkCount = uint32(len(c.Sub()))
}
safeBytes := buf.Bytes()
countBorder, err := buf.ReadByte()
if err != nil {
pk.RawPayload = append(writeBuf.Bytes(), safeBytes...)
break
}
borderBytes := make([]byte, countBorder)
if _, err = buf.Read(borderBytes); err != nil {
pk.RawPayload = append(writeBuf.Bytes(), safeBytes...)
break
}
writeBuf.WriteByte(countBorder)
writeBuf.Write(borderBytes)
enc := nbt.NewEncoderWithEncoding(writeBuf, nbt.NetworkLittleEndian)
dec := nbt.NewDecoderWithEncoding(buf, nbt.NetworkLittleEndian)
for {
var decNbt map[string]any
if err = dec.Decode(&decNbt); err != nil {
break
}
t.mapping.DowngradeBlockActorData(decNbt)
if err = enc.Encode(decNbt); err != nil {
break
}
}
pk.RawPayload = append(writeBuf.Bytes(), buf.Bytes()...)
case *packet.SubChunk:
r := world.Overworld.Range()
if t.oldFormat {
r = cube.Range{0, 255}
}
for i, entry := range pk.SubChunkEntries {
if entry.Result == protocol.SubChunkResultSuccess {
buf := bytes.NewBuffer(entry.RawPayload)
writeBuf := bytes.NewBuffer(nil)
if !pk.CacheEnabled && !conn.ClientCacheEnabled() {
ind := byte(i)
subChunk, err := chunk.DecodeSubChunk(t.latest.Air(), r, buf, &ind, chunk.NetworkEncoding, LatestNetworkPersistentEncoding, LatestBlockPaletteEncoding)
if err != nil {
//fmt.Println(err)
continue
}
t.DowngradeSubChunk(subChunk)
writeBuf.Write(chunk.EncodeSubChunk(subChunk, chunk.NetworkEncoding, t.pe, chunk.SubChunkVersion9, r, int(ind)))
}
enc := nbt.NewEncoderWithEncoding(writeBuf, nbt.NetworkLittleEndian)
dec := nbt.NewDecoderWithEncoding(buf, nbt.NetworkLittleEndian)
for {
var decNbt map[string]any
if err := dec.Decode(&decNbt); err != nil {
break
}
t.mapping.DowngradeBlockActorData(decNbt)
if err := enc.Encode(decNbt); err != nil {
break
}
}
entry.RawPayload = append(writeBuf.Bytes(), buf.Bytes()...)
pk.SubChunkEntries[i] = entry
}
}
case *packet.ClientCacheMissResponse:
r := world.Overworld.Range()
if t.oldFormat {
r = cube.Range{0, 255}
}
for i, blob := range pk.Blobs {
buf := bytes.NewBuffer(blob.Payload)
ind := byte(0)
subChunk, err := chunk.DecodeSubChunk(t.latest.Air(), r, buf, &ind, chunk.NetworkEncoding, LatestNetworkPersistentEncoding, LatestBlockPaletteEncoding)
if err != nil {
// Has a possibility to be a biome, ignore then
continue
}
t.DowngradeSubChunk(subChunk)
blob.Payload = append(chunk.EncodeSubChunk(subChunk, chunk.NetworkEncoding, t.pe, chunk.SubChunkVersion9, r, int(ind)), buf.Bytes()...)
pk.Blobs[i] = blob
}
case *packet.UpdateSubChunkBlocks:
for i, block := range pk.Blocks {
block.BlockRuntimeID = t.DowngradeBlockRuntimeID(block.BlockRuntimeID)
pk.Blocks[i] = block
}
for i, block := range pk.Extra {
block.BlockRuntimeID = t.DowngradeBlockRuntimeID(block.BlockRuntimeID)
pk.Extra[i] = block
}
case *packet.UpdateBlock:
pk.NewBlockRuntimeID = t.DowngradeBlockRuntimeID(pk.NewBlockRuntimeID)
case *packet.UpdateBlockSynced:
pk.NewBlockRuntimeID = t.DowngradeBlockRuntimeID(pk.NewBlockRuntimeID)
case *packet.InventoryTransaction:
if transactionData, ok := pk.TransactionData.(*protocol.UseItemTransactionData); ok {
transactionData.BlockRuntimeID = t.DowngradeBlockRuntimeID(transactionData.BlockRuntimeID)
pk.TransactionData = transactionData
}
case *packet.LevelEvent:
switch pk.EventType {
case packet.LevelEventParticleLegacyEvent | 20: // terrain
fallthrough
case packet.LevelEventParticlesDestroyBlock:
fallthrough
case packet.LevelEventParticlesDestroyBlockNoSound:
pk.EventData = int32(t.DowngradeBlockRuntimeID(uint32(pk.EventData)))
case packet.LevelEventParticlesCrackBlock:
face := pk.EventData >> 24
rid := t.DowngradeBlockRuntimeID(uint32(pk.EventData & 0xffff))
pk.EventData = int32(rid) | (face << 24)
}
case *packet.LevelSoundEvent:
switch pk.SoundType {
case packet.SoundEventBreak:
fallthrough
case packet.SoundEventPlace:
fallthrough
case packet.SoundEventHit:
fallthrough
case packet.SoundEventLand:
fallthrough
case packet.SoundEventItemUseOn:
pk.ExtraData = int32(t.DowngradeBlockRuntimeID(uint32(pk.ExtraData)))
}
case *packet.AddActor:
if pk.EntityType == "minecraft:falling_block" {
pk.EntityMetadata = t.downgradeEntityMetadata(pk.EntityMetadata)
}
case *packet.SetActorData:
pk.EntityMetadata = t.downgradeEntityMetadata(pk.EntityMetadata)
case *packet.StartGame:
t.latest.Adjust(pk.Blocks)
t.mapping.Adjust(pk.Blocks)
case *packet.ResourcePackStack:
var packs []protocol.StackResourcePack
for _, pack := range pk.TexturePacks {
if pack.UUID == "0fba4063-dba1-4281-9b89-ff9390653530" {
continue
}
packs = append(packs, pack)
}
pk.TexturePacks = packs
}
result = append(result, pk)
}
return result
}
func (t *DefaultBlockTranslator) UpgradeBlockPackets(pks []packet.Packet, conn *minecraft.Conn) (result []packet.Packet) {
for _, pk := range pks {
switch pk := pk.(type) {
case *packet.InventoryTransaction:
if transactionData, ok := pk.TransactionData.(*protocol.UseItemTransactionData); ok {
transactionData.BlockRuntimeID = t.UpgradeBlockRuntimeID(transactionData.BlockRuntimeID)
pk.TransactionData = transactionData
}
case *packet.SetActorData:
pk.EntityMetadata = t.upgradeEntityMetadata(pk.EntityMetadata)
}
result = append(result, pk)
}
return result
}
func (t *DefaultBlockTranslator) DowngradeBlockRuntimeID(input uint32) uint32 {
if t.latest == t.mapping {
return input
}
state, ok := t.latest.RuntimeIDToState(input)
if !ok {
return t.mapping.Air()
}
runtimeID, ok := t.mapping.StateToRuntimeID(state)
if !ok {
return t.mapping.Air()
}
return runtimeID
}
func (t *DefaultBlockTranslator) DowngradeChunk(input *chunk.Chunk) *chunk.Chunk {
if t.latest == t.mapping {
return input
}
start := 0
r := world.Overworld.Range()
if t.oldFormat {
start = 4
r = cube.Range{0, 255}
}
downgraded := chunk.New(t.mapping.Air(), r)
i := 0
// First downgrade the blocks.
for _, sub := range input.Sub()[start : len(input.Sub())-start] {
t.DowngradeSubChunk(sub)
downgraded.Sub()[i] = sub
i += 1
}
i = 0
// Then downgrade the biome ids.
for _, sub := range input.BiomeSub()[start : len(input.BiomeSub())-start] {
// todo
sub.Palette().Replace(func(v uint32) uint32 {
return 0 // at least the client doesn't crash now
})
downgraded.BiomeSub()[i] = sub
i += 1
}
return downgraded
}
func (t *DefaultBlockTranslator) DowngradeSubChunk(input *chunk.SubChunk) {
if t.latest == t.mapping {
return
}
for _, storage := range input.Layers() {
storage.Palette().Replace(t.DowngradeBlockRuntimeID)
}
}
func (t *DefaultBlockTranslator) downgradeEntityMetadata(metadata map[uint32]any) map[uint32]any {
if t.latest == t.mapping {
return metadata
}
if latestRID, ok := metadata[protocol.EntityDataKeyVariant]; ok {
metadata[protocol.EntityDataKeyVariant] = int32(t.DowngradeBlockRuntimeID(uint32(latestRID.(int32))))
}
return metadata
}
func (t *DefaultBlockTranslator) UpgradeBlockRuntimeID(input uint32) uint32 {
if t.latest == t.mapping {
return input
}
state, ok := t.mapping.RuntimeIDToState(input)
if !ok {
return t.latest.Air()
}
runtimeID, ok := t.latest.StateToRuntimeID(state)
if !ok {
return t.latest.Air()
}
return runtimeID
}
func (t *DefaultBlockTranslator) upgradeEntityMetadata(metadata map[uint32]any) map[uint32]any {
if t.latest == t.mapping {
return metadata
}
if latestRID, ok := metadata[protocol.EntityDataKeyVariant]; ok {
metadata[protocol.EntityDataKeyVariant] = int32(t.UpgradeBlockRuntimeID(uint32(latestRID.(int32))))
}
return metadata
}

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,767 @@
package legacyver
import (
"fmt"
"github.com/akmalfairuz/legacy-version/internal/item"
"github.com/akmalfairuz/legacy-version/mapping"
"github.com/akmalfairuz/legacy-version/packbuilder"
"github.com/df-mc/dragonfly/server/world"
"github.com/samber/lo"
"github.com/sandertv/gophertunnel/minecraft"
"github.com/sandertv/gophertunnel/minecraft/protocol"
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
)
type ItemTranslator interface {
// DowngradeItemType downgrades the input item type to a legacy item type.
DowngradeItemType(input protocol.ItemType) protocol.ItemType
// DowngradeItemStack downgrades the input item stack to a legacy item stack.
DowngradeItemStack(input protocol.ItemStack) protocol.ItemStack
// DowngradeItemInstance downgrades the input item instance to a legacy item instance.
DowngradeItemInstance(input protocol.ItemInstance) protocol.ItemInstance
// DowngradeItemDescriptor downgrades the input item descriptor to a legacy item descriptor.
DowngradeItemDescriptor(input protocol.ItemDescriptor) protocol.ItemDescriptor
// DowngradeItemDescriptorCount downgrades the input item descriptor (with count) to a legacy item descriptor (with count).
DowngradeItemDescriptorCount(input protocol.ItemDescriptorCount) protocol.ItemDescriptorCount
DowngradeItemPackets(pks []packet.Packet, conn *minecraft.Conn) []packet.Packet
// UpgradeItemType upgrades the input item type to the latest item type.
UpgradeItemType(input protocol.ItemType) protocol.ItemType
// UpgradeItemStack upgrades the input item stack to the latest item stack.
UpgradeItemStack(input protocol.ItemStack) protocol.ItemStack
// UpgradeItemInstance upgrades the input item instance to the latest item instance.
UpgradeItemInstance(input protocol.ItemInstance) protocol.ItemInstance
// UpgradeItemDescriptor upgrades the input item descriptor to the latest item descriptor.
UpgradeItemDescriptor(input protocol.ItemDescriptor) protocol.ItemDescriptor
// UpgradeItemDescriptorCount upgrades the input item descriptor (with count) to the latest item descriptor (with count).
UpgradeItemDescriptorCount(input protocol.ItemDescriptorCount) protocol.ItemDescriptorCount
UpgradeItemPackets(pks []packet.Packet, conn *minecraft.Conn) []packet.Packet
// Register registers a custom item entry.
Register(item world.CustomItem, replacement string)
// CustomItems lists all custom items used as substitutes, with the runtime id as the key
CustomItems() map[int32]world.CustomItem
}
type DefaultItemTranslator struct {
mapping mapping.Item
latest mapping.Item
blockMapping mapping.Block
blockMappingLatest mapping.Block
ridToCustomItem map[int32]world.CustomItem
originalToCustom map[int32]int32
customToOriginal map[int32]int32
}
func NewItemTranslator(mapping mapping.Item, latestMapping mapping.Item, blockMapping mapping.Block, blockMappingLatest mapping.Block) *DefaultItemTranslator {
return &DefaultItemTranslator{mapping: mapping, latest: latestMapping, blockMapping: blockMapping, blockMappingLatest: blockMappingLatest,
ridToCustomItem: make(map[int32]world.CustomItem), originalToCustom: make(map[int32]int32), customToOriginal: make(map[int32]int32)}
}
func (t *DefaultItemTranslator) DowngradeItemType(input protocol.ItemType) protocol.ItemType {
if t.latest == t.mapping {
return input
}
if input.NetworkID == t.latest.Air() || input.NetworkID == 0 {
return protocol.ItemType{
NetworkID: t.mapping.Air(),
}
}
networkID := input.NetworkID
metadata := input.MetadataValue
var ok bool
if networkID, ok = t.originalToCustom[input.NetworkID]; !ok {
name, _ := t.latest.ItemRuntimeIDToName(input.NetworkID)
i := item.Downgrade(item.Item{
Name: name,
Metadata: input.MetadataValue,
Version: t.latest.ItemVersion(),
}, t.mapping.ItemVersion())
metadata = i.Metadata
if i.Name == "minecraft:netherstar" {
i.Name = "minecraft:nether_star"
}
networkID, ok = t.mapping.ItemNameToRuntimeID(i.Name)
if !ok {
networkID, _ = t.mapping.ItemNameToRuntimeID("minecraft:info_update")
}
}
return protocol.ItemType{
NetworkID: networkID,
MetadataValue: metadata,
}
}
func (t *DefaultItemTranslator) DowngradeItemStack(input protocol.ItemStack) protocol.ItemStack {
if t.latest == t.mapping {
return input
}
input.ItemType = t.DowngradeItemType(input.ItemType)
blockRuntimeId := uint32(0)
if input.NetworkID != t.mapping.Air() {
name, _ := t.mapping.ItemRuntimeIDToName(input.NetworkID)
if latestBlockState, ok := item.BlockStateFromItemName(name, input.MetadataValue); ok {
var found bool
if blockRuntimeId, found = t.blockMapping.StateToRuntimeID(latestBlockState); !found {
blockRuntimeId = t.blockMapping.Air()
}
}
}
return protocol.ItemStack{
ItemType: input.ItemType,
BlockRuntimeID: int32(blockRuntimeId),
Count: input.Count,
NBTData: input.NBTData,
CanBePlacedOn: input.CanBePlacedOn,
CanBreak: input.CanBreak,
HasNetworkID: input.HasNetworkID,
}
}
func (t *DefaultItemTranslator) DowngradeItemInstance(input protocol.ItemInstance) protocol.ItemInstance {
if t.latest == t.mapping {
return input
}
input.Stack = t.DowngradeItemStack(input.Stack)
return input
}
func (t *DefaultItemTranslator) DowngradeItemDescriptor(input protocol.ItemDescriptor) protocol.ItemDescriptor {
if t.latest == t.mapping {
return input
}
switch descriptor := input.(type) {
case *protocol.InvalidItemDescriptor:
return input
case *protocol.DefaultItemDescriptor:
itemType := t.DowngradeItemType(protocol.ItemType{NetworkID: int32(descriptor.NetworkID), MetadataValue: uint32(descriptor.MetadataValue)})
descriptor.NetworkID, descriptor.MetadataValue = int16(itemType.NetworkID), int16(itemType.MetadataValue)
return descriptor
case *protocol.MoLangItemDescriptor:
return input
case *protocol.ItemTagItemDescriptor:
return input
case *protocol.DeferredItemDescriptor:
rid, ok := t.latest.ItemNameToRuntimeID(descriptor.Name)
descriptor.Name = "minecraft:air"
if !ok {
descriptor.MetadataValue = 0
return descriptor
}
itemType := t.DowngradeItemType(protocol.ItemType{NetworkID: rid, MetadataValue: uint32(descriptor.MetadataValue)})
descriptor.MetadataValue = int16(itemType.MetadataValue)
if name, ok := t.mapping.ItemRuntimeIDToName(itemType.NetworkID); ok {
descriptor.Name = name
}
return descriptor
case *protocol.ComplexAliasItemDescriptor:
rid, ok := t.latest.ItemNameToRuntimeID(descriptor.Name)
descriptor.Name = "minecraft:air"
if !ok {
return descriptor
}
itemType := t.DowngradeItemType(protocol.ItemType{NetworkID: rid})
if name, ok := t.mapping.ItemRuntimeIDToName(itemType.NetworkID); ok {
descriptor.Name = name
}
return descriptor
}
panic("unknown item descriptor")
}
func (t *DefaultItemTranslator) DowngradeItemDescriptorCount(input protocol.ItemDescriptorCount) protocol.ItemDescriptorCount {
if t.latest == t.mapping {
return input
}
input.Descriptor = t.DowngradeItemDescriptor(input.Descriptor)
return input
}
func (t *DefaultItemTranslator) UpgradeItemType(input protocol.ItemType) protocol.ItemType {
if t.latest == t.mapping {
return input
}
if input.NetworkID == t.mapping.Air() || input.NetworkID == 0 {
return protocol.ItemType{
NetworkID: t.latest.Air(),
}
}
networkID := input.NetworkID
metadata := input.MetadataValue
var ok bool
if networkID, ok = t.customToOriginal[input.NetworkID]; !ok {
name, _ := t.mapping.ItemRuntimeIDToName(input.NetworkID)
i := item.Upgrade(item.Item{
Name: name,
Metadata: input.MetadataValue,
Version: t.mapping.ItemVersion(),
}, t.latest.ItemVersion())
networkID, ok = t.latest.ItemNameToRuntimeID(i.Name)
if !ok {
networkID, _ = t.latest.ItemNameToRuntimeID("minecraft:info_update")
}
}
return protocol.ItemType{
NetworkID: networkID,
MetadataValue: metadata,
}
}
func (t *DefaultItemTranslator) UpgradeItemStack(input protocol.ItemStack) protocol.ItemStack {
if t.latest == t.mapping {
return input
}
input.ItemType = t.UpgradeItemType(input.ItemType)
blockRuntimeId := uint32(0)
if input.NetworkID != t.latest.Air() {
name, _ := t.latest.ItemRuntimeIDToName(input.NetworkID)
if latestBlockState, ok := item.BlockStateFromItemName(name, input.MetadataValue); ok {
blockRuntimeId, _ = t.blockMappingLatest.StateToRuntimeID(latestBlockState)
}
}
return protocol.ItemStack{
ItemType: input.ItemType,
BlockRuntimeID: int32(blockRuntimeId),
Count: input.Count,
NBTData: input.NBTData,
CanBePlacedOn: input.CanBePlacedOn,
CanBreak: input.CanBreak,
HasNetworkID: input.HasNetworkID,
}
}
func (t *DefaultItemTranslator) UpgradeItemInstance(input protocol.ItemInstance) protocol.ItemInstance {
if t.latest == t.mapping {
return input
}
input.Stack = t.UpgradeItemStack(input.Stack)
return input
}
func (t *DefaultItemTranslator) UpgradeItemDescriptor(input protocol.ItemDescriptor) protocol.ItemDescriptor {
if t.latest == t.mapping {
return input
}
switch descriptor := input.(type) {
case *protocol.InvalidItemDescriptor:
return input
case *protocol.DefaultItemDescriptor:
itemType := t.UpgradeItemType(protocol.ItemType{NetworkID: int32(descriptor.NetworkID), MetadataValue: uint32(descriptor.MetadataValue)})
descriptor.NetworkID, descriptor.MetadataValue = int16(itemType.NetworkID), int16(itemType.MetadataValue)
return descriptor
case *protocol.MoLangItemDescriptor:
return input
case *protocol.ItemTagItemDescriptor:
return input
case *protocol.DeferredItemDescriptor:
rid, ok := t.mapping.ItemNameToRuntimeID(descriptor.Name)
descriptor.Name = "minecraft:air"
if !ok {
descriptor.MetadataValue = 0
return descriptor
}
itemType := t.UpgradeItemType(protocol.ItemType{NetworkID: rid, MetadataValue: uint32(descriptor.MetadataValue)})
descriptor.MetadataValue = int16(itemType.MetadataValue)
if name, ok := t.latest.ItemRuntimeIDToName(itemType.NetworkID); ok {
descriptor.Name = name
}
return descriptor
case *protocol.ComplexAliasItemDescriptor:
rid, ok := t.mapping.ItemNameToRuntimeID(descriptor.Name)
descriptor.Name = "minecraft:air"
if !ok {
return descriptor
}
itemType := t.UpgradeItemType(protocol.ItemType{NetworkID: rid})
if name, ok := t.latest.ItemRuntimeIDToName(itemType.NetworkID); ok {
descriptor.Name = name
}
return descriptor
}
panic("unknown item descriptor")
}
func (t *DefaultItemTranslator) UpgradeItemDescriptorCount(input protocol.ItemDescriptorCount) protocol.ItemDescriptorCount {
if t.latest == t.mapping {
return input
}
input.Descriptor = t.UpgradeItemDescriptor(input.Descriptor)
return input
}
func (t *DefaultItemTranslator) DowngradeItemPackets(pks []packet.Packet, _ *minecraft.Conn) (result []packet.Packet) {
for _, pk := range pks {
switch pk := pk.(type) {
case *packet.MobEquipment:
pk.NewItem = t.DowngradeItemInstance(pk.NewItem)
case *packet.MobArmourEquipment:
pk.Helmet = t.DowngradeItemInstance(pk.Helmet)
pk.Chestplate = t.DowngradeItemInstance(pk.Chestplate)
pk.Leggings = t.DowngradeItemInstance(pk.Leggings)
pk.Boots = t.DowngradeItemInstance(pk.Boots)
pk.Body = t.DowngradeItemInstance(pk.Body)
case *packet.ActorEvent:
if pk.EventType == packet.ActorEventFeed {
value := pk.EventData
itemType := t.DowngradeItemType(protocol.ItemType{NetworkID: value >> 16, MetadataValue: uint32(value & 0xf)})
pk.EventData = (itemType.NetworkID << 16) | int32(itemType.MetadataValue)
}
case *packet.AddItemActor:
pk.Item = t.DowngradeItemInstance(pk.Item)
case *packet.AddPlayer:
pk.HeldItem = t.DowngradeItemInstance(pk.HeldItem)
case *packet.InventorySlot:
pk.NewItem = t.DowngradeItemInstance(pk.NewItem)
case *packet.InventoryContent:
pk.Content = lo.Map(pk.Content, func(item protocol.ItemInstance, _ int) protocol.ItemInstance {
return t.DowngradeItemInstance(item)
})
case *packet.ItemStackRequest:
for i, request := range pk.Requests {
for i2, action := range request.Actions {
if act, ok := action.(*protocol.CraftResultsDeprecatedStackRequestAction); ok {
act.ResultItems = lo.Map(act.ResultItems, func(item protocol.ItemStack, _ int) protocol.ItemStack {
return t.DowngradeItemStack(item)
})
pk.Requests[i].Actions[i2] = act
}
}
}
case *packet.CraftingData:
for i, recipe := range pk.Recipes {
switch recipe := recipe.(type) {
case *protocol.ShapelessRecipe:
for i2, input := range recipe.Input {
recipe.Input[i2] = t.DowngradeItemDescriptorCount(input)
}
for i2, stack := range recipe.Output {
recipe.Output[i2] = t.DowngradeItemStack(stack)
}
pk.Recipes[i] = recipe
case *protocol.ShapedRecipe:
for i2, input := range recipe.Input {
recipe.Input[i2] = t.DowngradeItemDescriptorCount(input)
}
for i2, stack := range recipe.Output {
recipe.Output[i2] = t.DowngradeItemStack(stack)
}
pk.Recipes[i] = recipe
case *protocol.FurnaceRecipe:
recipe.InputType = t.DowngradeItemType(recipe.InputType)
recipe.Output = t.DowngradeItemStack(recipe.Output)
pk.Recipes[i] = recipe
case *protocol.FurnaceDataRecipe:
recipe.InputType = t.DowngradeItemType(recipe.InputType)
recipe.Output = t.DowngradeItemStack(recipe.Output)
pk.Recipes[i] = recipe
case *protocol.ShulkerBoxRecipe:
for i2, input := range recipe.Input {
recipe.Input[i2] = t.DowngradeItemDescriptorCount(input)
}
for i2, stack := range recipe.Output {
recipe.Output[i2] = t.DowngradeItemStack(stack)
}
pk.Recipes[i] = recipe
case *protocol.ShapelessChemistryRecipe:
for i2, input := range recipe.Input {
recipe.Input[i2] = t.DowngradeItemDescriptorCount(input)
}
for i2, stack := range recipe.Output {
recipe.Output[i2] = t.DowngradeItemStack(stack)
}
pk.Recipes[i] = recipe
case *protocol.ShapedChemistryRecipe:
for i2, input := range recipe.Input {
recipe.Input[i2] = t.DowngradeItemDescriptorCount(input)
}
for i2, stack := range recipe.Output {
recipe.Output[i2] = t.DowngradeItemStack(stack)
}
pk.Recipes[i] = recipe
case *protocol.SmithingTransformRecipe:
recipe.Template = t.DowngradeItemDescriptorCount(recipe.Template)
recipe.Base = t.DowngradeItemDescriptorCount(recipe.Base)
recipe.Addition = t.DowngradeItemDescriptorCount(recipe.Addition)
recipe.Result = t.DowngradeItemStack(recipe.Result)
pk.Recipes[i] = recipe
case *protocol.SmithingTrimRecipe:
recipe.Template = t.DowngradeItemDescriptorCount(recipe.Template)
recipe.Base = t.DowngradeItemDescriptorCount(recipe.Base)
recipe.Addition = t.DowngradeItemDescriptorCount(recipe.Addition)
}
}
for i, recipe := range pk.PotionRecipes {
itemType := t.DowngradeItemType(protocol.ItemType{NetworkID: recipe.InputPotionID, MetadataValue: uint32(recipe.InputPotionMetadata)})
recipe.InputPotionID, recipe.InputPotionMetadata = itemType.NetworkID, int32(itemType.MetadataValue)
itemType = t.DowngradeItemType(protocol.ItemType{NetworkID: recipe.ReagentItemID, MetadataValue: uint32(recipe.ReagentItemMetadata)})
recipe.ReagentItemID, recipe.ReagentItemMetadata = itemType.NetworkID, int32(itemType.MetadataValue)
itemType = t.DowngradeItemType(protocol.ItemType{NetworkID: recipe.OutputPotionID, MetadataValue: uint32(recipe.OutputPotionMetadata)})
recipe.OutputPotionID, recipe.OutputPotionMetadata = itemType.NetworkID, int32(itemType.MetadataValue)
pk.PotionRecipes[i] = recipe
}
for i, recipe := range pk.PotionContainerChangeRecipes {
itemType := t.DowngradeItemType(protocol.ItemType{NetworkID: recipe.InputItemID})
recipe.InputItemID = itemType.NetworkID
itemType = t.DowngradeItemType(protocol.ItemType{NetworkID: recipe.ReagentItemID})
recipe.ReagentItemID = itemType.NetworkID
itemType = t.DowngradeItemType(protocol.ItemType{NetworkID: recipe.OutputItemID})
recipe.OutputItemID = itemType.NetworkID
pk.PotionContainerChangeRecipes[i] = recipe
}
for i, recipe := range pk.MaterialReducers {
recipe.InputItem = t.DowngradeItemType(recipe.InputItem)
for i2, output := range recipe.Outputs {
itemType := t.DowngradeItemType(protocol.ItemType{NetworkID: output.NetworkID})
output.NetworkID = itemType.NetworkID
recipe.Outputs[i2] = output
}
pk.MaterialReducers[i] = recipe
}
//case *packet.CraftingEvent:
// pk.Input = lo.Map(pk.Input, func(item protocol.ItemInstance, _ int) protocol.ItemInstance {
// return t.DowngradeItemInstance(item)
// })
// pk.Output = lo.Map(pk.Output, func(item protocol.ItemInstance, _ int) protocol.ItemInstance {
// return t.DowngradeItemInstance(item)
// })
case *packet.PlayerAuthInput:
for i, action := range pk.ItemStackRequest.Actions {
if act, ok := action.(*protocol.CraftResultsDeprecatedStackRequestAction); ok {
act.ResultItems = lo.Map(act.ResultItems, func(item protocol.ItemStack, _ int) protocol.ItemStack {
return t.DowngradeItemStack(item)
})
pk.ItemStackRequest.Actions[i] = act
}
}
for i, action := range pk.ItemInteractionData.Actions {
action.OldItem = t.DowngradeItemInstance(action.OldItem)
action.NewItem = t.DowngradeItemInstance(action.NewItem)
pk.ItemInteractionData.Actions[i] = action
}
pk.ItemInteractionData.HeldItem = t.DowngradeItemInstance(pk.ItemInteractionData.HeldItem)
case *packet.CreativeContent:
for i, creativeItem := range pk.Items {
creativeItem.Item = t.DowngradeItemStack(creativeItem.Item)
pk.Items[i] = creativeItem
}
case *packet.InventoryTransaction:
for i, action := range pk.Actions {
action.OldItem = t.DowngradeItemInstance(action.OldItem)
action.NewItem = t.DowngradeItemInstance(action.NewItem)
pk.Actions[i] = action
}
switch transactionData := pk.TransactionData.(type) {
case *protocol.UseItemTransactionData:
transactionData.HeldItem = t.DowngradeItemInstance(transactionData.HeldItem)
for i, action := range transactionData.Actions {
action.OldItem = t.DowngradeItemInstance(action.OldItem)
action.NewItem = t.DowngradeItemInstance(action.NewItem)
transactionData.Actions[i] = action
}
case *protocol.UseItemOnEntityTransactionData:
transactionData.HeldItem = t.DowngradeItemInstance(transactionData.HeldItem)
case *protocol.ReleaseItemTransactionData:
transactionData.HeldItem = t.DowngradeItemInstance(transactionData.HeldItem)
}
case *packet.LevelEvent:
if pk.EventType == packet.LevelEventParticleLegacyEvent|14 { // egg crack
itemType := t.DowngradeItemType(protocol.ItemType{
NetworkID: pk.EventData >> 16,
MetadataValue: uint32(pk.EventData & 0xf),
})
pk.EventData = (itemType.NetworkID << 16) | int32(itemType.MetadataValue)
}
case *packet.StartGame:
for i, entry := range pk.Items {
if !entry.ComponentBased {
itemType := t.DowngradeItemType(protocol.ItemType{
NetworkID: int32(entry.RuntimeID),
MetadataValue: 0,
})
if itemType.NetworkID == t.mapping.Air() {
removeIndex(pk.Items, i)
continue
}
entry.RuntimeID = int16(itemType.NetworkID)
var ok bool
if entry.Name, ok = t.mapping.ItemRuntimeIDToName(itemType.NetworkID); !ok {
panic(itemType)
}
} else {
t.latest.RegisterEntry(entry.Name)
entry.RuntimeID = int16(t.mapping.RegisterEntry(entry.Name))
}
pk.Items[i] = entry
}
for rid, i := range t.CustomItems() {
name, _ := i.EncodeItem()
pk.Items = append(pk.Items, protocol.ItemEntry{
Name: name,
RuntimeID: int16(rid),
ComponentBased: true,
})
}
case *packet.ItemComponent:
for _, i := range t.CustomItems() {
name, _ := i.EncodeItem()
pk.Items = append(pk.Items, protocol.ItemComponentEntry{
Name: name,
Data: packbuilder.Components(i),
})
}
}
result = append(result, pk)
}
return result
}
func (t *DefaultItemTranslator) UpgradeItemPackets(pks []packet.Packet, _ *minecraft.Conn) (result []packet.Packet) {
for _, pk := range pks {
switch pk := pk.(type) {
case *packet.MobEquipment:
pk.NewItem = t.UpgradeItemInstance(pk.NewItem)
case *packet.MobArmourEquipment:
pk.Helmet = t.UpgradeItemInstance(pk.Helmet)
pk.Chestplate = t.UpgradeItemInstance(pk.Chestplate)
pk.Leggings = t.UpgradeItemInstance(pk.Leggings)
pk.Boots = t.UpgradeItemInstance(pk.Boots)
pk.Body = t.UpgradeItemInstance(pk.Body)
case *packet.AddItemActor:
pk.Item = t.UpgradeItemInstance(pk.Item)
case *packet.AddPlayer:
pk.HeldItem = t.UpgradeItemInstance(pk.HeldItem)
case *packet.InventorySlot:
pk.NewItem = t.UpgradeItemInstance(pk.NewItem)
case *packet.InventoryContent:
pk.Content = lo.Map(pk.Content, func(item protocol.ItemInstance, _ int) protocol.ItemInstance {
return t.UpgradeItemInstance(item)
})
case *packet.ItemStackRequest:
for i, request := range pk.Requests {
for i2, action := range request.Actions {
if act, ok := action.(*protocol.CraftResultsDeprecatedStackRequestAction); ok {
act.ResultItems = lo.Map(act.ResultItems, func(item protocol.ItemStack, _ int) protocol.ItemStack {
return t.UpgradeItemStack(item)
})
pk.Requests[i].Actions[i2] = act
}
}
}
case *packet.CraftingData:
for i, recipe := range pk.Recipes {
switch recipe := recipe.(type) {
case *protocol.ShapelessRecipe:
for i2, input := range recipe.Input {
recipe.Input[i2] = t.UpgradeItemDescriptorCount(input)
}
for i2, stack := range recipe.Output {
recipe.Output[i2] = t.UpgradeItemStack(stack)
}
pk.Recipes[i] = recipe
case *protocol.ShapedRecipe:
for i2, input := range recipe.Input {
recipe.Input[i2] = t.UpgradeItemDescriptorCount(input)
}
for i2, stack := range recipe.Output {
recipe.Output[i2] = t.UpgradeItemStack(stack)
}
pk.Recipes[i] = recipe
case *protocol.FurnaceRecipe:
recipe.InputType = t.UpgradeItemType(recipe.InputType)
recipe.Output = t.UpgradeItemStack(recipe.Output)
pk.Recipes[i] = recipe
case *protocol.FurnaceDataRecipe:
recipe.InputType = t.UpgradeItemType(recipe.InputType)
recipe.Output = t.UpgradeItemStack(recipe.Output)
pk.Recipes[i] = recipe
case *protocol.ShulkerBoxRecipe:
for i2, input := range recipe.Input {
recipe.Input[i2] = t.UpgradeItemDescriptorCount(input)
}
for i2, stack := range recipe.Output {
recipe.Output[i2] = t.UpgradeItemStack(stack)
}
pk.Recipes[i] = recipe
case *protocol.ShapelessChemistryRecipe:
for i2, input := range recipe.Input {
recipe.Input[i2] = t.UpgradeItemDescriptorCount(input)
}
for i2, stack := range recipe.Output {
recipe.Output[i2] = t.UpgradeItemStack(stack)
}
pk.Recipes[i] = recipe
case *protocol.ShapedChemistryRecipe:
for i2, input := range recipe.Input {
recipe.Input[i2] = t.UpgradeItemDescriptorCount(input)
}
for i2, stack := range recipe.Output {
recipe.Output[i2] = t.UpgradeItemStack(stack)
}
pk.Recipes[i] = recipe
case *protocol.SmithingTransformRecipe:
recipe.Template = t.UpgradeItemDescriptorCount(recipe.Template)
recipe.Base = t.UpgradeItemDescriptorCount(recipe.Base)
recipe.Addition = t.UpgradeItemDescriptorCount(recipe.Addition)
recipe.Result = t.UpgradeItemStack(recipe.Result)
pk.Recipes[i] = recipe
case *protocol.SmithingTrimRecipe:
recipe.Template = t.UpgradeItemDescriptorCount(recipe.Template)
recipe.Base = t.UpgradeItemDescriptorCount(recipe.Base)
recipe.Addition = t.UpgradeItemDescriptorCount(recipe.Addition)
}
}
for i, recipe := range pk.PotionRecipes {
itemType := t.UpgradeItemType(protocol.ItemType{NetworkID: recipe.InputPotionID, MetadataValue: uint32(recipe.InputPotionMetadata)})
recipe.InputPotionID, recipe.InputPotionMetadata = itemType.NetworkID, int32(itemType.MetadataValue)
itemType = t.UpgradeItemType(protocol.ItemType{NetworkID: recipe.ReagentItemID, MetadataValue: uint32(recipe.ReagentItemMetadata)})
recipe.ReagentItemID, recipe.ReagentItemMetadata = itemType.NetworkID, int32(itemType.MetadataValue)
itemType = t.UpgradeItemType(protocol.ItemType{NetworkID: recipe.OutputPotionID, MetadataValue: uint32(recipe.OutputPotionMetadata)})
recipe.OutputPotionID, recipe.OutputPotionMetadata = itemType.NetworkID, int32(itemType.MetadataValue)
pk.PotionRecipes[i] = recipe
}
for i, recipe := range pk.PotionContainerChangeRecipes {
itemType := t.UpgradeItemType(protocol.ItemType{NetworkID: recipe.InputItemID})
recipe.InputItemID = itemType.NetworkID
itemType = t.UpgradeItemType(protocol.ItemType{NetworkID: recipe.ReagentItemID})
recipe.ReagentItemID = itemType.NetworkID
itemType = t.UpgradeItemType(protocol.ItemType{NetworkID: recipe.OutputItemID})
recipe.OutputItemID = itemType.NetworkID
pk.PotionContainerChangeRecipes[i] = recipe
}
for i, recipe := range pk.MaterialReducers {
recipe.InputItem = t.UpgradeItemType(recipe.InputItem)
for i2, output := range recipe.Outputs {
itemType := t.UpgradeItemType(protocol.ItemType{NetworkID: output.NetworkID})
output.NetworkID = itemType.NetworkID
recipe.Outputs[i2] = output
}
pk.MaterialReducers[i] = recipe
}
//case *packet.CraftingEvent:
// pk.Input = lo.Map(pk.Input, func(item protocol.ItemInstance, _ int) protocol.ItemInstance {
// return t.UpgradeItemInstance(item)
// })
// pk.Output = lo.Map(pk.Output, func(item protocol.ItemInstance, _ int) protocol.ItemInstance {
// return t.UpgradeItemInstance(item)
// })
case *packet.PlayerAuthInput:
for i, action := range pk.ItemStackRequest.Actions {
if act, ok := action.(*protocol.CraftResultsDeprecatedStackRequestAction); ok {
act.ResultItems = lo.Map(act.ResultItems, func(item protocol.ItemStack, _ int) protocol.ItemStack {
return t.UpgradeItemStack(item)
})
pk.ItemStackRequest.Actions[i] = act
}
}
for i, action := range pk.ItemInteractionData.Actions {
action.OldItem = t.UpgradeItemInstance(action.OldItem)
action.NewItem = t.UpgradeItemInstance(action.NewItem)
pk.ItemInteractionData.Actions[i] = action
}
pk.ItemInteractionData.HeldItem = t.UpgradeItemInstance(pk.ItemInteractionData.HeldItem)
case *packet.CreativeContent:
for i, creativeItem := range pk.Items {
creativeItem.Item = t.UpgradeItemStack(creativeItem.Item)
pk.Items[i] = creativeItem
}
case *packet.InventoryTransaction:
for i, action := range pk.Actions {
action.OldItem = t.UpgradeItemInstance(action.OldItem)
action.NewItem = t.UpgradeItemInstance(action.NewItem)
pk.Actions[i] = action
}
switch transactionData := pk.TransactionData.(type) {
case *protocol.UseItemTransactionData:
transactionData.HeldItem = t.UpgradeItemInstance(transactionData.HeldItem)
for i, action := range transactionData.Actions {
action.OldItem = t.UpgradeItemInstance(action.OldItem)
action.NewItem = t.UpgradeItemInstance(action.NewItem)
transactionData.Actions[i] = action
}
case *protocol.UseItemOnEntityTransactionData:
transactionData.HeldItem = t.UpgradeItemInstance(transactionData.HeldItem)
case *protocol.ReleaseItemTransactionData:
transactionData.HeldItem = t.UpgradeItemInstance(transactionData.HeldItem)
}
case *packet.LevelEvent:
if pk.EventType == packet.LevelEventParticleLegacyEvent|14 { // egg crack
itemType := t.UpgradeItemType(protocol.ItemType{
NetworkID: pk.EventData >> 16,
MetadataValue: uint32(pk.EventData & 0xf),
})
pk.EventData = (itemType.NetworkID << 16) | int32(itemType.MetadataValue)
}
case *packet.StartGame:
for i, entry := range pk.Items {
if !entry.ComponentBased {
itemType := t.UpgradeItemType(protocol.ItemType{
NetworkID: int32(entry.RuntimeID),
MetadataValue: 0,
})
entry.RuntimeID = int16(itemType.NetworkID)
var ok bool
if entry.Name, ok = t.latest.ItemRuntimeIDToName(itemType.NetworkID); !ok {
panic(itemType)
}
} else {
t.latest.RegisterEntry(entry.Name)
entry.RuntimeID = int16(t.mapping.RegisterEntry(entry.Name))
}
pk.Items[i] = entry
}
for rid, i := range t.CustomItems() {
name, _ := i.EncodeItem()
pk.Items = append(pk.Items, protocol.ItemEntry{
Name: name,
RuntimeID: int16(rid),
ComponentBased: true,
})
}
case *packet.ItemComponent:
for _, i := range t.CustomItems() {
name, _ := i.EncodeItem()
pk.Items = append(pk.Items, protocol.ItemComponentEntry{
Name: name,
Data: packbuilder.Components(i),
})
}
}
result = append(result, pk)
}
return result
}
func (t *DefaultItemTranslator) Register(item world.CustomItem, replacement string) {
name, _ := item.EncodeItem()
originalRid, ok := t.latest.ItemNameToRuntimeID(replacement)
if !ok {
panic(fmt.Errorf("%v not found in latest items", replacement))
}
if _, ok := t.originalToCustom[originalRid]; ok {
panic(fmt.Errorf("%v is already mapped", replacement))
}
nextRID := t.mapping.RegisterEntry(name)
t.ridToCustomItem[nextRID] = item
t.originalToCustom[originalRid] = nextRID
t.customToOriginal[nextRID] = originalRid
}
func (t *DefaultItemTranslator) CustomItems() map[int32]world.CustomItem {
return t.ridToCustomItem
}
func removeIndex[T any](s []T, index int) []T {
ret := make([]T, 0)
ret = append(ret, s[:index]...)
return append(ret, s[index+1:]...)
}

View File

@@ -0,0 +1,27 @@
package legacypacket
import (
"github.com/akmalfairuz/legacy-version/legacyver/proto"
"github.com/sandertv/gophertunnel/minecraft/protocol"
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
)
// ItemStackResponse is sent by the server in response to an ItemStackRequest packet from the client. This
// packet is used to either approve or reject ItemStackRequests from the client. If a request is approved, the
// client will simply continue as normal. If rejected, the client will undo the actions so that the inventory
// should be in sync with the server again.
type ItemStackResponse struct {
// Responses is a list of responses to ItemStackRequests sent by the client before. Responses either
// approve or reject a request from the client.
// Vanilla limits the size of this slice to 4096.
Responses []proto.ItemStackResponse
}
// ID ...
func (*ItemStackResponse) ID() uint32 {
return packet.IDItemStackResponse
}
func (pk *ItemStackResponse) Marshal(io protocol.IO) {
protocol.Slice(io, &pk.Responses)
}

View File

@@ -0,0 +1 @@
package legacypacket

View File

@@ -0,0 +1,210 @@
package legacypacket
import (
"github.com/akmalfairuz/legacy-version/legacyver/proto"
"github.com/go-gl/mathgl/mgl32"
"github.com/sandertv/gophertunnel/minecraft/protocol"
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
)
const PlayerAuthInputBitsetSize = 65
const (
InputFlagAscend = iota
InputFlagDescend
InputFlagNorthJump
InputFlagJumpDown
InputFlagSprintDown
InputFlagChangeHeight
InputFlagJumping
InputFlagAutoJumpingInWater
InputFlagSneaking
InputFlagSneakDown
InputFlagUp
InputFlagDown
InputFlagLeft
InputFlagRight
InputFlagUpLeft
InputFlagUpRight
InputFlagWantUp
InputFlagWantDown
InputFlagWantDownSlow
InputFlagWantUpSlow
InputFlagSprinting
InputFlagAscendBlock
InputFlagDescendBlock
InputFlagSneakToggleDown
InputFlagPersistSneak
InputFlagStartSprinting
InputFlagStopSprinting
InputFlagStartSneaking
InputFlagStopSneaking
InputFlagStartSwimming
InputFlagStopSwimming
InputFlagStartJumping
InputFlagStartGliding
InputFlagStopGliding
InputFlagPerformItemInteraction
InputFlagPerformBlockActions
InputFlagPerformItemStackRequest
InputFlagHandledTeleport
InputFlagEmoting
InputFlagMissedSwing
InputFlagStartCrawling
InputFlagStopCrawling
InputFlagStartFlying
InputFlagStopFlying
InputFlagClientAckServerData
InputFlagClientPredictedVehicle
InputFlagPaddlingLeft
InputFlagPaddlingRight
InputFlagBlockBreakingDelayEnabled
InputFlagHorizontalCollision
InputFlagVerticalCollision
InputFlagDownLeft
InputFlagDownRight
InputFlagStartUsingItem
InputFlagCameraRelativeMovementEnabled
InputFlagRotControlledByMoveDirection
InputFlagStartSpinAttack
InputFlagStopSpinAttack
InputFlagIsHotbarTouchOnly
InputFlagJumpReleasedRaw
InputFlagJumpPressedRaw
InputFlagJumpCurrentRaw
InputFlagSneakReleasedRaw
InputFlagSneakPressedRaw
InputFlagSneakCurrentRaw
)
const (
InputModeMouse = iota + 1
InputModeTouch
InputModeGamePad
InputModeMotionController
)
const (
PlayModeNormal = iota
PlayModeTeaser
PlayModeScreen
PlayModeViewer
PlayModeReality
PlayModePlacement
PlayModeLivingRoom
PlayModeExitLevel
PlayModeExitLevelLivingRoom
PlayModeNumModes
)
const (
InteractionModelTouch = iota
InteractionModelCrosshair
InteractionModelClassic
)
// PlayerAuthInput is sent by the client to allow for server authoritative movement. It is used to synchronise
// the player input with the position server-side.
// The client sends this packet when the ServerAuthoritativeMovementMode field in the StartGame packet is set
// to true, instead of the MovePlayer packet. The client will send this packet once every tick.
type PlayerAuthInput struct {
// Pitch and Yaw hold the rotation that the player reports it has.
Pitch, Yaw float32
// Position holds the position that the player reports it has.
Position mgl32.Vec3
// MoveVector is a Vec2 that specifies the direction in which the player moved, as a combination of X/Z
// values which are created using the WASD/controller stick state.
MoveVector mgl32.Vec2
// HeadYaw is the horizontal rotation of the head that the player reports it has.
HeadYaw float32
// InputData is a combination of bit flags that together specify the way the player moved last tick. It
// is a combination of the flags above.
InputData protocol.Bitset
// InputMode specifies the way that the client inputs data to the screen. It is one of the constants that
// may be found above.
InputMode uint32
// PlayMode specifies the way that the player is playing. The values it holds, which are rather random,
// may be found above.
PlayMode uint32
// InteractionModel is a constant representing the interaction model the player is using. It is one of the
// constants that may be found above.
InteractionModel uint32
// InteractPitch and interactYaw is the rotation the player is looking that they intend to use for
// interactions. This is only different to Pitch and Yaw in cases such as VR or when custom cameras
// being used.
InteractPitch, InteractYaw float32
// Tick is the server tick at which the packet was sent. It is used in relation to
// CorrectPlayerMovePrediction.
Tick uint64
// Delta was the delta between the old and the new position. There isn't any practical use for this field
// as it can be calculated by the server itself.
Delta mgl32.Vec3
// ItemInteractionData is the transaction data if the InputData includes an item interaction.
ItemInteractionData protocol.UseItemTransactionData
// ItemStackRequest is sent by the client to change an item in their inventory.
ItemStackRequest protocol.ItemStackRequest
// BlockActions is a slice of block actions that the client has interacted with.
BlockActions []protocol.PlayerBlockAction
// VehicleRotation is the rotation of the vehicle that the player is in, if any.
VehicleRotation mgl32.Vec2
// ClientPredictedVehicle is the unique ID of the vehicle that the client predicts the player to be in.
ClientPredictedVehicle int64
// AnalogueMoveVector is a Vec2 that specifies the direction in which the player moved, as a combination
// of X/Z values which are created using an analogue input.
AnalogueMoveVector mgl32.Vec2
// CameraOrientation is the vector that represents the camera's forward direction which can be used to
// transform movement to be camera relative.
CameraOrientation mgl32.Vec3
// RawMoveVector is the value of MoveVector before it is affected by input permissions, sneaking/fly
// speeds and isn't normalised for analogue inputs.
RawMoveVector mgl32.Vec2
}
// ID ...
func (pk *PlayerAuthInput) ID() uint32 {
return packet.IDPlayerAuthInput
}
func (pk *PlayerAuthInput) Marshal(io protocol.IO) {
io.Float32(&pk.Pitch)
io.Float32(&pk.Yaw)
io.Vec3(&pk.Position)
io.Vec2(&pk.MoveVector)
io.Float32(&pk.HeadYaw)
if proto.IsProtoGTE(io, proto.ID766) {
io.Bitset(&pk.InputData, PlayerAuthInputBitsetSize)
} else {
io.Bitset(&pk.InputData, 64)
}
io.Varuint32(&pk.InputMode)
io.Varuint32(&pk.PlayMode)
io.Varuint32(&pk.InteractionModel)
io.Float32(&pk.InteractPitch)
io.Float32(&pk.InteractYaw)
io.Varuint64(&pk.Tick)
io.Vec3(&pk.Delta)
if pk.InputData.Load(InputFlagPerformItemInteraction) {
io.PlayerInventoryAction(&pk.ItemInteractionData)
}
if pk.InputData.Load(InputFlagPerformItemStackRequest) {
protocol.Single(io, &pk.ItemStackRequest)
}
if pk.InputData.Load(InputFlagPerformBlockActions) {
protocol.SliceVarint32Length(io, &pk.BlockActions)
}
if pk.InputData.Load(InputFlagClientPredictedVehicle) {
io.Vec2(&pk.VehicleRotation)
io.Varint64(&pk.ClientPredictedVehicle)
}
io.Vec2(&pk.AnalogueMoveVector)
io.Vec3(&pk.CameraOrientation)
if proto.IsProtoGTE(io, proto.ID766) {
io.Vec2(&pk.RawMoveVector)
}
}

View File

@@ -0,0 +1,50 @@
package legacypacket
import (
"github.com/akmalfairuz/legacy-version/legacyver/proto"
"github.com/google/uuid"
"github.com/sandertv/gophertunnel/minecraft/protocol"
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
)
// ResourcePacksInfo is sent by the server to inform the client on what resource packs the server has. It
// sends a list of the resource packs it has and basic information on them like the version and description.
type ResourcePacksInfo struct {
// TexturePackRequired specifies if the client must accept the texture packs the server has in order to
// join the server. If set to true, the client gets the option to either download the resource packs and
// join, or quit entirely. Behaviour packs never have to be downloaded.
TexturePackRequired bool
// HasAddons specifies if any of the resource packs contain addons in them. If set to true, only clients
// that support addons will be able to download them.
HasAddons bool
// HasScripts specifies if any of the resource packs contain scripts in them. If set to true, only clients
// that support scripts will be able to download them.
HasScripts bool
// WorldTemplateUUID is the UUID of the template that has been used to generate the world. Templates can
// be downloaded from the marketplace or installed via '.mctemplate' files. If the world was not generated
// from a template, this field is empty.
WorldTemplateUUID uuid.UUID
// WorldTemplateVersion is the version of the world template that has been used to generate the world. If
// the world was not generated from a template, this field is empty.
WorldTemplateVersion string
// TexturePacks is a list of texture packs that the client needs to download before joining the server.
// The order of these texture packs is not relevant in this packet. It is however important in the
// ResourcePackStack packet.
TexturePacks []proto.TexturePackInfo
}
// ID ...
func (*ResourcePacksInfo) ID() uint32 {
return packet.IDResourcePacksInfo
}
func (pk *ResourcePacksInfo) Marshal(io protocol.IO) {
io.Bool(&pk.TexturePackRequired)
io.Bool(&pk.HasAddons)
io.Bool(&pk.HasScripts)
if proto.IsProtoGTE(io, proto.ID766) {
io.UUID(&pk.WorldTemplateUUID)
io.String(&pk.WorldTemplateVersion)
}
protocol.SliceUint16Length(io, &pk.TexturePacks)
}

32
legacyver/proto/id.go Normal file
View File

@@ -0,0 +1,32 @@
package proto
import "github.com/sandertv/gophertunnel/minecraft/protocol"
const (
ID766 = 766
ID748 = 748
ID729 = 729
ID712 = 712
ID686 = 686
ID685 = 685
)
func IsProtoGTE(io protocol.IO, proto int32) bool {
return io.(IO).ProtocolID() >= proto
}
func IsProtoLTE(io protocol.IO, proto int32) bool {
return io.(IO).ProtocolID() <= proto
}
func IsProtoLT(io protocol.IO, proto int32) bool {
return io.(IO).ProtocolID() < proto
}
func IsProtoGT(io protocol.IO, proto int32) bool {
return io.(IO).ProtocolID() > proto
}
func FetchProtoID(io protocol.IO) int32 {
return io.(IO).ProtocolID()
}

53
legacyver/proto/io.go Normal file
View File

@@ -0,0 +1,53 @@
package proto
import (
"github.com/sandertv/gophertunnel/minecraft/protocol"
)
type IO interface {
protocol.IO
SetProtocolID(protocolID int32)
ProtocolID() int32
}
type Reader struct {
*protocol.Reader
protocolID int32
}
func NewReader(r *protocol.Reader, protocolID int32) *Reader {
return &Reader{
Reader: r,
protocolID: protocolID,
}
}
func (r *Reader) SetProtocolID(protocolID int32) { r.protocolID = protocolID }
func (r *Reader) ProtocolID() int32 { return r.protocolID }
func (r *Reader) Reads() bool { return true }
type Writer struct {
*protocol.Writer
protocolID int32
}
func NewWriter(w *protocol.Writer, protocolID int32) *Writer {
return &Writer{w, protocolID}
}
func (w *Writer) SetProtocolID(protocolID int32) { w.protocolID = protocolID }
func (w *Writer) ProtocolID() int32 { return w.protocolID }
func (w *Writer) Reads() bool { return false }
func IsReader(r protocol.IO) bool {
_, ok := r.(*Reader)
return ok
}
func IsWriter(w protocol.IO) bool {
_, ok := w.(*Writer)
return ok
}

View File

@@ -0,0 +1,652 @@
package proto
import (
"github.com/sandertv/gophertunnel/minecraft/protocol"
)
const (
FilterCauseServerChatPublic = iota
FilterCauseServerChatWhisper
FilterCauseSignText
FilterCauseAnvilText
FilterCauseBookAndQuillText
FilterCauseCommandBlockText
FilterCauseBlockActorDataText
FilterCauseJoinEventText
FilterCauseLeaveEventText
FilterCauseSlashCommandChat
FilterCauseCartographyText
FilterCauseKickCommand
FilterCauseTitleCommand
FilterCauseSummonCommand
)
// ItemStackRequest represents a single request present in an ItemStackRequest packet sent by the client to
// change an item in an inventory.
// Item stack requests are either approved or rejected by the server using the ItemStackResponse packet.
type ItemStackRequest struct {
// RequestID is a unique ID for the request. This ID is used by the server to send a response for this
// specific request in the ItemStackResponse packet.
RequestID int32
// Actions is a list of actions performed by the client. The actual type of the actions depends on which
// ID was present, and is one of the concrete types below.
Actions []protocol.StackRequestAction
// FilterStrings is a list of filter strings involved in the request. This is typically filled with one string
// when an anvil or cartography is used.
FilterStrings []string
// FilterCause represents the cause of any potential filtering. This is one of the constants above.
FilterCause int32
}
// Marshal encodes/decodes an ItemStackRequest.
func (x *ItemStackRequest) Marshal(r protocol.IO) {
r.Varint32(&x.RequestID)
protocol.FuncSlice(r, &x.Actions, r.StackRequestAction)
protocol.FuncSlice(r, &x.FilterStrings, r.String)
r.Int32(&x.FilterCause)
}
// lookupStackRequestActionType looks up the ID of a StackRequestAction.
func lookupStackRequestActionType(x StackRequestAction, id *uint8) bool {
switch x.(type) {
case *TakeStackRequestAction:
*id = StackRequestActionTake
case *PlaceStackRequestAction:
*id = StackRequestActionPlace
case *SwapStackRequestAction:
*id = StackRequestActionSwap
case *DropStackRequestAction:
*id = StackRequestActionDrop
case *DestroyStackRequestAction:
*id = StackRequestActionDestroy
case *ConsumeStackRequestAction:
*id = StackRequestActionConsume
case *CreateStackRequestAction:
*id = StackRequestActionCreate
case *LabTableCombineStackRequestAction:
*id = StackRequestActionLabTableCombine
case *BeaconPaymentStackRequestAction:
*id = StackRequestActionBeaconPayment
case *MineBlockStackRequestAction:
*id = StackRequestActionMineBlock
case *CraftRecipeStackRequestAction:
*id = StackRequestActionCraftRecipe
case *AutoCraftRecipeStackRequestAction:
*id = StackRequestActionCraftRecipeAuto
case *CraftCreativeStackRequestAction:
*id = StackRequestActionCraftCreative
case *CraftRecipeOptionalStackRequestAction:
*id = StackRequestActionCraftRecipeOptional
case *CraftGrindstoneRecipeStackRequestAction:
*id = StackRequestActionCraftGrindstone
case *CraftLoomRecipeStackRequestAction:
*id = StackRequestActionCraftLoom
case *CraftNonImplementedStackRequestAction:
*id = StackRequestActionCraftNonImplementedDeprecated
case *CraftResultsDeprecatedStackRequestAction:
*id = StackRequestActionCraftResultsDeprecated
default:
return false
}
return true
}
// lookupStackRequestAction looks up the StackRequestAction matching an ID.
func lookupStackRequestAction(id uint8, x *StackRequestAction) bool {
switch id {
case StackRequestActionTake:
*x = &TakeStackRequestAction{}
case StackRequestActionPlace:
*x = &PlaceStackRequestAction{}
case StackRequestActionSwap:
*x = &SwapStackRequestAction{}
case StackRequestActionDrop:
*x = &DropStackRequestAction{}
case StackRequestActionDestroy:
*x = &DestroyStackRequestAction{}
case StackRequestActionConsume:
*x = &ConsumeStackRequestAction{}
case StackRequestActionCreate:
*x = &CreateStackRequestAction{}
case StackRequestActionPlaceInContainer:
*x = &PlaceInContainerStackRequestAction{}
case StackRequestActionTakeOutContainer:
*x = &TakeOutContainerStackRequestAction{}
case StackRequestActionLabTableCombine:
*x = &LabTableCombineStackRequestAction{}
case StackRequestActionBeaconPayment:
*x = &BeaconPaymentStackRequestAction{}
case StackRequestActionMineBlock:
*x = &MineBlockStackRequestAction{}
case StackRequestActionCraftRecipe:
*x = &CraftRecipeStackRequestAction{}
case StackRequestActionCraftRecipeAuto:
*x = &AutoCraftRecipeStackRequestAction{}
case StackRequestActionCraftCreative:
*x = &CraftCreativeStackRequestAction{}
case StackRequestActionCraftRecipeOptional:
*x = &CraftRecipeOptionalStackRequestAction{}
case StackRequestActionCraftGrindstone:
*x = &CraftGrindstoneRecipeStackRequestAction{}
case StackRequestActionCraftLoom:
*x = &CraftLoomRecipeStackRequestAction{}
case StackRequestActionCraftNonImplementedDeprecated:
*x = &CraftNonImplementedStackRequestAction{}
case StackRequestActionCraftResultsDeprecated:
*x = &CraftResultsDeprecatedStackRequestAction{}
default:
return false
}
return true
}
const (
ItemStackResponseStatusOK = iota
ItemStackResponseStatusError
ItemStackResponseStatusInvalidRequestActionType
ItemStackResponseStatusActionRequestNotAllowed
ItemStackResponseStatusScreenHandlerEndRequestFailed
ItemStackResponseStatusItemRequestActionHandlerCommitFailed
ItemStackResponseStatusInvalidRequestCraftActionType
ItemStackResponseStatusInvalidCraftRequest
ItemStackResponseStatusInvalidCraftRequestScreen
ItemStackResponseStatusInvalidCraftResult
ItemStackResponseStatusInvalidCraftResultIndex
ItemStackResponseStatusInvalidCraftResultItem
ItemStackResponseStatusInvalidItemNetId
ItemStackResponseStatusMissingCreatedOutputContainer
ItemStackResponseStatusFailedToSetCreatedItemOutputSlot
ItemStackResponseStatusRequestAlreadyInProgress
ItemStackResponseStatusFailedToInitSparseContainer
ItemStackResponseStatusResultTransferFailed
ItemStackResponseStatusExpectedItemSlotNotFullyConsumed
ItemStackResponseStatusExpectedAnywhereItemNotFullyConsumed
ItemStackResponseStatusItemAlreadyConsumedFromSlot
ItemStackResponseStatusConsumedTooMuchFromSlot
ItemStackResponseStatusMismatchSlotExpectedConsumedItem
ItemStackResponseStatusMismatchSlotExpectedConsumedItemNetIdVariant
ItemStackResponseStatusFailedToMatchExpectedSlotConsumedItem
ItemStackResponseStatusFailedToMatchExpectedAllowedAnywhereConsumedItem
ItemStackResponseStatusConsumedItemOutOfAllowedSlotRange
ItemStackResponseStatusConsumedItemNotAllowed
ItemStackResponseStatusPlayerNotInCreativeMode
ItemStackResponseStatusInvalidExperimentalRecipeRequest
ItemStackResponseStatusFailedToCraftCreative
ItemStackResponseStatusFailedToGetLevelRecipe
ItemStackResponseStatusFailedToFindRecipeByNetId
ItemStackResponseStatusMismatchedCraftingSize
ItemStackResponseStatusMissingInputSparseContainer
ItemStackResponseStatusMismatchedRecipeForInputGridItems
ItemStackResponseStatusEmptyCraftResults
ItemStackResponseStatusFailedToEnchant
ItemStackResponseStatusMissingInputItem
ItemStackResponseStatusInsufficientPlayerLevelToEnchant
ItemStackResponseStatusMissingMaterialItem
ItemStackResponseStatusMissingActor
ItemStackResponseStatusUnknownPrimaryEffect
ItemStackResponseStatusPrimaryEffectOutOfRange
ItemStackResponseStatusPrimaryEffectUnavailable
ItemStackResponseStatusSecondaryEffectOutOfRange
ItemStackResponseStatusSecondaryEffectUnavailable
ItemStackResponseStatusDstContainerEqualToCreatedOutputContainer
ItemStackResponseStatusDstContainerAndSlotEqualToSrcContainerAndSlot
ItemStackResponseStatusFailedToValidateSrcSlot
ItemStackResponseStatusFailedToValidateDstSlot
ItemStackResponseStatusInvalidAdjustedAmount
ItemStackResponseStatusInvalidItemSetType
ItemStackResponseStatusInvalidTransferAmount
ItemStackResponseStatusCannotSwapItem
ItemStackResponseStatusCannotPlaceItem
ItemStackResponseStatusUnhandledItemSetType
ItemStackResponseStatusInvalidRemovedAmount
ItemStackResponseStatusInvalidRegion
ItemStackResponseStatusCannotDropItem
ItemStackResponseStatusCannotDestroyItem
ItemStackResponseStatusInvalidSourceContainer
ItemStackResponseStatusItemNotConsumed
ItemStackResponseStatusInvalidNumCrafts
ItemStackResponseStatusInvalidCraftResultStackSize
ItemStackResponseStatusCannotRemoveItem
ItemStackResponseStatusCannotConsumeItem
ItemStackResponseStatusScreenStackError
)
// ItemStackResponse is a response to an individual ItemStackRequest.
type ItemStackResponse struct {
// Status specifies if the request with the RequestID below was successful. If this is the case, the
// ContainerInfo below will have information on what slots ended up changing. If not, the container info
// will be empty.
// A non-0 status means an error occurred and will result in the action being reverted.
Status uint8
// RequestID is the unique ID of the request that this response is in reaction to. If rejected, the client
// will undo the actions from the request with this ID.
RequestID int32
// ContainerInfo holds information on the containers that had their contents changed as a result of the
// request.
ContainerInfo []StackResponseContainerInfo
}
func (x *ItemStackResponse) ToLatest() protocol.ItemStackResponse {
ret := protocol.ItemStackResponse{
Status: x.Status,
RequestID: x.RequestID,
ContainerInfo: make([]protocol.StackResponseContainerInfo, len(x.ContainerInfo)),
}
for i, v := range x.ContainerInfo {
ret.ContainerInfo[i] = protocol.StackResponseContainerInfo{
Container: v.Container,
SlotInfo: make([]protocol.StackResponseSlotInfo, len(v.SlotInfo)),
}
for j, w := range v.SlotInfo {
ret.ContainerInfo[i].SlotInfo[j] = protocol.StackResponseSlotInfo{
Slot: w.Slot,
HotbarSlot: w.HotbarSlot,
Count: w.Count,
StackNetworkID: w.StackNetworkID,
CustomName: w.CustomName,
FilteredCustomName: w.FilteredCustomName,
DurabilityCorrection: w.DurabilityCorrection,
}
}
}
return ret
}
// Marshal encodes/decodes an ItemStackResponse.
func (x *ItemStackResponse) Marshal(r protocol.IO) {
r.Uint8(&x.Status)
r.Varint32(&x.RequestID)
if x.Status == ItemStackResponseStatusOK {
protocol.Slice(r, &x.ContainerInfo)
}
}
// StackResponseContainerInfo holds information on what slots in a container have what item stack in them.
type StackResponseContainerInfo struct {
// Container is the FullContainerName that describes the container that the slots that follow are in. For
// the main inventory, the ContainerID seems to be 0x1b. Fur the cursor, this value seems to be 0x3a. For
// the crafting grid, this value seems to be 0x0d.
Container protocol.FullContainerName
// SlotInfo holds information on what item stack should be present in specific slots in the container.
SlotInfo []StackResponseSlotInfo
}
// Marshal encodes/decodes a StackResponseContainerInfo.
func (x *StackResponseContainerInfo) Marshal(r protocol.IO) {
protocol.Single(r, &x.Container)
protocol.Slice(r, &x.SlotInfo)
}
// StackResponseSlotInfo holds information on what item stack should be present in a specific slot.
type StackResponseSlotInfo struct {
// Slot and HotbarSlot seem to be the same value every time: The slot that was actually changed. I'm not
// sure if these slots ever differ.
Slot, HotbarSlot byte
// Count is the total count of the item stack. This count will be shown client-side after the response is
// sent to the client.
Count byte
// StackNetworkID is the network ID of the new stack at a specific slot.
StackNetworkID int32
// CustomName is the custom name of the item stack. It is used in relation to text filtering.
CustomName string
// FilteredCustomName is a filtered version of CustomName with all the profanity removed. The client will
// use this over CustomName if this field is not empty and they have the "Filter Profanity" setting enabled.
FilteredCustomName string
// DurabilityCorrection is the current durability of the item stack. This durability will be shown
// client-side after the response is sent to the client.
DurabilityCorrection int32
}
// Marshal encodes/decodes a StackResponseSlotInfo.
func (x *StackResponseSlotInfo) Marshal(r protocol.IO) {
r.Uint8(&x.Slot)
r.Uint8(&x.HotbarSlot)
r.Uint8(&x.Count)
r.Varint32(&x.StackNetworkID)
if x.Slot != x.HotbarSlot {
r.InvalidValue(x.HotbarSlot, "hotbar slot", "hot bar slot must be equal to normal slot")
}
r.String(&x.CustomName)
if IsProtoGTE(r, ID766) {
r.String(&x.FilteredCustomName)
}
r.Varint32(&x.DurabilityCorrection)
}
// StackRequestAction represents a single action related to the inventory present in an ItemStackRequest.
// The action is one of the concrete types below, each of which are indicative of a different action by the
// client, such as moving an item around the inventory or placing a block. It is an alias of Marshaler.
type StackRequestAction interface {
protocol.Marshaler
}
const (
StackRequestActionTake = iota
StackRequestActionPlace
StackRequestActionSwap
StackRequestActionDrop
StackRequestActionDestroy
StackRequestActionConsume
StackRequestActionCreate
StackRequestActionPlaceInContainer
StackRequestActionTakeOutContainer
StackRequestActionLabTableCombine
StackRequestActionBeaconPayment
StackRequestActionMineBlock
StackRequestActionCraftRecipe
StackRequestActionCraftRecipeAuto
StackRequestActionCraftCreative
StackRequestActionCraftRecipeOptional
StackRequestActionCraftGrindstone
StackRequestActionCraftLoom
StackRequestActionCraftNonImplementedDeprecated
StackRequestActionCraftResultsDeprecated
)
// transferStackRequestAction is the structure shared by StackRequestActions that transfer items from one
// slot into another.
type transferStackRequestAction struct {
// Count is the count of the item in the source slot that was taken towards the destination slot.
Count byte
// Source and Destination point to the source slot from which Count of the item stack were taken and the
// destination slot to which this item was moved.
Source, Destination StackRequestSlotInfo
}
// Marshal ...
func (a *transferStackRequestAction) Marshal(r protocol.IO) {
r.Uint8(&a.Count)
StackReqSlotInfo(r, &a.Source)
StackReqSlotInfo(r, &a.Destination)
}
// TakeStackRequestAction is sent by the client to the server to take x amount of items from one slot in a
// container to the cursor.
type TakeStackRequestAction struct {
transferStackRequestAction
}
// PlaceStackRequestAction is sent by the client to the server to place x amount of items from one slot into
// another slot, such as when shift clicking an item in the inventory to move it around or when moving an item
// in the cursor into a slot.
type PlaceStackRequestAction struct {
transferStackRequestAction
}
// SwapStackRequestAction is sent by the client to swap the item in its cursor with an item present in another
// container. The two item stacks swap places.
type SwapStackRequestAction struct {
// Source and Destination point to the source slot from which Count of the item stack were taken and the
// destination slot to which this item was moved.
Source, Destination StackRequestSlotInfo
}
// Marshal ...
func (a *SwapStackRequestAction) Marshal(r protocol.IO) {
StackReqSlotInfo(r, &a.Source)
StackReqSlotInfo(r, &a.Destination)
}
// DropStackRequestAction is sent by the client when it drops an item out of the inventory when it has its
// inventory opened. This action is not sent when a player drops an item out of the hotbar using the Q button
// (or the equivalent on mobile). The InventoryTransaction packet is still used for that action, regardless of
// whether the item stack network IDs are used or not.
type DropStackRequestAction struct {
// Count is the count of the item in the source slot that was taken towards the destination slot.
Count byte
// Source is the source slot from which items were dropped to the ground.
Source StackRequestSlotInfo
// Randomly seems to be set to false in most cases. I'm not entirely sure what this does, but this is what
// vanilla calls this field.
Randomly bool
}
// Marshal ...
func (a *DropStackRequestAction) Marshal(r protocol.IO) {
r.Uint8(&a.Count)
StackReqSlotInfo(r, &a.Source)
r.Bool(&a.Randomly)
}
// DestroyStackRequestAction is sent by the client when it destroys an item in creative mode by moving it
// back into the creative inventory.
type DestroyStackRequestAction struct {
// Count is the count of the item in the source slot that was destroyed.
Count byte
// Source is the source slot from which items came that were destroyed by moving them into the creative
// inventory.
Source StackRequestSlotInfo
}
// Marshal ...
func (a *DestroyStackRequestAction) Marshal(r protocol.IO) {
r.Uint8(&a.Count)
StackReqSlotInfo(r, &a.Source)
}
// ConsumeStackRequestAction is sent by the client when it uses an item to craft another item. The original
// item is 'consumed'.
type ConsumeStackRequestAction struct {
DestroyStackRequestAction
}
// CreateStackRequestAction is sent by the client when an item is created through being used as part of a
// recipe. For example, when milk is used to craft a cake, the buckets are leftover. The buckets are moved to
// the slot sent by the client here.
// Note that before this is sent, an action for consuming all items in the crafting table/grid is sent. Items
// that are not fully consumed when used for a recipe should not be destroyed there, but instead, should be
// turned into their respective resulting items.
type CreateStackRequestAction struct {
// ResultsSlot is the slot in the inventory in which the results of the crafting ingredients are to be
// placed.
ResultsSlot byte
}
// Marshal ...
func (a *CreateStackRequestAction) Marshal(r protocol.IO) {
r.Uint8(&a.ResultsSlot)
}
// PlaceInContainerStackRequestAction currently has no known purpose.
type PlaceInContainerStackRequestAction struct {
transferStackRequestAction
}
// TakeOutContainerStackRequestAction currently has no known purpose.
type TakeOutContainerStackRequestAction struct {
transferStackRequestAction
}
// LabTableCombineStackRequestAction is sent by the client when it uses a lab table to combine item stacks.
type LabTableCombineStackRequestAction struct{}
// Marshal ...
func (a *LabTableCombineStackRequestAction) Marshal(protocol.IO) {}
// BeaconPaymentStackRequestAction is sent by the client when it submits an item to enable effects from a
// beacon. These items will have been moved into the beacon item slot in advance.
type BeaconPaymentStackRequestAction struct {
// PrimaryEffect and SecondaryEffect are the effects that were selected from the beacon.
PrimaryEffect, SecondaryEffect int32
}
// Marshal ...
func (a *BeaconPaymentStackRequestAction) Marshal(r protocol.IO) {
r.Varint32(&a.PrimaryEffect)
r.Varint32(&a.SecondaryEffect)
}
// MineBlockStackRequestAction is sent by the client when it breaks a block.
type MineBlockStackRequestAction struct {
// HotbarSlot is the slot held by the player while mining a block.
HotbarSlot int32
// PredictedDurability is the durability of the item that the client assumes to be present at the time.
PredictedDurability int32
// StackNetworkID is the unique stack ID that the client assumes to be present at the time. The server
// must check if these IDs match. If they do not match, servers should reject the stack request that the
// action holding this info was in.
StackNetworkID int32
}
// Marshal ...
func (a *MineBlockStackRequestAction) Marshal(r protocol.IO) {
r.Varint32(&a.HotbarSlot)
r.Varint32(&a.PredictedDurability)
r.Varint32(&a.StackNetworkID)
}
// CraftRecipeStackRequestAction is sent by the client the moment it begins crafting an item. This is the
// first action sent, before the Consume and Create item stack request actions.
// This action is also sent when an item is enchanted. Enchanting should be treated mostly the same way as
// crafting, where the old item is consumed.
type CraftRecipeStackRequestAction struct {
// RecipeNetworkID is the network ID of the recipe that is about to be crafted. This network ID matches
// one of the recipes sent in the CraftingData packet, where each of the recipes have a RecipeNetworkID as
// of 1.16.
RecipeNetworkID uint32
// NumberOfCrafts is how many times the recipe was crafted. This field appears to be boilerplate and
// has no effect.
NumberOfCrafts byte
}
// Marshal ...
func (a *CraftRecipeStackRequestAction) Marshal(r protocol.IO) {
r.Varuint32(&a.RecipeNetworkID)
r.Uint8(&a.NumberOfCrafts)
}
// AutoCraftRecipeStackRequestAction is sent by the client similarly to the CraftRecipeStackRequestAction. The
// only difference is that the recipe is automatically created and crafted by shift clicking the recipe book.
type AutoCraftRecipeStackRequestAction struct {
// RecipeNetworkID is the network ID of the recipe that is about to be crafted. This network ID matches
// one of the recipes sent in the CraftingData packet, where each of the recipes have a RecipeNetworkID as
// of 1.16.
RecipeNetworkID uint32
// NumberOfCrafts is how many times the recipe was crafted. This field is just a duplicate of TimesCrafted.
NumberOfCrafts byte
// TimesCrafted is how many times the recipe was crafted.
TimesCrafted byte
// Ingredients is a slice of ItemDescriptorCount that contains the ingredients that were used to craft the recipe.
// It is not exactly clear what this is used for, but it is sent by the vanilla client.
Ingredients []protocol.ItemDescriptorCount
}
// Marshal ...
func (a *AutoCraftRecipeStackRequestAction) Marshal(r protocol.IO) {
r.Varuint32(&a.RecipeNetworkID)
r.Uint8(&a.NumberOfCrafts)
r.Uint8(&a.TimesCrafted)
protocol.FuncSlice(r, &a.Ingredients, r.ItemDescriptorCount)
}
// CraftCreativeStackRequestAction is sent by the client when it takes an item out fo the creative inventory.
// The item is thus not really crafted, but instantly created.
type CraftCreativeStackRequestAction struct {
// CreativeItemNetworkID is the network ID of the creative item that is being created. This is one of the
// creative item network IDs sent in the CreativeContent packet.
CreativeItemNetworkID uint32
// NumberOfCrafts is how many times the recipe was crafted. This field appears to be boilerplate and
// has no effect.
NumberOfCrafts byte
}
// Marshal ...
func (a *CraftCreativeStackRequestAction) Marshal(r protocol.IO) {
r.Varuint32(&a.CreativeItemNetworkID)
r.Uint8(&a.NumberOfCrafts)
}
// CraftRecipeOptionalStackRequestAction is sent when using an anvil. When this action is sent, the
// FilterStrings field in the respective stack request is non-empty and contains the name of the item created
// using the anvil or cartography table.
type CraftRecipeOptionalStackRequestAction struct {
// RecipeNetworkID is the network ID of the multi-recipe that is about to be crafted. This network ID matches
// one of the multi-recipes sent in the CraftingData packet, where each of the recipes have a RecipeNetworkID as
// of 1.16.
RecipeNetworkID uint32
// FilterStringIndex is the index of a filter string sent in a ItemStackRequest.
FilterStringIndex int32
}
// Marshal ...
func (c *CraftRecipeOptionalStackRequestAction) Marshal(r protocol.IO) {
r.Varuint32(&c.RecipeNetworkID)
r.Int32(&c.FilterStringIndex)
}
// CraftGrindstoneRecipeStackRequestAction is sent when a grindstone recipe is crafted. It contains the RecipeNetworkID
// to identify the recipe crafted, and the cost for crafting the recipe.
type CraftGrindstoneRecipeStackRequestAction struct {
// RecipeNetworkID is the network ID of the recipe that is about to be crafted. This network ID matches
// one of the recipes sent in the CraftingData packet, where each of the recipes have a RecipeNetworkID as
// of 1.16.
RecipeNetworkID uint32
// NumberOfCrafts is how many times the recipe was crafted. This field appears to be boilerplate and
// has no effect.
NumberOfCrafts byte
// Cost is the cost of the recipe that was crafted.
Cost int32
}
// Marshal ...
func (c *CraftGrindstoneRecipeStackRequestAction) Marshal(r protocol.IO) {
r.Varuint32(&c.RecipeNetworkID)
r.Uint8(&c.NumberOfCrafts)
r.Varint32(&c.Cost)
}
// CraftLoomRecipeStackRequestAction is sent when a loom recipe is crafted. It simply contains the
// pattern identifier to figure out what pattern is meant to be applied to the item.
type CraftLoomRecipeStackRequestAction struct {
// Pattern is the pattern identifier for the loom recipe.
Pattern string
// TimesCrafted is how many times the recipe was crafted.
TimesCrafted byte
}
// Marshal ...
func (c *CraftLoomRecipeStackRequestAction) Marshal(r protocol.IO) {
r.String(&c.Pattern)
r.Uint8(&c.TimesCrafted)
}
// CraftNonImplementedStackRequestAction is an action sent for inventory actions that aren't yet implemented
// in the new system. These include, for example, anvils.
type CraftNonImplementedStackRequestAction struct{}
// Marshal ...
func (*CraftNonImplementedStackRequestAction) Marshal(protocol.IO) {}
// CraftResultsDeprecatedStackRequestAction is an additional, deprecated packet sent by the client after
// crafting. It holds the final results and the amount of times the recipe was crafted. It shouldn't be used.
// This action is also sent when an item is enchanted. Enchanting should be treated mostly the same way as
// crafting, where the old item is consumed.
type CraftResultsDeprecatedStackRequestAction struct {
ResultItems []protocol.ItemStack
TimesCrafted byte
}
// Marshal ...
func (a *CraftResultsDeprecatedStackRequestAction) Marshal(r protocol.IO) {
protocol.FuncSlice(r, &a.ResultItems, r.Item)
r.Uint8(&a.TimesCrafted)
}
// StackRequestSlotInfo holds information on a specific slot client-side.
type StackRequestSlotInfo struct {
// Container is the FullContainerName that describes the container that the slot is in.
Container protocol.FullContainerName
// Slot is the index of the slot within the container with the ContainerID above.
Slot byte
// StackNetworkID is the unique stack ID that the client assumes to be present in this slot. The server
// must check if these IDs match. If they do not match, servers should reject the stack request that the
// action holding this info was in.
StackNetworkID int32
}
// StackReqSlotInfo reads/writes a StackRequestSlotInfo x using IO r.
func StackReqSlotInfo(r protocol.IO, x *StackRequestSlotInfo) {
protocol.Single(r, &x.Container)
r.Uint8(&x.Slot)
r.Varint32(&x.StackNetworkID)
}

View File

@@ -0,0 +1,79 @@
package proto
import (
"github.com/google/uuid"
"github.com/sandertv/gophertunnel/minecraft/protocol"
)
// TexturePackInfo represents a texture pack's info sent over network. It holds information about the
// texture pack such as its name, description and version.
type TexturePackInfo struct {
// UUID is the UUID of the texture pack. Each texture pack downloaded must have a different UUID in
// order for the client to be able to handle them properly.
UUID uuid.UUID
// Version is the version of the texture pack. The client will cache texture packs sent by the server as
// long as they carry the same version. Sending a texture pack with a different version than previously
// will force the client to re-download it.
Version string
// Size is the total size in bytes that the texture pack occupies. This is the size of the compressed
// archive (zip) of the texture pack.
Size uint64
// ContentKey is the key used to decrypt the behaviour pack if it is encrypted. This is generally the case
// for marketplace texture packs.
ContentKey string
// SubPackName ...
SubPackName string
// ContentIdentity is another UUID for the resource pack, and is generally set for marketplace texture
// packs. It is also required for client-side validations when the resource pack is encrypted.
ContentIdentity string
// HasScripts specifies if the texture packs has any scripts in it. A client will only download the
// behaviour pack if it supports scripts, which, up to 1.11, only includes Windows 10.
HasScripts bool
// AddonPack specifies if the texture pack is from an addon.
AddonPack bool
// RTXEnabled specifies if the texture pack uses the raytracing technology introduced in 1.16.200.
RTXEnabled bool
// DownloadURL is a URL that the client can use to download the pack instead of the server sending it in
// chunks, which it will continue to do if this field is left empty.
DownloadURL string
}
func (x *TexturePackInfo) ToLatest() protocol.TexturePackInfo {
return protocol.TexturePackInfo{
UUID: x.UUID,
Version: x.Version,
Size: x.Size,
ContentKey: x.ContentKey,
SubPackName: x.SubPackName,
ContentIdentity: x.ContentIdentity,
HasScripts: x.HasScripts,
AddonPack: x.AddonPack,
RTXEnabled: x.RTXEnabled,
DownloadURL: x.DownloadURL,
}
}
// Marshal encodes/decodes a TexturePackInfo.
func (x *TexturePackInfo) Marshal(r protocol.IO) {
if IsProtoGTE(r, ID766) {
r.UUID(&x.UUID)
} else {
if IsReader(r) {
uuidStr := ""
r.String(&uuidStr)
x.UUID = uuid.MustParse(uuidStr)
} else {
uuidStr := x.UUID.String()
r.String(&uuidStr)
}
}
r.String(&x.Version)
r.Uint64(&x.Size)
r.String(&x.ContentKey)
r.String(&x.SubPackName)
r.String(&x.ContentIdentity)
r.Bool(&x.HasScripts)
r.Bool(&x.AddonPack)
r.Bool(&x.RTXEnabled)
r.String(&x.DownloadURL)
}

5
legacyver/proto/type.go Normal file
View File

@@ -0,0 +1,5 @@
package proto
type legacyType struct {
ProtocolID int32
}

215
legacyver/protocol.go Normal file
View File

@@ -0,0 +1,215 @@
package legacyver
import (
"github.com/akmalfairuz/legacy-version/legacyver/legacypacket"
"github.com/akmalfairuz/legacy-version/legacyver/proto"
"github.com/sandertv/gophertunnel/minecraft"
"github.com/sandertv/gophertunnel/minecraft/protocol"
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
)
var (
packetPoolClient packet.Pool
packetPoolServer packet.Pool
)
func init() {
packetPoolClient = packet.NewClientPool()
packetPoolServer = packet.NewServerPool()
packetPoolServer[packet.IDItemStackResponse] = func() packet.Packet { return &legacypacket.ItemStackResponse{} }
packetPoolServer[packet.IDResourcePacksInfo] = func() packet.Packet { return &legacypacket.ResourcePacksInfo{} }
packetPoolClient[packet.IDPlayerAuthInput] = func() packet.Packet { return &legacypacket.PlayerAuthInput{} }
}
type Protocol struct {
ver string
id int32
blockTranslator BlockTranslator
itemTranslator ItemTranslator
}
func (p *Protocol) Ver() string {
return p.ver
}
func (p *Protocol) ID() int32 {
return p.id
}
func (p *Protocol) Packets(listener bool) packet.Pool {
if listener {
return packetPoolClient
}
return packetPoolServer
}
func (p *Protocol) NewReader(r minecraft.ByteReader, shieldID int32, enableLimits bool) protocol.IO {
return proto.NewReader(protocol.NewReader(r, shieldID, enableLimits), p.id)
}
func (p *Protocol) NewWriter(w minecraft.ByteWriter, shieldID int32) protocol.IO {
return proto.NewWriter(protocol.NewWriter(w, shieldID), p.id)
}
func (p *Protocol) ConvertToLatest(pk packet.Packet, conn *minecraft.Conn) []packet.Packet {
return p.blockTranslator.UpgradeBlockPackets(
p.itemTranslator.UpgradeItemPackets(p.upgradePackets([]packet.Packet{pk}, conn), conn),
conn)
}
func (p *Protocol) ConvertFromLatest(pk packet.Packet, conn *minecraft.Conn) []packet.Packet {
return p.downgradePackets(p.blockTranslator.DowngradeBlockPackets(
p.itemTranslator.DowngradeItemPackets([]packet.Packet{pk}, conn),
conn), conn)
}
func (p *Protocol) downgradePackets(pks []packet.Packet, conn *minecraft.Conn) []packet.Packet {
for pkIndex, pk := range pks {
switch pk := pk.(type) {
case *packet.StartGame:
pk.GameVersion = p.ver
pk.BaseGameVersion = p.ver
case *packet.PlayerAuthInput:
inputData := pk.InputData
if p.ID() < proto.ID766 {
inputData = fitBitset(inputData, 64)
}
pks[pkIndex] = &legacypacket.PlayerAuthInput{
Pitch: pk.Pitch,
Yaw: pk.Yaw,
Position: pk.Position,
MoveVector: pk.MoveVector,
HeadYaw: pk.HeadYaw,
InputData: inputData,
InputMode: pk.InputMode,
PlayMode: pk.PlayMode,
InteractionModel: pk.InteractionModel,
InteractPitch: pk.InteractPitch,
InteractYaw: pk.InteractYaw,
Tick: pk.Tick,
Delta: pk.Delta,
ItemInteractionData: pk.ItemInteractionData,
ItemStackRequest: pk.ItemStackRequest,
BlockActions: pk.BlockActions,
VehicleRotation: pk.VehicleRotation,
ClientPredictedVehicle: pk.ClientPredictedVehicle,
AnalogueMoveVector: pk.AnalogueMoveVector,
CameraOrientation: pk.CameraOrientation,
RawMoveVector: pk.RawMoveVector,
}
case *packet.ItemStackResponse:
responses := make([]proto.ItemStackResponse, len(pk.Responses))
for i, r := range pk.Responses {
containerInfo := make([]proto.StackResponseContainerInfo, len(r.ContainerInfo))
for j, c := range r.ContainerInfo {
slotInfo := make([]proto.StackResponseSlotInfo, len(c.SlotInfo))
for k, s := range c.SlotInfo {
slotInfo[k] = proto.StackResponseSlotInfo{
Slot: s.Slot,
HotbarSlot: s.HotbarSlot,
Count: s.Count,
StackNetworkID: s.StackNetworkID,
CustomName: s.CustomName,
FilteredCustomName: s.FilteredCustomName,
DurabilityCorrection: s.DurabilityCorrection,
}
}
containerInfo[j] = proto.StackResponseContainerInfo{
Container: c.Container,
SlotInfo: slotInfo,
}
}
responses[i] = proto.ItemStackResponse{
Status: r.Status,
RequestID: r.RequestID,
ContainerInfo: containerInfo,
}
}
pks[pkIndex] = &legacypacket.ItemStackResponse{Responses: responses}
case *packet.ResourcePacksInfo:
texturePacks := make([]proto.TexturePackInfo, len(pk.TexturePacks))
for i, t := range pk.TexturePacks {
texturePacks[i] = proto.TexturePackInfo{
UUID: t.UUID,
Version: t.Version,
Size: t.Size,
ContentKey: t.ContentKey,
SubPackName: t.SubPackName,
ContentIdentity: t.ContentIdentity,
HasScripts: t.HasScripts,
AddonPack: t.AddonPack,
RTXEnabled: t.RTXEnabled,
DownloadURL: t.DownloadURL,
}
}
pks[pkIndex] = &legacypacket.ResourcePacksInfo{
TexturePackRequired: pk.TexturePackRequired,
HasAddons: pk.HasAddons,
HasScripts: pk.HasScripts,
WorldTemplateUUID: pk.WorldTemplateUUID,
WorldTemplateVersion: pk.WorldTemplateVersion,
TexturePacks: texturePacks,
}
}
}
return pks
}
func (p *Protocol) upgradePackets(pks []packet.Packet, conn *minecraft.Conn) []packet.Packet {
for pkIndex, pk := range pks {
switch pk := pk.(type) {
case *packet.StartGame:
pk.GameVersion = p.ver
pk.BaseGameVersion = p.ver
case *legacypacket.PlayerAuthInput:
pks[pkIndex] = &packet.PlayerAuthInput{
Pitch: pk.Pitch,
Yaw: pk.Yaw,
Position: pk.Position,
MoveVector: pk.MoveVector,
HeadYaw: pk.HeadYaw,
InputData: fitBitset(pk.InputData, packet.PlayerAuthInputBitsetSize),
InputMode: pk.InputMode,
PlayMode: pk.PlayMode,
InteractionModel: pk.InteractionModel,
InteractPitch: pk.InteractPitch,
InteractYaw: pk.InteractYaw,
Tick: pk.Tick,
Delta: pk.Delta,
ItemInteractionData: pk.ItemInteractionData,
ItemStackRequest: pk.ItemStackRequest,
BlockActions: pk.BlockActions,
VehicleRotation: pk.VehicleRotation,
ClientPredictedVehicle: pk.ClientPredictedVehicle,
AnalogueMoveVector: pk.AnalogueMoveVector,
CameraOrientation: pk.CameraOrientation,
RawMoveVector: pk.RawMoveVector,
}
case *legacypacket.ItemStackResponse:
responses := make([]protocol.ItemStackResponse, len(pk.Responses))
for i, r := range pk.Responses {
responses[i] = r.ToLatest()
}
pks[pkIndex] = &packet.ItemStackResponse{Responses: responses}
case *legacypacket.ResourcePacksInfo:
texturePacks := make([]protocol.TexturePackInfo, len(pk.TexturePacks))
for i, t := range pk.TexturePacks {
texturePacks[i] = t.ToLatest()
}
pks[pkIndex] = &packet.ResourcePacksInfo{
TexturePackRequired: pk.TexturePackRequired,
HasAddons: pk.HasAddons,
HasScripts: pk.HasScripts,
WorldTemplateUUID: pk.WorldTemplateUUID,
WorldTemplateVersion: pk.WorldTemplateVersion,
TexturePacks: texturePacks,
}
}
}
return pks
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

36
legacyver/v748.go Normal file
View File

@@ -0,0 +1,36 @@
package legacyver
import (
_ "embed"
"github.com/akmalfairuz/legacy-version/internal/chunk"
"github.com/akmalfairuz/legacy-version/legacyver/proto"
"github.com/akmalfairuz/legacy-version/mapping"
)
const (
// ItemVersion748 ...
ItemVersion748 = 221
// BlockVersion748 ...
BlockVersion748 int32 = (1 << 24) | (21 << 16) | (40 << 8)
)
var (
//go:embed item_runtime_ids_748.nbt
itemRuntimeIDData748 []byte
//go:embed required_item_list_748.json
requiredItemList748 []byte
//go:embed block_states_748.nbt
blockStateData748 []byte
)
func New748(direct bool) *Protocol {
itemMapping := mapping.NewItemMapping(itemRuntimeIDData748, requiredItemList748, ItemVersion748, false)
blockMapping := mapping.NewBlockMapping(blockStateData748)
return &Protocol{
ver: "1.21.40",
id: proto.ID748,
blockTranslator: NewBlockTranslator(blockMapping, latestBlockMapping, chunk.NewNetworkPersistentEncoding(blockMapping, BlockVersion748), chunk.NewBlockPaletteEncoding(blockMapping, BlockVersion748), false),
itemTranslator: NewItemTranslator(itemMapping, itemMappingLatest, blockMapping, blockMappingLatest),
}
}

25
legacyver/v766.go Normal file
View File

@@ -0,0 +1,25 @@
package legacyver
import (
_ "embed"
"github.com/akmalfairuz/legacy-version/mapping"
)
const (
// ItemVersion766 ...
ItemVersion766 = 231
// BlockVersion766 ...
BlockVersion766 int32 = (1 << 24) | (21 << 16) | (50 << 8)
)
var (
//go:embed item_runtime_ids_766.nbt
itemRuntimeIDData766 []byte
//go:embed required_item_list_766.json
requiredItemList766 []byte
//go:embed block_states_766.nbt
blockStateData766 []byte
itemMappingLatest = mapping.NewItemMapping(itemRuntimeIDData766, requiredItemList766, ItemVersion766, false)
blockMappingLatest = mapping.NewBlockMapping(blockStateData766)
)

146
main.go Normal file
View File

@@ -0,0 +1,146 @@
package main
import (
"errors"
"github.com/akmalfairuz/legacy-version/legacyver"
"github.com/pelletier/go-toml"
"github.com/sandertv/gophertunnel/minecraft"
"github.com/sandertv/gophertunnel/minecraft/auth"
"golang.org/x/oauth2"
"log"
"os"
"sync"
)
// The following program implements a proxy that forwards players from one local address to a remote address.
func main() {
config := readConfig()
token, err := auth.RequestLiveToken()
if err != nil {
panic(err)
}
src := auth.RefreshTokenSource(token)
p, err := minecraft.NewForeignStatusProvider(config.Connection.RemoteAddress)
if err != nil {
panic(err)
}
listener, err := minecraft.ListenConfig{
StatusProvider: p,
AcceptedProtocols: []minecraft.Protocol{
legacyver.New748(false),
},
}.Listen("raknet", config.Connection.LocalAddress)
if err != nil {
panic(err)
}
defer listener.Close()
for {
c, err := listener.Accept()
if err != nil {
panic(err)
}
go handleConn(c.(*minecraft.Conn), listener, config, src)
}
}
// handleConn handles a new incoming minecraft.Conn from the minecraft.Listener passed.
func handleConn(conn *minecraft.Conn, listener *minecraft.Listener, config config, src oauth2.TokenSource) {
serverConn, err := minecraft.Dialer{
TokenSource: src,
ClientData: conn.ClientData(),
}.Dial("raknet", config.Connection.RemoteAddress)
if err != nil {
panic(err)
}
var g sync.WaitGroup
g.Add(2)
go func() {
if err := conn.StartGame(serverConn.GameData()); err != nil {
panic(err)
}
g.Done()
}()
go func() {
if err := serverConn.DoSpawn(); err != nil {
panic(err)
}
g.Done()
}()
g.Wait()
go func() {
defer listener.Disconnect(conn, "connection lost")
defer serverConn.Close()
for {
pk, err := conn.ReadPacket()
if err != nil {
return
}
if err := serverConn.WritePacket(pk); err != nil {
var disc minecraft.DisconnectError
if ok := errors.As(err, &disc); ok {
_ = listener.Disconnect(conn, disc.Error())
}
return
}
}
}()
go func() {
defer serverConn.Close()
defer listener.Disconnect(conn, "connection lost")
for {
pk, err := serverConn.ReadPacket()
if err != nil {
var disc minecraft.DisconnectError
if ok := errors.As(err, &disc); ok {
_ = listener.Disconnect(conn, disc.Error())
}
return
}
if err := conn.WritePacket(pk); err != nil {
return
}
}
}()
}
type config struct {
Connection struct {
LocalAddress string
RemoteAddress string
}
}
func readConfig() config {
c := config{}
if _, err := os.Stat("config.toml"); os.IsNotExist(err) {
f, err := os.Create("config.toml")
if err != nil {
log.Fatalf("create config: %v", err)
}
data, err := toml.Marshal(c)
if err != nil {
log.Fatalf("encode default config: %v", err)
}
if _, err := f.Write(data); err != nil {
log.Fatalf("write default config: %v", err)
}
_ = f.Close()
}
data, err := os.ReadFile("config.toml")
if err != nil {
log.Fatalf("read config: %v", err)
}
if err := toml.Unmarshal(data, &c); err != nil {
log.Fatalf("decode config: %v", err)
}
if c.Connection.LocalAddress == "" {
c.Connection.LocalAddress = "0.0.0.0:19132"
}
data, _ = toml.Marshal(c)
if err := os.WriteFile("config.toml", data, 0644); err != nil {
log.Fatalf("write config: %v", err)
}
return c
}

136
mapping/block.go Normal file
View File

@@ -0,0 +1,136 @@
package mapping
import (
"bytes"
"github.com/akmalfairuz/legacy-version/internal"
"sort"
"github.com/df-mc/worldupgrader/blockupgrader"
"github.com/sandertv/gophertunnel/minecraft/nbt"
"github.com/sandertv/gophertunnel/minecraft/protocol"
"github.com/segmentio/fasthash/fnv1"
)
type Block interface {
// StateToRuntimeID converts a block state to a runtime ID.
StateToRuntimeID(blockupgrader.BlockState) (uint32, bool)
// RuntimeIDToState converts a runtime ID to a name and its state properties.
RuntimeIDToState(uint32) (blockupgrader.BlockState, bool)
// DowngradeBlockActorData downgrades the input sub chunk to a legacy block actor.
DowngradeBlockActorData(map[string]any)
// UpgradeBlockActorData upgrades the input sub chunk to the latest block actor.
UpgradeBlockActorData(map[string]any)
// Adjust adjusts the latest mappings to account for custom states.
Adjust([]protocol.BlockEntry)
Air() uint32
}
type DefaultBlockMapping struct {
// states holds a list of all possible vanilla block states.
states []blockupgrader.BlockState
// stateRuntimeIDs holds a map for looking up the runtime ID of a block by the stateHash it produces.
stateRuntimeIDs map[internal.StateHash]uint32
// runtimeIDToState holds a map for looking up the blockState of a block by its runtime ID.
runtimeIDToState map[uint32]blockupgrader.BlockState
upgrader, downgrader func(map[string]any) map[string]any
// airRID is the runtime ID of the air block in the latest version of the game.
airRID uint32
}
func NewBlockMapping(raw []byte) *DefaultBlockMapping {
dec := nbt.NewDecoder(bytes.NewBuffer(raw))
var states []blockupgrader.BlockState
stateRuntimeIDs := make(map[internal.StateHash]uint32)
runtimeIDToState := make(map[uint32]blockupgrader.BlockState)
var airRID *uint32
var s blockupgrader.BlockState
for {
if err := dec.Decode(&s); err != nil {
break
}
rid := uint32(len(states))
states = append(states, s)
if s.Name == "minecraft:air" {
airRID = &rid
}
stateRuntimeIDs[internal.HashState(blockupgrader.Upgrade(s))] = rid
runtimeIDToState[rid] = s
}
if airRID == nil {
panic("couldn't find air")
}
return &DefaultBlockMapping{
states: states,
stateRuntimeIDs: stateRuntimeIDs,
runtimeIDToState: runtimeIDToState,
airRID: *airRID,
}
}
func (m *DefaultBlockMapping) WithBlockActorRemapper(downgrader, upgrader func(map[string]any) map[string]any) *DefaultBlockMapping {
m.downgrader = downgrader
m.upgrader = upgrader
return m
}
func (m *DefaultBlockMapping) StateToRuntimeID(state blockupgrader.BlockState) (uint32, bool) {
rid, ok := m.stateRuntimeIDs[internal.HashState(blockupgrader.Upgrade(state))]
return rid, ok
}
func (m *DefaultBlockMapping) RuntimeIDToState(runtimeId uint32) (blockupgrader.BlockState, bool) {
state, found := m.runtimeIDToState[runtimeId]
return state, found
}
func (m *DefaultBlockMapping) DowngradeBlockActorData(actorData map[string]any) {
if m.downgrader != nil {
m.downgrader(actorData)
}
}
func (m *DefaultBlockMapping) UpgradeBlockActorData(actorData map[string]any) {
if m.upgrader != nil {
m.upgrader(actorData)
}
}
func (m *DefaultBlockMapping) Adjust(entries []protocol.BlockEntry) {
if len(entries) == 0 {
return
}
customStates := convert(entries)
var newStates []blockupgrader.BlockState
for _, state := range customStates {
if _, ok := m.StateToRuntimeID(state); !ok {
newStates = append(newStates, state)
}
}
if len(newStates) == 0 {
return
}
adjustedStates := append(m.states, customStates...)
sort.SliceStable(adjustedStates, func(i, j int) bool {
stateOne, stateTwo := adjustedStates[i], adjustedStates[j]
return stateOne.Name != stateTwo.Name && fnv1.HashString64(stateOne.Name) < fnv1.HashString64(stateTwo.Name)
})
m.stateRuntimeIDs = make(map[internal.StateHash]uint32, len(adjustedStates))
m.runtimeIDToState = make(map[uint32]blockupgrader.BlockState, len(adjustedStates))
for rid, state := range adjustedStates {
m.stateRuntimeIDs[internal.HashState(blockupgrader.Upgrade(state))] = uint32(rid)
m.runtimeIDToState[uint32(rid)] = state
}
}
func (m *DefaultBlockMapping) Air() uint32 {
return m.airRID
}

84
mapping/custom_blocks.go Normal file
View File

@@ -0,0 +1,84 @@
package mapping
import (
"github.com/akmalfairuz/legacy-version/internal"
"github.com/df-mc/worldupgrader/blockupgrader"
"github.com/sandertv/gophertunnel/minecraft/protocol"
"golang.org/x/exp/maps"
)
func convert(entries []protocol.BlockEntry) (states []blockupgrader.BlockState) {
for _, entry := range entries {
propertiesMap := map[string][]any{}
if props := jsonCheck[[]any](entry.Properties, "properties"); props != nil {
for _, prop := range *props {
prop := prop.(map[string]any)
name := jsonCheck[string](prop, "name")
enum := jsonCheck[[]any](prop, "enum")
if enum == nil {
int32Enum := jsonCheck[[]int32](prop, "enum")
if int32Enum != nil {
enum = &[]any{}
for _, i := range *int32Enum {
*enum = append(*enum, i)
}
}
}
if name == nil || enum == nil {
panic("could not find field `name` and `enum`")
}
propertiesMap[*name] = *enum
}
}
combinations := make([]map[string]any, 0)
generateCombinationsRecursively(propertiesMap, internal.NewIterator(maps.Keys(propertiesMap)), map[string]any{}, &combinations)
if len(combinations) == 0 {
combinations = append(combinations, map[string]any{})
}
for _, combination := range combinations {
blockState := blockupgrader.BlockState{
Name: entry.Name,
Properties: combination,
}
states = append(states, blockState)
}
}
return
}
func generateCombinationsRecursively[K comparable, V any](all map[K][]V, iterator *internal.Iterator[K], current map[K]V, output *[]map[K]V) {
if !iterator.HasNext() {
entry := map[K]V{}
for k, v := range current {
entry[k] = v
}
out := append(*output, entry)
*output = out
} else {
key := iterator.Next()
set := all[key]
for _, value := range set {
current[key] = value
generateCombinationsRecursively(all, iterator, current, output)
delete(current, key)
}
iterator.Previous()
}
}
func jsonCheck[T any](json map[string]any, field string) *T {
fieldValue, found := json[field]
if !found {
return nil
}
castedValue, ok := fieldValue.(T)
if !ok {
return nil
}
return &castedValue
}

109
mapping/item.go Normal file
View File

@@ -0,0 +1,109 @@
package mapping
import (
"encoding/json"
"github.com/sandertv/gophertunnel/minecraft/nbt"
"sync"
)
type Item interface {
// ItemRuntimeIDToName converts an item runtime ID to a string ID.
ItemRuntimeIDToName(int32) (string, bool)
// ItemNameToRuntimeID converts a string ID to an item runtime ID.
ItemNameToRuntimeID(string) (int32, bool)
RegisterEntry(string) int32
Air() int32
ItemVersion() uint16
}
type DefaultItemMapping struct {
mu sync.Mutex
// itemRuntimeIDsToNames holds a map to translate item runtime IDs to string IDs.
itemRuntimeIDsToNames map[int32]string
// itemNamesToRuntimeIDs holds a map to translate item string IDs to runtime IDs.
itemNamesToRuntimeIDs map[string]int32
airRID int32
itemVersion uint16
}
func NewItemMapping(itemRuntimeIDData []byte, requiredItemList []byte, itemVersion uint16, direct bool) *DefaultItemMapping {
itemRuntimeIDsToNames := make(map[int32]string)
itemNamesToRuntimeIDs := make(map[string]int32)
var airRID *int32
if direct {
var items map[string]int32
if err := nbt.Unmarshal(itemRuntimeIDData, &items); err != nil {
panic(err)
}
for name, rid := range items {
if name == "minecraft:air" {
airRID = &rid
}
itemNamesToRuntimeIDs[name] = rid
itemRuntimeIDsToNames[rid] = name
}
} else {
var m map[string]struct {
RuntimeID int16 `json:"runtime_id"`
ComponentBased bool `json:"component_based"`
}
if err := json.Unmarshal(requiredItemList, &m); err != nil {
panic(err)
}
for name, data := range m {
rid := int32(data.RuntimeID)
if name == "minecraft:air" {
airRID = &rid
}
itemNamesToRuntimeIDs[name] = rid
itemRuntimeIDsToNames[rid] = name
}
}
if airRID == nil {
panic("couldn't find air")
}
return &DefaultItemMapping{itemRuntimeIDsToNames: itemRuntimeIDsToNames, itemNamesToRuntimeIDs: itemNamesToRuntimeIDs, itemVersion: itemVersion}
}
func (m *DefaultItemMapping) ItemRuntimeIDToName(runtimeID int32) (name string, found bool) {
defer m.mu.Unlock()
m.mu.Lock()
name, ok := m.itemRuntimeIDsToNames[runtimeID]
return name, ok
}
func (m *DefaultItemMapping) ItemNameToRuntimeID(name string) (runtimeID int32, found bool) {
defer m.mu.Unlock()
m.mu.Lock()
rid, ok := m.itemNamesToRuntimeIDs[name]
return rid, ok
}
func (m *DefaultItemMapping) RegisterEntry(name string) int32 {
defer m.mu.Unlock()
m.mu.Lock()
if rid, ok := m.itemNamesToRuntimeIDs[name]; ok {
return rid
}
nextRID := int32(len(m.itemRuntimeIDsToNames))
m.itemNamesToRuntimeIDs[name] = nextRID
m.itemRuntimeIDsToNames[nextRID] = name
return nextRID
}
func (m *DefaultItemMapping) Air() int32 {
defer m.mu.Unlock()
m.mu.Lock()
return m.airRID
}
func (m *DefaultItemMapping) ItemVersion() uint16 {
defer m.mu.Unlock()
m.mu.Lock()
return m.itemVersion
}

71
packbuilder/builder.go Normal file
View File

@@ -0,0 +1,71 @@
package packbuilder
import (
"github.com/df-mc/dragonfly/server/item/category"
"golang.org/x/exp/maps"
"strings"
)
// ComponentBuilder represents a builder that can be used to construct an item components map to be sent to a client.
type ComponentBuilder struct {
name string
identifier string
category category.Category
properties map[string]any
components map[string]any
}
// NewComponentBuilder returns a new component builder with the provided item data.
func NewComponentBuilder(name, identifier string, category category.Category) *ComponentBuilder {
return &ComponentBuilder{
name: name,
identifier: identifier,
category: category,
properties: make(map[string]any),
components: make(map[string]any),
}
}
// AddProperty adds the provided property to the builder.
func (builder *ComponentBuilder) AddProperty(name string, value any) {
builder.properties[name] = value
}
// AddComponent adds the provided component to the builder.
func (builder *ComponentBuilder) AddComponent(name string, value any) {
builder.components[name] = value
}
// Construct constructs the final item components map and returns it. It also applies the default properties required
// for the item to work without modifying the original maps in the builder.
func (builder *ComponentBuilder) Construct() map[string]any {
properties := maps.Clone(builder.properties)
components := maps.Clone(builder.components)
builder.applyDefaultProperties(properties)
builder.applyDefaultComponents(components, properties)
return map[string]any{"components": components}
}
// applyDefaultProperties applies the default properties to the provided map. It is important that this method does
// not modify the builder's properties map directly otherwise Empty() will return false in future use of the builder.
func (builder *ComponentBuilder) applyDefaultProperties(x map[string]any) {
x["minecraft:icon"] = map[string]any{
"texture": strings.Split(builder.identifier, ":")[1],
}
x["creative_group"] = builder.category.Group()
x["creative_category"] = int32(builder.category.Uint8())
if _, ok := x["max_stack_size"]; !ok {
x["max_stack_size"] = int32(64)
}
}
// applyDefaultComponents applies the default components to the provided map. It is important that this method does not
// modify the builder's components map directly otherwise Empty() will return false in future use of the builder.
func (builder *ComponentBuilder) applyDefaultComponents(x, properties map[string]any) {
x["item_properties"] = properties
x["minecraft:display_name"] = map[string]any{
"value": builder.name,
}
}

119
packbuilder/components.go Normal file
View File

@@ -0,0 +1,119 @@
package packbuilder
import (
"github.com/df-mc/dragonfly/server/item"
"github.com/df-mc/dragonfly/server/world"
"strings"
)
// Components returns all the components of the given custom item. If the item has no components, a nil map and false
// are returned.
func Components(it world.CustomItem) map[string]any {
category := it.Category()
identifier, _ := it.EncodeItem()
name := strings.Split(identifier, ":")[1]
builder := NewComponentBuilder(it.Name(), identifier, category)
if x, ok := it.(item.Armour); ok {
builder.AddComponent("minecraft:armor", map[string]any{
"protection": int32(x.DefencePoints()),
})
builder.AddComponent("minecraft:knockback_resistance", map[string]any{
"protection": float32(x.KnockBackResistance()),
})
var slot string
switch it.(type) {
case item.HelmetType:
slot = "slot.armor.head"
case item.ChestplateType:
slot = "slot.armor.chest"
case item.LeggingsType:
slot = "slot.armor.legs"
case item.BootsType:
slot = "slot.armor.feet"
}
builder.AddComponent("minecraft:wearable", map[string]any{
"slot": slot,
})
}
if x, ok := it.(item.Consumable); ok {
builder.AddProperty("use_duration", int32(x.ConsumeDuration().Seconds()*20))
builder.AddComponent("minecraft:food", map[string]any{
"can_always_eat": x.AlwaysConsumable(),
})
if y, ok := it.(item.Drinkable); ok && y.Drinkable() {
builder.AddProperty("use_animation", int32(2))
} else {
builder.AddProperty("use_animation", int32(1))
}
}
if x, ok := it.(item.Cooldown); ok {
builder.AddComponent("minecraft:cooldown", map[string]any{
"category": name,
"duration": float32(x.Cooldown().Seconds()),
})
}
if x, ok := it.(item.Durable); ok {
builder.AddComponent("minecraft:durability", map[string]any{
"max_durability": int32(x.DurabilityInfo().MaxDurability),
})
}
if x, ok := it.(item.MaxCounter); ok {
builder.AddProperty("max_stack_size", int32(x.MaxCount()))
}
if x, ok := it.(item.OffHand); ok {
builder.AddProperty("allow_off_hand", x.OffHand())
}
if x, ok := it.(item.Throwable); ok {
// The data in minecraft:projectile is only used by vanilla server-side, but we must send at least an empty map
// so the client will play the throwing animation.
builder.AddComponent("minecraft:projectile", map[string]any{})
builder.AddComponent("minecraft:throwable", map[string]any{
"do_swing_animation": x.SwingAnimation(),
})
}
if x, ok := it.(item.Glinted); ok {
builder.AddProperty("foil", x.Glinted())
}
if x, ok := it.(item.HandEquipped); ok {
builder.AddProperty("hand_equipped", x.HandEquipped())
}
itemScale := calculateItemScale(it)
builder.AddComponent("minecraft:render_offsets", map[string]any{
"main_hand": map[string]any{
"first_person": map[string]any{
"scale": itemScale,
},
"third_person": map[string]any{
"scale": itemScale,
},
},
"off_hand": map[string]any{
"first_person": map[string]any{
"scale": itemScale,
},
"third_person": map[string]any{
"scale": itemScale,
},
},
})
return builder.Construct()
}
// calculateItemScale calculates the scale of the item to be rendered to the player according to the given size.
func calculateItemScale(it world.CustomItem) []float32 {
width := float32(it.Texture().Bounds().Dx())
height := float32(it.Texture().Bounds().Dy())
var x, y, z float32 = 0.1, 0.1, 0.1
if _, ok := it.(item.HandEquipped); ok {
x, y, z = 0.075, 0.125, 0.075
}
newX := x / (width / 16)
newY := y / (height / 16)
newZ := z / (width / 16)
return []float32{newX, newY, newZ}
}

81
packbuilder/items.go Normal file
View File

@@ -0,0 +1,81 @@
package packbuilder
import (
"encoding/json"
"fmt"
"github.com/df-mc/dragonfly/server/world"
"golang.org/x/image/colornames"
"image"
"image/png"
"os"
"path/filepath"
"strings"
_ "unsafe" // Imported for compiler directives.
)
// buildItems builds all the item-related files for the resource pack. This includes textures, language
// entries and item atlas.
func buildItems(dir string, customItems []world.CustomItem) (count int, lang []string) {
if err := os.Mkdir(filepath.Join(dir, "items"), os.ModePerm); err != nil {
panic(err)
}
if err := os.MkdirAll(filepath.Join(dir, "textures/items"), os.ModePerm); err != nil {
panic(err)
}
textureData := make(map[string]any)
for _, item := range customItems {
identifier, _ := item.EncodeItem()
lang = append(lang, fmt.Sprintf("item.%s.name=%s", identifier, item.Name()))
name := strings.Split(identifier, ":")[1]
textureData[name] = map[string]string{"textures": fmt.Sprintf("textures/items/%s.png", name)}
buildItemTexture(dir, name, item.Texture())
count++
}
buildItemAtlas(dir, map[string]any{
"resource_pack_name": "vanilla",
"texture_name": "atlas.items",
"texture_data": textureData,
})
return
}
// buildItemTexture creates a PNG file for the item from the provided image and name and writes it to the pack.
func buildItemTexture(dir, name string, img image.Image) {
texture, err := os.Create(filepath.Join(dir, "textures/items", name+".png"))
if err != nil {
panic(err)
}
if img == nil {
im := image.NewAlpha(image.Rect(0, 0, 64, 64))
for x := 0; x < im.Bounds().Dx(); x++ {
for y := 0; y < im.Bounds().Dy(); y++ {
im.Set(x, y, colornames.Goldenrod)
}
}
img = im
}
if err := png.Encode(texture, img); err != nil {
_ = texture.Close()
panic(err)
}
if err := texture.Close(); err != nil {
panic(err)
}
}
// buildItemAtlas creates the identifier to texture mapping and writes it to the pack.
func buildItemAtlas(dir string, atlas map[string]any) {
b, err := json.Marshal(atlas)
if err != nil {
panic(err)
}
if err := os.WriteFile(filepath.Join(dir, "textures/item_texture.json"), b, 0666); err != nil {
panic(err)
}
}

17
packbuilder/lang.go Normal file
View File

@@ -0,0 +1,17 @@
package packbuilder
import (
"os"
"path/filepath"
"strings"
)
// buildLanguageFile creates a lang file and writes all of the language entries to the pack.
func buildLanguageFile(dir string, lang []string) {
if err := os.Mkdir(filepath.Join(dir, "texts"), os.ModePerm); err != nil {
panic(err)
}
if err := os.WriteFile(filepath.Join(dir, "texts/en_US.lang"), []byte(strings.Join(lang, "\n")), 0666); err != nil {
panic(err)
}
}

52
packbuilder/manifest.go Normal file
View File

@@ -0,0 +1,52 @@
package packbuilder
import (
"encoding/json"
"github.com/google/uuid"
"github.com/sandertv/gophertunnel/minecraft/resource"
"os"
"path/filepath"
"strconv"
"strings"
)
// buildManifest creates a JSON manifest file for the client to be able to read the resource pack. It creates
// basic information and writes it to the pack.
func buildManifest(dir, version string, headerUUID, moduleUUID uuid.UUID) {
m, err := json.Marshal(resource.Manifest{
FormatVersion: 2,
Header: resource.Header{
Name: "dragonfly auto-generated resource pack",
Description: "This resource pack contains auto-generated content from dragonfly",
UUID: headerUUID,
Version: [3]int{0, 0, 1},
MinimumGameVersion: parseVersion(version),
},
Modules: []resource.Module{
{
UUID: moduleUUID.String(),
Description: "This resource pack contains auto-generated content from dragonfly",
Type: "resources",
Version: [3]int{0, 0, 1},
},
},
})
if err != nil {
panic(err)
}
if err := os.WriteFile(filepath.Join(dir, "manifest.json"), m, 0666); err != nil {
panic(err)
}
}
// parseVersion parses the version passed in the format of a.b.c as a [3]int.
func parseVersion(ver string) [3]int {
frag := strings.Split(ver, ".")
if len(frag) != 3 {
panic("invalid version number " + ver)
}
a, _ := strconv.ParseInt(frag[0], 10, 64)
b, _ := strconv.ParseInt(frag[1], 10, 64)
c, _ := strconv.ParseInt(frag[2], 10, 64)
return [3]int{int(a), int(b), int(c)}
}

View File

@@ -0,0 +1,40 @@
package packbuilder
import (
"github.com/df-mc/dragonfly/server/world"
"github.com/rogpeppe/go-internal/dirhash"
"github.com/sandertv/gophertunnel/minecraft/resource"
"os"
)
// BuildResourcePack builds a resource pack based on custom features that have been registered to the server.
// It creates a UUID based on the hash of the directory so the client will only be prompted to download it
// once it is changed.
func BuildResourcePack(customItems []world.CustomItem, version string) (*resource.Pack, bool) {
dir, err := os.MkdirTemp("", "dragonfly_resource_pack-")
if err != nil {
panic(err)
}
defer os.RemoveAll(dir)
var assets int
var lang []string
itemCount, itemLang := buildItems(dir, customItems)
assets += itemCount
lang = append(lang, itemLang...)
if assets > 0 {
buildLanguageFile(dir, lang)
hash, err := dirhash.HashDir(dir, "", dirhash.Hash1)
if err != nil {
panic(err)
}
var header, module [16]byte
copy(header[:], hash)
copy(module[:], hash[16:])
buildManifest(dir, version, header, module)
return resource.MustReadPath(dir), true
}
return nil, false
}