Merge pull request #5 from AkmalFairuz/v1.21.60

1.21.60 support
This commit is contained in:
Akmal Fairuz
2025-02-13 10:47:21 +07:00
committed by GitHub
28 changed files with 9988 additions and 98 deletions

View File

@@ -5,6 +5,7 @@ A gophertunnel protocol interface implementation to support older Minecraft Bedr
## Supported Versions ## Supported Versions
| Protocol ID | Version | Support | | Protocol ID | Version | Support |
|-------------|---------|---------| |-------------|---------|---------|
| 776 | 1.21.60 | ✅ |
| 766 | 1.21.50 | ✅ | | 766 | 1.21.50 | ✅ |
| 748 | 1.21.40 | ✅ | | 748 | 1.21.40 | ✅ |
| 729 | 1.21.30 | ✅ | | 729 | 1.21.30 | ✅ |

View File

@@ -10,6 +10,7 @@ import (
"log" "log"
"os" "os"
"sync" "sync"
"time"
) )
// The following program implements a proxy that forwards players from one local address to a remote address. // The following program implements a proxy that forwards players from one local address to a remote address.
@@ -28,6 +29,7 @@ func main() {
listener, err := minecraft.ListenConfig{ listener, err := minecraft.ListenConfig{
StatusProvider: p, StatusProvider: p,
AcceptedProtocols: []minecraft.Protocol{ AcceptedProtocols: []minecraft.Protocol{
legacyver.New766(),
legacyver.New748(), legacyver.New748(),
legacyver.New729(), legacyver.New729(),
legacyver.New712(), legacyver.New712(),
@@ -54,7 +56,7 @@ func handleConn(conn *minecraft.Conn, listener *minecraft.Listener, config confi
serverConn, err := minecraft.Dialer{ serverConn, err := minecraft.Dialer{
TokenSource: src, TokenSource: src,
ClientData: conn.ClientData(), ClientData: conn.ClientData(),
}.Dial("raknet", config.Connection.RemoteAddress) }.DialTimeout("raknet", config.Connection.RemoteAddress, time.Second*100)
if err != nil { if err != nil {
panic(err) panic(err)
} }

10
go.mod
View File

@@ -10,11 +10,11 @@ require (
github.com/pelletier/go-toml v1.9.5 github.com/pelletier/go-toml v1.9.5
github.com/rogpeppe/go-internal v1.13.1 github.com/rogpeppe/go-internal v1.13.1
github.com/samber/lo v1.47.0 github.com/samber/lo v1.47.0
github.com/sandertv/gophertunnel v1.43.1 github.com/sandertv/gophertunnel v1.44.1-0.20250211173944-b0dbda19ad6a
github.com/segmentio/fasthash v1.0.3 github.com/segmentio/fasthash v1.0.3
golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67
golang.org/x/image v0.21.0 golang.org/x/image v0.21.0
golang.org/x/oauth2 v0.24.0 golang.org/x/oauth2 v0.26.0
) )
require ( require (
@@ -27,8 +27,8 @@ require (
github.com/klauspost/compress v1.17.11 // indirect github.com/klauspost/compress v1.17.11 // indirect
github.com/muhammadmuzzammil1998/jsonc v1.0.0 // indirect github.com/muhammadmuzzammil1998/jsonc v1.0.0 // indirect
github.com/sandertv/go-raknet v1.14.2 // indirect github.com/sandertv/go-raknet v1.14.2 // indirect
golang.org/x/crypto v0.31.0 // indirect golang.org/x/crypto v0.33.0 // indirect
golang.org/x/mod v0.22.0 // indirect golang.org/x/mod v0.22.0 // indirect
golang.org/x/net v0.33.0 // indirect golang.org/x/net v0.35.0 // indirect
golang.org/x/text v0.21.0 // indirect golang.org/x/text v0.22.0 // indirect
) )

14
go.sum
View File

@@ -49,6 +49,10 @@ github.com/sandertv/gophertunnel v1.43.1-0.20241229203958-5af8736ae319 h1:yxDqIv
github.com/sandertv/gophertunnel v1.43.1-0.20241229203958-5af8736ae319/go.mod h1:XuEJo+ARim+NKiD90Z56sQRcDtCOErz26e2bt3LEd9I= 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 h1:wY6Fy8dRMKtpZUQzCR35o9k05135vOJZhrz0GF6OXFI=
github.com/sandertv/gophertunnel v1.43.1/go.mod h1:XuEJo+ARim+NKiD90Z56sQRcDtCOErz26e2bt3LEd9I= github.com/sandertv/gophertunnel v1.43.1/go.mod h1:XuEJo+ARim+NKiD90Z56sQRcDtCOErz26e2bt3LEd9I=
github.com/sandertv/gophertunnel v1.43.2-0.20250207144337-b2857453171d h1:JK3btKqp1QUPWLfgXyTUaKa8wUHJnO0Z3jqdKebbNq0=
github.com/sandertv/gophertunnel v1.43.2-0.20250207144337-b2857453171d/go.mod h1:XuEJo+ARim+NKiD90Z56sQRcDtCOErz26e2bt3LEd9I=
github.com/sandertv/gophertunnel v1.44.1-0.20250211173944-b0dbda19ad6a h1:1dFrVCwemoApT79gWY0PMjMTjZooS553YNRvml6IA2Y=
github.com/sandertv/gophertunnel v1.44.1-0.20250211173944-b0dbda19ad6a/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 h1:EI9+KE1EwvMLBWwjpRDc+fEM+prwxDYbslddQGtrmhM=
github.com/segmentio/fasthash v1.0.3/go.mod h1:waKX8l2N8yckOgmSsXJi7x1ZfdKZ4x7KRMzBtS3oedY= 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/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@@ -61,6 +65,8 @@ 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.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 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 h1:1UoZQm6f0P/ZO0w1Ri+f+ifG/gXhegadRdwBIXEFWDo= 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/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.0.0-20190321063152-3fc05d484e9f/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
@@ -80,10 +86,16 @@ 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.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= 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/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= 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.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE= golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE=
golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/oauth2 v0.26.0 h1:afQXWNNaeC4nvZ0Ed9XvCCzXM6UHJG7iCg0W4fPqSBE=
golang.org/x/oauth2 v0.26.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-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-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -112,6 +124,8 @@ 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.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 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=

View File

@@ -0,0 +1,13 @@
{
"remappedMetas": {
"minecraft:boat": {
"9": "minecraft:pale_oak_boat"
},
"minecraft:chest_boat": {
"9": "minecraft:pale_oak_chest_boat"
},
"minecraft:spawn_egg": {
"146": "minecraft:creaking_spawn_egg"
}
}
}

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -3,8 +3,8 @@ package legacyver
import ( import (
"fmt" "fmt"
"github.com/akmalfairuz/legacy-version/internal/item" "github.com/akmalfairuz/legacy-version/internal/item"
"github.com/akmalfairuz/legacy-version/legacyver/proto"
"github.com/akmalfairuz/legacy-version/mapping" "github.com/akmalfairuz/legacy-version/mapping"
"github.com/akmalfairuz/legacy-version/packbuilder"
"github.com/df-mc/dragonfly/server/world" "github.com/df-mc/dragonfly/server/world"
"github.com/samber/lo" "github.com/samber/lo"
"github.com/sandertv/gophertunnel/minecraft" "github.com/sandertv/gophertunnel/minecraft"
@@ -39,6 +39,10 @@ type ItemTranslator interface {
Register(item world.CustomItem, replacement string) Register(item world.CustomItem, replacement string)
// CustomItems lists all custom items used as substitutes, with the runtime id as the key // CustomItems lists all custom items used as substitutes, with the runtime id as the key
CustomItems() map[int32]world.CustomItem CustomItems() map[int32]world.CustomItem
// DowngradeLegacyItemRegistry ...
DowngradeLegacyItemRegistry(entries []proto.LegacyItemRegistryEntry) []proto.LegacyItemRegistryEntry
// UpgradeLegacyItemRegistry ...
UpgradeLegacyItemRegistry(entries []proto.LegacyItemRegistryEntry) []proto.LegacyItemRegistryEntry
} }
type DefaultItemTranslator struct { type DefaultItemTranslator struct {
@@ -480,51 +484,84 @@ func (t *DefaultItemTranslator) DowngradeItemPackets(pks []packet.Packet, _ *min
}) })
pk.EventData = (itemType.NetworkID << 16) | int32(itemType.MetadataValue) pk.EventData = (itemType.NetworkID << 16) | int32(itemType.MetadataValue)
} }
case *packet.StartGame: //case *packet.ItemRegistry:
for i, entry := range pk.Items { // for _, i := range t.CustomItems() {
if !entry.ComponentBased { // name, _ := i.EncodeItem()
itemType := t.DowngradeItemType(protocol.ItemType{ // pk.Items = append(pk.Items, protocol.ItemEntry{
NetworkID: int32(entry.RuntimeID), // Name: name,
MetadataValue: 0, // Data: packbuilder.Components(i),
}) // })
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.RegisterEntryRID(entry.Name, int32(entry.RuntimeID))
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) result = append(result, pk)
} }
return result return result
} }
func (t *DefaultItemTranslator) DowngradeLegacyItemRegistry(entries []proto.LegacyItemRegistryEntry) []proto.LegacyItemRegistryEntry {
for i, entry := range entries {
if !entry.ComponentBased {
itemType := t.DowngradeItemType(protocol.ItemType{
NetworkID: int32(entry.RuntimeID),
MetadataValue: 0,
})
if itemType.NetworkID == t.mapping.Air() {
removeIndex(entries, 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.RegisterEntryRID(entry.Name, int32(entry.RuntimeID), 2)
entry.RuntimeID = int16(t.mapping.RegisterEntry(entry.Name))
}
entries[i] = entry
}
for rid, i := range t.CustomItems() {
name, _ := i.EncodeItem()
entries = append(entries, proto.LegacyItemRegistryEntry{
Name: name,
RuntimeID: int16(rid),
ComponentBased: true,
})
}
return entries
}
func (t *DefaultItemTranslator) UpgradeLegacyItemRegistry(entries []proto.LegacyItemRegistryEntry) []proto.LegacyItemRegistryEntry {
for i, entry := range entries {
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.RegisterEntryRID(entry.Name, int32(entry.RuntimeID), 2)
entry.RuntimeID = int16(t.mapping.RegisterEntry(entry.Name))
}
entries[i] = entry
}
for rid, i := range t.CustomItems() {
name, _ := i.EncodeItem()
entries = append(entries, proto.LegacyItemRegistryEntry{
Name: name,
RuntimeID: int16(rid),
ComponentBased: true,
})
}
return entries
}
func (t *DefaultItemTranslator) UpgradeItemPackets(pks []packet.Packet, _ *minecraft.Conn) (result []packet.Packet) { func (t *DefaultItemTranslator) UpgradeItemPackets(pks []packet.Packet, _ *minecraft.Conn) (result []packet.Packet) {
for _, pk := range pks { for _, pk := range pks {
switch pk := pk.(type) { switch pk := pk.(type) {
@@ -703,41 +740,14 @@ func (t *DefaultItemTranslator) UpgradeItemPackets(pks []packet.Packet, _ *minec
}) })
pk.EventData = (itemType.NetworkID << 16) | int32(itemType.MetadataValue) pk.EventData = (itemType.NetworkID << 16) | int32(itemType.MetadataValue)
} }
case *packet.StartGame: //case *packet.ItemRegistry:
for i, entry := range pk.Items { // for _, i := range t.CustomItems() {
if !entry.ComponentBased { // name, _ := i.EncodeItem()
itemType := t.UpgradeItemType(protocol.ItemType{ // pk.Items = append(pk.Items, protocol.ItemEntry{
NetworkID: int32(entry.RuntimeID), // Name: name,
MetadataValue: 0, // Data: packbuilder.Components(i),
}) // })
entry.RuntimeID = int16(itemType.NetworkID) // }
var ok bool
if entry.Name, ok = t.latest.ItemRuntimeIDToName(itemType.NetworkID); !ok {
panic(itemType)
}
} else {
t.latest.RegisterEntryRID(entry.Name, int32(entry.RuntimeID))
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) result = append(result, pk)
} }

View File

@@ -53,7 +53,7 @@ type AddPlayer struct {
// attributes of the entity. // attributes of the entity.
EntityProperties protocol.EntityProperties EntityProperties protocol.EntityProperties
// AbilityData represents various data about the abilities of a player, such as ability layers or permissions. // AbilityData represents various data about the abilities of a player, such as ability layers or permissions.
AbilityData protocol.AbilityData AbilityData proto.AbilityData
// EntityLinks is a list of entity links that are currently active on the player. These links alter the // EntityLinks is a list of entity links that are currently active on the player. These links alter the
// way the player shows up when first spawned in terms of it shown as riding an entity. Setting these // way the player shows up when first spawned in terms of it shown as riding an entity. Setting these
// links is important for new viewers to see the player is riding another entity. // links is important for new viewers to see the player is riding another entity.

View File

@@ -0,0 +1,115 @@
package legacypacket
import (
"github.com/akmalfairuz/legacy-version/legacyver/proto"
"github.com/sandertv/gophertunnel/minecraft/protocol"
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
)
const (
BossEventShow = iota
BossEventRegisterPlayer
BossEventHide
BossEventUnregisterPlayer
BossEventHealthPercentage
BossEventTitle
BossEventAppearanceProperties
BossEventTexture
BossEventRequest
)
const (
BossEventColourGrey = iota
BossEventColourBlue
BossEventColourRed
BossEventColourGreen
BossEventColourYellow
BossEventColourPurple
BossEventColourWhite
)
// BossEvent is sent by the server to make a specific 'boss event' occur in the
// world. It includes features such as showing a boss bar to the player and
// turning the sky dark.
type BossEvent struct {
// BossEntityUniqueID is the unique ID of the boss entity that the boss
// event sent involves. By default, the health percentage and title of the
// boss bar depend on the health and name tag of this entity. If
// BossEntityUniqueID is the same as the client's entity unique ID, its
// HealthPercentage and BossBarTitle can be freely altered.
BossEntityUniqueID int64
// EventType is the type of the event. The fields written depend on the
// event type set, and some event types are sent by the client, whereas
// others are sent by the server. The event type is one of the constants
// above.
EventType uint32
// PlayerUniqueID is the unique ID of the player that is registered to or
// unregistered from the boss fight. It is set if EventType is either
// BossEventRegisterPlayer or BossEventUnregisterPlayer.
PlayerUniqueID int64
// BossBarTitle is the title shown above the boss bar. It may be set to set
// a different title if the BossEntityUniqueID matches the client's entity
// unique ID.
BossBarTitle string
// FilteredBossBarTitle is a filtered version of BossBarTitle with all the
// profanity removed. The client will use this over BossBarTitle if this
// field is not empty and they have the "Filter Profanity" setting enabled.
FilteredBossBarTitle string
// HealthPercentage is the percentage of health that is shown in the boss
// bar (0.0-1.0). The HealthPercentage may be set to a specific value if the
// BossEntityUniqueID matches the client's entity unique ID.
HealthPercentage float32
// ScreenDarkening currently seems not to do anything.
ScreenDarkening uint16
// Colour is the colour of the boss bar that is shown when a player is
// subscribed. It is only set if the EventType is BossEventShow,
// BossEventAppearanceProperties or BossEventTexture. This is functional as
// of 1.18 and can be any of the BossEventColour constants listed above.
Colour uint32
// Overlay is the overlay of the boss bar that is shown on top of the boss
// bar when a player is subscribed. It currently does not function. It is
// only set if the EventType is BossEventShow, BossEventAppearanceProperties
// or BossEventTexture.
Overlay uint32
}
// ID ...
func (*BossEvent) ID() uint32 {
return packet.IDBossEvent
}
func (pk *BossEvent) Marshal(io protocol.IO) {
io.Varint64(&pk.BossEntityUniqueID)
io.Varuint32(&pk.EventType)
switch pk.EventType {
case BossEventShow:
io.String(&pk.BossBarTitle)
if proto.IsProtoGTE(io, proto.ID776) {
io.String(&pk.FilteredBossBarTitle)
}
io.Float32(&pk.HealthPercentage)
io.Uint16(&pk.ScreenDarkening)
io.Varuint32(&pk.Colour)
io.Varuint32(&pk.Overlay)
case BossEventRegisterPlayer, BossEventUnregisterPlayer, BossEventRequest:
io.Varint64(&pk.PlayerUniqueID)
case BossEventHide:
// No extra payload for this boss event type.
case BossEventHealthPercentage:
io.Float32(&pk.HealthPercentage)
case BossEventTitle:
io.String(&pk.BossBarTitle)
if proto.IsProtoGTE(io, proto.ID776) {
io.String(&pk.FilteredBossBarTitle)
}
case BossEventAppearanceProperties:
io.Uint16(&pk.ScreenDarkening)
io.Varuint32(&pk.Colour)
io.Varuint32(&pk.Overlay)
case BossEventTexture:
io.Varuint32(&pk.Colour)
io.Varuint32(&pk.Overlay)
default:
io.UnknownEnumOption(pk.EventType, "boss event type")
}
}

View File

@@ -0,0 +1,36 @@
package legacypacket
import (
"github.com/akmalfairuz/legacy-version/legacyver/proto"
"github.com/sandertv/gophertunnel/minecraft/protocol"
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
)
const (
CameraAunAssistPresetOperationSet = iota
CameraAunAssistPresetOperationAddToExisting
)
// CameraAimAssistPresets is sent by the server to the client to provide a list of categories and presets
// that can be used when sending a CameraAimAssist packet or a CameraInstruction including aim assist.
type CameraAimAssistPresets struct {
// CategoryGroups is a list of groups of categories which can be referenced by one of the Presets.
CategoryGroups []protocol.CameraAimAssistCategoryGroup
// Presets is a list of presets which define a base for how aim assist should behave
Presets []protocol.CameraAimAssistPreset
// Operation is the operation to perform with the presets. It is one of the constants above.
Operation byte
}
// ID ...
func (*CameraAimAssistPresets) ID() uint32 {
return packet.IDCameraAimAssistPresets
}
func (pk *CameraAimAssistPresets) Marshal(io protocol.IO) {
protocol.Slice(io, &pk.CategoryGroups)
protocol.Slice(io, &pk.Presets)
if proto.IsProtoGTE(io, proto.ID776) {
io.Uint8(&pk.Operation)
}
}

View File

@@ -0,0 +1,24 @@
package legacypacket
import (
"github.com/akmalfairuz/legacy-version/legacyver/proto"
"github.com/sandertv/gophertunnel/minecraft/protocol"
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
)
// ClientCheatAbility functions the same as UpdateAbilities. It is unclear why these two were separated.
//
// Deprecated: ClientCheatAbility is deprecated as of 1.20.10.
type ClientCheatAbility struct {
// AbilityData represents various data about the abilities of a player, such as ability layers or permissions.
AbilityData proto.AbilityData
}
// ID ...
func (*ClientCheatAbility) ID() uint32 {
return packet.IDClientCheatAbility
}
func (pk *ClientCheatAbility) Marshal(io protocol.IO) {
protocol.Single(io, &pk.AbilityData)
}

View File

@@ -0,0 +1,88 @@
package legacypacket
import (
"github.com/akmalfairuz/legacy-version/legacyver/proto"
"github.com/sandertv/gophertunnel/minecraft/protocol"
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
)
const (
CommandBlockImpulse = iota
CommandBlockRepeating
CommandBlockChain
)
// CommandBlockUpdate is sent by the client to update a command block at a specific position. The command
// block may be either a physical block or an entity.
type CommandBlockUpdate struct {
// Block specifies if the command block updated was an actual physical block. If false, the command block
// is in a minecart and has an entity runtime ID instead.
Block bool
// Position is the position of the command block updated. It is only set if Block is set to true. Nothing
// happens if no command block is set at this position.
Position protocol.BlockPos
// Mode is the mode of the command block. It is either CommandBlockImpulse, CommandBlockChain or
// CommandBlockRepeat. It is only set if Block is set to true.
Mode uint32
// NeedsRedstone specifies if the command block needs to be powered by redstone to be activated. If false,
// the command block is always active. The field is only set if Block is set to true.
NeedsRedstone bool
// Conditional specifies the behaviour of the command block if the command block before it (the opposite
// side of the direction the arrow if facing) fails to execute. If set to false, it will activate at all
// times, whereas if set to true, it will activate only if the previous command block executed
// successfully. The field is only set if Block is set to true.
Conditional bool
// MinecartEntityRuntimeID is the runtime ID of the minecart entity carrying the command block that is
// updated. It is set only if Block is set to false.
MinecartEntityRuntimeID uint64
// Command is the command currently entered in the command block. This is the command that is executed
// when the command block is activated.
Command string
// LastOutput is the output of the last command executed by the command block. It may be left empty to
// show simply no output at all, in combination with setting ShouldTrackOutput to false.
LastOutput string
// Name is the name of the command block updated. If not empty, it will show this name hovering above the
// command block when hovering over the block with the cursor.
Name string
// FilteredName is a filtered version of Name with all the profanity removed. The client will use this
// over Name if this field is not empty and they have the "Filter Profanity" setting enabled.
FilteredName string
// ShouldTrackOutput specifies if the command block tracks output. If set to false, the output box won't
// be shown within the command block.
ShouldTrackOutput bool
// TickDelay is the delay in ticks between executions of a command block, if it is a repeating command
// block.
TickDelay int32
// ExecuteOnFirstTick specifies if the command block should execute on the first tick, AKA as soon as the
// command block is enabled.
ExecuteOnFirstTick bool
}
// ID ...
func (*CommandBlockUpdate) ID() uint32 {
return packet.IDCommandBlockUpdate
}
func (pk *CommandBlockUpdate) Marshal(io protocol.IO) {
io.Bool(&pk.Block)
if pk.Block {
io.UBlockPos(&pk.Position)
io.Varuint32(&pk.Mode)
io.Bool(&pk.NeedsRedstone)
io.Bool(&pk.Conditional)
} else {
io.Varuint64(&pk.MinecartEntityRuntimeID)
}
io.String(&pk.Command)
io.String(&pk.LastOutput)
io.String(&pk.Name)
if proto.IsProtoGTE(io, proto.ID776) {
io.String(&pk.FilteredName)
}
io.Bool(&pk.ShouldTrackOutput)
io.Int32(&pk.TickDelay)
io.Bool(&pk.ExecuteOnFirstTick)
}

View File

@@ -0,0 +1,53 @@
package legacypacket
import (
"github.com/akmalfairuz/legacy-version/legacyver/proto"
"github.com/sandertv/gophertunnel/minecraft/protocol"
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
)
// CreativeContent is a packet sent by the server to set the creative inventory's content for a player.
// Introduced in 1.16, this packet replaces the previous method - sending an InventoryContent packet with
// creative inventory window ID.
// As of v1.21.60, this packet is no longer required to be sent as part of the login sequence however the
// client will crash if they try to open their creative inventory before receiving this packet. Every item
// must be part of a group, any items that are not part of a group will need to reference an "anonymous group"
// which has an empty name OR no icon. The order of Groups and Items is how the client will render items
// in the creative inventory compared to the previous, hard coded order.
//
// Below is an example of defining 2 ungrouped items, 2 grouped items and then another 2 ungrouped items, all
// in the nature category.
//
// CreativeContent{
// Groups: []protocol.CreativeGroup{
// {Category: 1}, // No name or icon, this is the "anonymous group"
// {Category: 1, Name: "itemGroup.name.planks", Icon: protocol.ItemStack{...}}, // A "planks" group
// {Category: 1}, // Another "anonymous group"
// },
// Items: []protocol.CreativeItem{
// {CreativeItemNetworkID: 0, Item: protocol.ItemStack{...}, GroupIndex: 0}, // Ungrouped before "planks"
// {CreativeItemNetworkID: 1, Item: protocol.ItemStack{...}, GroupIndex: 0}, // Ungrouped before "planks"
// {CreativeItemNetworkID: 2, Item: protocol.ItemStack{...}, GroupIndex: 1}, // Grouped under the "planks" group
// {CreativeItemNetworkID: 3, Item: protocol.ItemStack{...}, GroupIndex: 1}, // Grouped under the "planks" group
// {CreativeItemNetworkID: 4, Item: protocol.ItemStack{...}, GroupIndex: 2}, // Ungrouped after "planks"
// {CreativeItemNetworkID: 5, Item: protocol.ItemStack{...}, GroupIndex: 2}, // Ungrouped after "planks"
// }
// }
type CreativeContent struct {
// Groups is a list of the groups that should be added to the creative inventory.
Groups []protocol.CreativeGroup
// Items is a list of the items that should be added to the creative inventory.
Items []proto.CreativeItem
}
// ID ...
func (*CreativeContent) ID() uint32 {
return packet.IDCreativeContent
}
func (pk *CreativeContent) Marshal(io protocol.IO) {
if proto.IsProtoGTE(io, proto.ID776) {
protocol.Slice(io, &pk.Groups)
}
protocol.Slice(io, &pk.Items)
}

View File

@@ -0,0 +1,25 @@
package legacypacket
import (
"github.com/akmalfairuz/legacy-version/legacyver/proto"
"github.com/sandertv/gophertunnel/minecraft/protocol"
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
)
// ItemRegistry is sent by the server to send the client a list of available items and attach client-side
// components to a custom item. This packet was formerly known as the ItemComponent packet before 1.21.60,
// which did not include item definitions but only the components.
type ItemRegistry struct {
// Items is a list of all items with their legacy IDs which are available in the game. Failing to send any
// of the items that are in the game will crash mobile clients. Any custom components are also attached to
// the items in this list.
Items []proto.ItemEntry
}
// ID ...
func (*ItemRegistry) ID() uint32 {
return packet.IDItemRegistry
}
func (pk *ItemRegistry) Marshal(io protocol.IO) {
protocol.Slice(io, &pk.Items)
}

View File

@@ -200,7 +200,7 @@ type StartGame struct {
Blocks []protocol.BlockEntry Blocks []protocol.BlockEntry
// Items is a list of all items with their legacy IDs which are available in the game. Failing to send any // Items is a list of all items with their legacy IDs which are available in the game. Failing to send any
// of the items that are in the game will crash mobile clients. // of the items that are in the game will crash mobile clients.
Items []protocol.ItemEntry Items []proto.LegacyItemRegistryEntry
// MultiPlayerCorrelationID is a unique ID specifying the multi-player session of the player. A random // MultiPlayerCorrelationID is a unique ID specifying the multi-player session of the player. A random
// UUID should be filled out for this field. // UUID should be filled out for this field.
MultiPlayerCorrelationID string MultiPlayerCorrelationID string
@@ -316,7 +316,11 @@ func (pk *StartGame) Marshal(io protocol.IO) {
io.Int64(&pk.Time) io.Int64(&pk.Time)
io.Varint32(&pk.EnchantmentSeed) io.Varint32(&pk.EnchantmentSeed)
protocol.Slice(io, &pk.Blocks) protocol.Slice(io, &pk.Blocks)
protocol.Slice(io, &pk.Items) if proto.IsProtoLT(io, proto.ID776) {
protocol.Slice(io, &pk.Items)
} else {
proto.EmptySlice(io, &pk.Items)
}
io.String(&pk.MultiPlayerCorrelationID) io.String(&pk.MultiPlayerCorrelationID)
io.Bool(&pk.ServerAuthoritativeInventory) io.Bool(&pk.ServerAuthoritativeInventory)
io.String(&pk.GameVersion) io.String(&pk.GameVersion)

View File

@@ -0,0 +1,82 @@
package legacypacket
import (
"github.com/akmalfairuz/legacy-version/legacyver/proto"
"github.com/sandertv/gophertunnel/minecraft/protocol"
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
)
const (
StructureBlockData = iota
StructureBlockSave
StructureBlockLoad
StructureBlockCorner
StructureBlockInvalid
StructureBlockExport
)
const (
StructureRedstoneSaveModeMemory = iota
StructureRedstoneSaveModeDisk
)
// StructureBlockUpdate is sent by the client when it updates a structure block using the in-game UI. The
// data it contains depends on the type of structure block that it is. In Minecraft Bedrock Edition v1.11,
// there is only the Export structure block type, but in v1.13 the ones present in Java Edition will,
// according to the wiki, be added too.
type StructureBlockUpdate struct {
// Position is the position of the structure block that is updated.
Position protocol.BlockPos
// StructureName is the name of the structure that was set in the structure block's UI. This is the name
// used to export the structure to a file.
StructureName string
// FilteredStructureName is a filtered version of StructureName with all the profanity removed. The client
// will use this over StructureName if this field is not empty and they have the "Filter Profanity"
// setting enabled.
FilteredStructureName string
// DataField is the name of a function to run, usually used during natural generation. A description can
// be found here: https://minecraft.wiki/w/Structure_Block#Data.
DataField string
// IncludePlayers specifies if the 'Include Players' toggle has been enabled, meaning players are also
// exported by the structure block.
IncludePlayers bool
// ShowBoundingBox specifies if the structure block should have its bounds outlined. A thin line will
// encapsulate the bounds of the structure if set to true.
ShowBoundingBox bool
// StructureBlockType is the type of the structure block updated. A list of structure block types that
// will be used can be found in the constants above.
StructureBlockType int32
// Settings is a struct of settings that should be used for exporting the structure. These settings are
// identical to the last sent in the StructureBlockUpdate packet by the client.
Settings protocol.StructureSettings
// RedstoneSaveMode is the mode that should be used to save the structure when used with redstone. In
// Java Edition, this is always stored in memory, but in Bedrock Edition it can be stored either to disk
// or memory. See the constants above for the options.
RedstoneSaveMode int32
// ShouldTrigger specifies if the structure block should be triggered immediately after this packet
// reaches the server.
ShouldTrigger bool
// Waterlogged specifies if non-air blocks replace water or combine with water.
Waterlogged bool
}
// ID ...
func (*StructureBlockUpdate) ID() uint32 {
return packet.IDStructureBlockUpdate
}
func (pk *StructureBlockUpdate) Marshal(io protocol.IO) {
io.UBlockPos(&pk.Position)
io.String(&pk.StructureName)
if proto.IsProtoGTE(io, proto.ID776) {
io.String(&pk.FilteredStructureName)
}
io.String(&pk.DataField)
io.Bool(&pk.IncludePlayers)
io.Bool(&pk.ShowBoundingBox)
io.Varint32(&pk.StructureBlockType)
protocol.Single(io, &pk.Settings)
io.Varint32(&pk.RedstoneSaveMode)
io.Bool(&pk.ShouldTrigger)
io.Bool(&pk.Waterlogged)
}

View File

@@ -0,0 +1,23 @@
package legacypacket
import (
"github.com/akmalfairuz/legacy-version/legacyver/proto"
"github.com/sandertv/gophertunnel/minecraft/protocol"
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
)
// UpdateAbilities is a packet sent from the server to the client to update the abilities of the player. It, along with
// the UpdateAdventureSettings packet, are replacements of the AdventureSettings packet since v1.19.10.
type UpdateAbilities struct {
// AbilityData represents various data about the abilities of a player, such as ability layers or permissions.
AbilityData proto.AbilityData
}
// ID ...
func (*UpdateAbilities) ID() uint32 {
return packet.IDUpdateAbilities
}
func (pk *UpdateAbilities) Marshal(io protocol.IO) {
protocol.Single(io, &pk.AbilityData)
}

108
legacyver/proto/ability.go Normal file
View File

@@ -0,0 +1,108 @@
package proto
import (
"github.com/sandertv/gophertunnel/minecraft/protocol"
)
// AbilityData represents various data about the abilities of a player, such as ability layers or permissions.
type AbilityData struct {
// EntityUniqueID is a unique identifier of the player. It appears it is not required to fill this field
// out with a correct value. Simply writing 0 seems to work.
EntityUniqueID int64
// PlayerPermissions is the permission level of the player as it shows up in the player list built up using
// the PlayerList packet.
PlayerPermissions byte
// CommandPermissions is a set of permissions that specify what commands a player is allowed to execute.
CommandPermissions byte
// Layers contains all ability layers and their potential values. This should at least have one entry, being the
// base layer.
Layers []AbilityLayer
}
// Marshal encodes/decodes an AbilityData.
func (x *AbilityData) Marshal(r protocol.IO) {
r.Int64(&x.EntityUniqueID)
r.Uint8(&x.PlayerPermissions)
r.Uint8(&x.CommandPermissions)
protocol.SliceUint8Length(r, &x.Layers)
}
// ToLatest ...
func (x *AbilityData) ToLatest() protocol.AbilityData {
layers := make([]protocol.AbilityLayer, len(x.Layers))
for i, layer := range x.Layers {
layers[i] = layer.ToLatest()
}
return protocol.AbilityData{
EntityUniqueID: x.EntityUniqueID,
PlayerPermissions: x.PlayerPermissions,
CommandPermissions: x.CommandPermissions,
Layers: layers,
}
}
// FromLatest ...
func (x *AbilityData) FromLatest(y protocol.AbilityData) AbilityData {
layers := make([]AbilityLayer, len(y.Layers))
for i, layer := range y.Layers {
layers[i] = (&AbilityLayer{}).FromLatest(layer)
}
x.EntityUniqueID = y.EntityUniqueID
x.PlayerPermissions = y.PlayerPermissions
x.CommandPermissions = y.CommandPermissions
x.Layers = layers
return *x
}
// AbilityLayer represents the abilities of a specific layer, such as the base layer or the spectator layer.
type AbilityLayer struct {
// Type represents the type of the layer. This is one of the AbilityLayerType constants defined above.
Type uint16
// Abilities is a set of abilities that are enabled for the layer. This is one of the Ability constants defined
// above.
Abilities uint32
// Values is a set of values that are associated with the enabled abilities, representing the values of the
// abilities.
Values uint32
// FlySpeed is the default horizontal fly speed of the layer.
FlySpeed float32
// VerticalFlySpeed is the default vertical fly speed of the layer.
VerticalFlySpeed float32
// WalkSpeed is the default walk speed of the layer.
WalkSpeed float32
}
// Marshal encodes/decodes an AbilityLayer.
func (x *AbilityLayer) Marshal(r protocol.IO) {
r.Uint16(&x.Type)
r.Uint32(&x.Abilities)
r.Uint32(&x.Values)
r.Float32(&x.FlySpeed)
if IsProtoGTE(r, ID776) {
r.Float32(&x.VerticalFlySpeed)
}
r.Float32(&x.WalkSpeed)
}
// ToLatest ...
func (x *AbilityLayer) ToLatest() protocol.AbilityLayer {
return protocol.AbilityLayer{
Type: x.Type,
Abilities: x.Abilities,
Values: x.Values,
FlySpeed: x.FlySpeed,
VerticalFlySpeed: x.VerticalFlySpeed,
WalkSpeed: x.WalkSpeed,
}
}
// FromLatest ...
func (x *AbilityLayer) FromLatest(y protocol.AbilityLayer) AbilityLayer {
x.Type = y.Type
x.Abilities = y.Abilities
x.Values = y.Values
x.FlySpeed = y.FlySpeed
x.VerticalFlySpeed = y.VerticalFlySpeed
x.WalkSpeed = y.WalkSpeed
return *x
}

View File

@@ -41,6 +41,10 @@ type CameraPreset struct {
// Radius is only used in a follow_orbit camera and controls how far away from the player the camera should // Radius is only used in a follow_orbit camera and controls how far away from the player the camera should
// be rendered. // be rendered.
Radius protocol.Optional[float32] Radius protocol.Optional[float32]
// MinYawLimit is the minimum yaw limit of the camera.
MinYawLimit protocol.Optional[float32]
// MaxYawLimit is the maximum yaw limit of the camera.
MaxYawLimit protocol.Optional[float32]
// AudioListener defines where the audio should be played from when using this preset. This is one of the // AudioListener defines where the audio should be played from when using this preset. This is one of the
// constants above. // constants above.
AudioListener protocol.Optional[byte] AudioListener protocol.Optional[byte]
@@ -95,6 +99,8 @@ func (x *CameraPreset) ToLatest() protocol.CameraPreset {
ViewOffset: x.ViewOffset, ViewOffset: x.ViewOffset,
EntityOffset: x.EntityOffset, EntityOffset: x.EntityOffset,
Radius: x.Radius, Radius: x.Radius,
MinYawLimit: x.MinYawLimit,
MaxYawLimit: x.MaxYawLimit,
AudioListener: x.AudioListener, AudioListener: x.AudioListener,
PlayerEffects: x.PlayerEffects, PlayerEffects: x.PlayerEffects,
AlignTargetAndCameraForward: x.AlignTargetAndCameraForward, AlignTargetAndCameraForward: x.AlignTargetAndCameraForward,
@@ -123,6 +129,10 @@ func (x *CameraPreset) Marshal(r protocol.IO) {
if IsProtoGTE(r, ID766) { if IsProtoGTE(r, ID766) {
protocol.OptionalFunc(r, &x.TrackingRadius, r.Float32) protocol.OptionalFunc(r, &x.TrackingRadius, r.Float32)
} }
if IsProtoGTE(r, ID776) {
protocol.OptionalFunc(r, &x.MinYawLimit, r.Float32)
protocol.OptionalFunc(r, &x.MaxYawLimit, r.Float32)
}
protocol.OptionalFunc(r, &x.ViewOffset, r.Vec2) protocol.OptionalFunc(r, &x.ViewOffset, r.Vec2)
if IsProtoGTE(r, ID729) { if IsProtoGTE(r, ID729) {
protocol.OptionalFunc(r, &x.EntityOffset, r.Vec3) protocol.OptionalFunc(r, &x.EntityOffset, r.Vec3)

View File

@@ -0,0 +1,41 @@
package proto
import "github.com/sandertv/gophertunnel/minecraft/protocol"
// CreativeItem represents a creative item present in the creative inventory.
type CreativeItem struct {
// CreativeItemNetworkID is a unique ID for the creative item. It has to be unique for each creative item
// sent to the client. An incrementing ID per creative item does the job.
CreativeItemNetworkID uint32
// Item is the item that should be added to the creative inventory.
Item protocol.ItemStack
// GroupIndex is the index of the group that the item should be placed in. It is the index of the group in
// the CreativeContent packet previously sent to the client.
GroupIndex uint32
}
// ToLatest ...
func (x *CreativeItem) ToLatest() protocol.CreativeItem {
return protocol.CreativeItem{
CreativeItemNetworkID: x.CreativeItemNetworkID,
Item: x.Item,
GroupIndex: x.GroupIndex,
}
}
// FromLatest ...
func (x *CreativeItem) FromLatest(y protocol.CreativeItem) CreativeItem {
x.CreativeItemNetworkID = y.CreativeItemNetworkID
x.Item = y.Item
x.GroupIndex = y.GroupIndex
return *x
}
// Marshal encodes/decodes a CreativeItem.
func (x *CreativeItem) Marshal(r protocol.IO) {
r.Varuint32(&x.CreativeItemNetworkID)
r.Item(&x.Item)
if IsProtoGTE(r, ID776) {
r.Varuint32(&x.GroupIndex)
}
}

View File

@@ -3,6 +3,7 @@ package proto
import "github.com/sandertv/gophertunnel/minecraft/protocol" import "github.com/sandertv/gophertunnel/minecraft/protocol"
const ( const (
ID776 = 776 // v1.21.60
ID766 = 766 // v1.21.50 ID766 = 766 // v1.21.50
ID748 = 748 // v1.21.40 ID748 = 748 // v1.21.40
ID729 = 729 // v1.21.30 ID729 = 729 // v1.21.30

92
legacyver/proto/item.go Normal file
View File

@@ -0,0 +1,92 @@
package proto
import (
"github.com/sandertv/gophertunnel/minecraft/nbt"
"github.com/sandertv/gophertunnel/minecraft/protocol"
)
// LegacyItemRegistryEntry is an item sent in the StartGame item table. It holds a name and a legacy ID, which is used to
// point back to that name.
type LegacyItemRegistryEntry struct {
// Name if the name of the item, which is a name like 'minecraft:stick'.
Name string
// RuntimeID is the ID that is used to identify the item over network. After sending all items in the
// StartGame packet, items will then be identified using these numerical IDs.
RuntimeID int16
// ComponentBased specifies if the item was created using components, meaning the item is a custom item.
ComponentBased bool
}
// Marshal encodes/decodes an LegacyItemRegistryEntry.
func (x *LegacyItemRegistryEntry) Marshal(r protocol.IO) {
r.String(&x.Name)
r.Int16(&x.RuntimeID)
r.Bool(&x.ComponentBased)
}
// FromLatest ...
func (x *LegacyItemRegistryEntry) FromLatest(y protocol.ItemEntry) LegacyItemRegistryEntry {
x.Name = y.Name
x.RuntimeID = y.RuntimeID
x.ComponentBased = y.ComponentBased
return *x
}
// ToLatest ...
func (x *LegacyItemRegistryEntry) ToLatest() protocol.ItemEntry {
return protocol.ItemEntry{
Name: x.Name,
RuntimeID: x.RuntimeID,
ComponentBased: x.ComponentBased,
Version: 2,
}
}
// ItemEntry is an item sent in the StartGame item table. It holds a name and a legacy ID, which is used to
// point back to that name.
type ItemEntry struct {
// Name if the name of the item, which is a name like 'minecraft:stick'.
Name string
// RuntimeID is the ID that is used to identify the item over network. After sending all items in the
// StartGame packet, items will then be identified using these numerical IDs.
RuntimeID int16
// ComponentBased specifies if the item was created using components, meaning the item is a custom item.
ComponentBased bool
// Version is the version of the item entry which is used by the client to determine how to handle the
// item entry. It is one of the constants above.
Version int32
// Data is a map containing the components and properties of the item, if the item is component based.
Data map[string]any
}
// ToLatest ...
func (x *ItemEntry) ToLatest() protocol.ItemEntry {
return protocol.ItemEntry{
Name: x.Name,
RuntimeID: x.RuntimeID,
ComponentBased: x.ComponentBased,
Version: x.Version,
Data: x.Data,
}
}
// FromLatest ...
func (x *ItemEntry) FromLatest(y protocol.ItemEntry) ItemEntry {
x.Name = y.Name
x.RuntimeID = y.RuntimeID
x.ComponentBased = y.ComponentBased
x.Version = y.Version
x.Data = y.Data
return *x
}
// Marshal encodes/decodes an ItemEntry.
func (x *ItemEntry) Marshal(r protocol.IO) {
r.String(&x.Name)
if IsProtoGTE(r, ID776) {
r.Int16(&x.RuntimeID)
r.Bool(&x.ComponentBased)
r.Varint32(&x.Version)
}
r.NBT(&x.Data, nbt.NetworkLittleEndian)
}

View File

@@ -90,6 +90,22 @@ func convertPacketFunc(pid uint32, cur func() packet.Packet) func() packet.Packe
return func() packet.Packet { return &legacypacket.StartGame{} } return func() packet.Packet { return &legacypacket.StartGame{} }
case packet.IDCodeBuilderSource: case packet.IDCodeBuilderSource:
return func() packet.Packet { return &legacypacket.CodeBuilderSource{} } return func() packet.Packet { return &legacypacket.CodeBuilderSource{} }
case packet.IDItemRegistry:
return func() packet.Packet { return &legacypacket.ItemRegistry{} }
case packet.IDStructureBlockUpdate:
return func() packet.Packet { return &legacypacket.StructureBlockUpdate{} }
case packet.IDBossEvent:
return func() packet.Packet { return &legacypacket.BossEvent{} }
case packet.IDCameraAimAssistPresets:
return func() packet.Packet { return &legacypacket.CameraAimAssistPresets{} }
case packet.IDCommandBlockUpdate:
return func() packet.Packet { return &legacypacket.CommandBlockUpdate{} }
case packet.IDCreativeContent:
return func() packet.Packet { return &legacypacket.CreativeContent{} }
case packet.IDUpdateAbilities:
return func() packet.Packet { return &legacypacket.UpdateAbilities{} }
case packet.IDClientCheatAbility:
return func() packet.Packet { return &legacypacket.ClientCheatAbility{} }
default: default:
return cur return cur
} }
@@ -313,7 +329,7 @@ func (p *Protocol) downgradePackets(pks []packet.Packet, conn *minecraft.Conn) [
GameType: pk.GameType, GameType: pk.GameType,
EntityMetadata: pk.EntityMetadata, EntityMetadata: pk.EntityMetadata,
EntityProperties: pk.EntityProperties, EntityProperties: pk.EntityProperties,
AbilityData: pk.AbilityData, AbilityData: (&proto.AbilityData{}).FromLatest(pk.AbilityData),
EntityLinks: links, EntityLinks: links,
DeviceID: pk.DeviceID, DeviceID: pk.DeviceID,
BuildPlatform: pk.BuildPlatform, BuildPlatform: pk.BuildPlatform,
@@ -445,7 +461,15 @@ func (p *Protocol) downgradePackets(pks []packet.Packet, conn *minecraft.Conn) [
pk.GameVersion = p.ver pk.GameVersion = p.ver
pk.BaseGameVersion = p.ver pk.BaseGameVersion = p.ver
items := make([]proto.LegacyItemRegistryEntry, len(conn.GameData().Items))
for i, it := range conn.GameData().Items {
items[i] = (&proto.LegacyItemRegistryEntry{}).FromLatest(it)
}
items = p.itemTranslator.DowngradeLegacyItemRegistry(items)
pks[pkIndex] = &legacypacket.StartGame{ pks[pkIndex] = &legacypacket.StartGame{
Items: items,
EntityUniqueID: pk.EntityUniqueID, EntityUniqueID: pk.EntityUniqueID,
EntityRuntimeID: pk.EntityRuntimeID, EntityRuntimeID: pk.EntityRuntimeID,
PlayerGameMode: pk.PlayerGameMode, PlayerGameMode: pk.PlayerGameMode,
@@ -509,7 +533,6 @@ func (p *Protocol) downgradePackets(pks []packet.Packet, conn *minecraft.Conn) [
Time: pk.Time, Time: pk.Time,
EnchantmentSeed: pk.EnchantmentSeed, EnchantmentSeed: pk.EnchantmentSeed,
Blocks: pk.Blocks, Blocks: pk.Blocks,
Items: pk.Items,
MultiPlayerCorrelationID: pk.MultiPlayerCorrelationID, MultiPlayerCorrelationID: pk.MultiPlayerCorrelationID,
ServerAuthoritativeInventory: pk.ServerAuthoritativeInventory, ServerAuthoritativeInventory: pk.ServerAuthoritativeInventory,
GameVersion: pk.GameVersion, GameVersion: pk.GameVersion,
@@ -531,6 +554,75 @@ func (p *Protocol) downgradePackets(pks []packet.Packet, conn *minecraft.Conn) [
Category: pk.Category, Category: pk.Category,
CodeStatus: pk.CodeStatus, CodeStatus: pk.CodeStatus,
} }
case *packet.ItemRegistry:
items := make([]proto.ItemEntry, len(pk.Items))
for i, it := range pk.Items {
items[i] = (&proto.ItemEntry{}).FromLatest(it)
}
pks[pkIndex] = &legacypacket.ItemRegistry{
Items: items,
}
case *packet.StructureBlockUpdate:
pks[pkIndex] = &legacypacket.StructureBlockUpdate{
Position: pk.Position,
StructureName: pk.StructureName,
FilteredStructureName: pk.FilteredStructureName,
DataField: pk.DataField,
IncludePlayers: pk.IncludePlayers,
ShowBoundingBox: pk.ShowBoundingBox,
StructureBlockType: pk.StructureBlockType,
Settings: pk.Settings,
RedstoneSaveMode: pk.RedstoneSaveMode,
ShouldTrigger: pk.ShouldTrigger,
Waterlogged: pk.Waterlogged,
}
case *packet.BossEvent:
pks[pkIndex] = &legacypacket.BossEvent{
BossEntityUniqueID: pk.BossEntityUniqueID,
EventType: pk.EventType,
PlayerUniqueID: pk.PlayerUniqueID,
BossBarTitle: pk.BossBarTitle,
FilteredBossBarTitle: pk.FilteredBossBarTitle,
HealthPercentage: pk.HealthPercentage,
ScreenDarkening: pk.ScreenDarkening,
Colour: pk.Colour,
Overlay: pk.Overlay,
}
case *packet.CameraAimAssistPresets:
pks[pkIndex] = &legacypacket.CameraAimAssistPresets{
CategoryGroups: pk.CategoryGroups,
Presets: pk.Presets,
Operation: pk.Operation,
}
case *packet.CommandBlockUpdate:
pks[pkIndex] = &legacypacket.CommandBlockUpdate{
Block: pk.Block,
Position: pk.Position,
Mode: pk.Mode,
NeedsRedstone: pk.NeedsRedstone,
Conditional: pk.Conditional,
MinecartEntityRuntimeID: pk.MinecartEntityRuntimeID,
Command: pk.Command,
LastOutput: pk.LastOutput,
Name: pk.Name,
FilteredName: pk.FilteredName,
ShouldTrackOutput: pk.ShouldTrackOutput,
TickDelay: pk.TickDelay,
ExecuteOnFirstTick: pk.ExecuteOnFirstTick,
}
case *packet.CreativeContent:
items := make([]proto.CreativeItem, len(pk.Items))
for i, it := range pk.Items {
items[i] = (&proto.CreativeItem{}).FromLatest(it)
}
pks[pkIndex] = &legacypacket.CreativeContent{
Groups: pk.Groups,
Items: items,
}
case *packet.UpdateAbilities:
pks[pkIndex] = &legacypacket.UpdateAbilities{
AbilityData: (&proto.AbilityData{}).FromLatest(pk.AbilityData),
}
} }
} }
@@ -709,7 +801,7 @@ func (p *Protocol) upgradePackets(pks []packet.Packet, conn *minecraft.Conn) []p
GameType: pk.GameType, GameType: pk.GameType,
EntityMetadata: pk.EntityMetadata, EntityMetadata: pk.EntityMetadata,
EntityProperties: pk.EntityProperties, EntityProperties: pk.EntityProperties,
AbilityData: pk.AbilityData, AbilityData: pk.AbilityData.ToLatest(),
EntityLinks: links, EntityLinks: links,
DeviceID: pk.DeviceID, DeviceID: pk.DeviceID,
BuildPlatform: pk.BuildPlatform, BuildPlatform: pk.BuildPlatform,
@@ -901,7 +993,6 @@ func (p *Protocol) upgradePackets(pks []packet.Packet, conn *minecraft.Conn) []p
Time: pk.Time, Time: pk.Time,
EnchantmentSeed: pk.EnchantmentSeed, EnchantmentSeed: pk.EnchantmentSeed,
Blocks: pk.Blocks, Blocks: pk.Blocks,
Items: pk.Items,
MultiPlayerCorrelationID: pk.MultiPlayerCorrelationID, MultiPlayerCorrelationID: pk.MultiPlayerCorrelationID,
ServerAuthoritativeInventory: pk.ServerAuthoritativeInventory, ServerAuthoritativeInventory: pk.ServerAuthoritativeInventory,
GameVersion: pk.GameVersion, GameVersion: pk.GameVersion,
@@ -923,6 +1014,65 @@ func (p *Protocol) upgradePackets(pks []packet.Packet, conn *minecraft.Conn) []p
Category: pk.Category, Category: pk.Category,
CodeStatus: pk.CodeStatus, CodeStatus: pk.CodeStatus,
} }
case *legacypacket.BossEvent:
pks[pkIndex] = &packet.BossEvent{
BossEntityUniqueID: pk.BossEntityUniqueID,
EventType: pk.EventType,
PlayerUniqueID: pk.PlayerUniqueID,
BossBarTitle: pk.BossBarTitle,
FilteredBossBarTitle: pk.FilteredBossBarTitle,
HealthPercentage: pk.HealthPercentage,
ScreenDarkening: pk.ScreenDarkening,
Colour: pk.Colour,
Overlay: pk.Overlay,
}
case *legacypacket.CommandBlockUpdate:
pks[pkIndex] = &packet.CommandBlockUpdate{
Block: pk.Block,
Position: pk.Position,
Mode: pk.Mode,
NeedsRedstone: pk.NeedsRedstone,
Conditional: pk.Conditional,
MinecartEntityRuntimeID: pk.MinecartEntityRuntimeID,
Command: pk.Command,
LastOutput: pk.LastOutput,
Name: pk.Name,
FilteredName: pk.FilteredName,
ShouldTrackOutput: pk.ShouldTrackOutput,
TickDelay: pk.TickDelay,
ExecuteOnFirstTick: pk.ExecuteOnFirstTick,
}
case *legacypacket.CreativeContent:
items := make([]protocol.CreativeItem, len(pk.Items))
for i, it := range pk.Items {
items[i] = it.ToLatest()
}
pks[pkIndex] = &packet.CreativeContent{
Groups: pk.Groups,
Items: items,
}
case *legacypacket.ItemRegistry:
items := make([]protocol.ItemEntry, len(pk.Items))
for i, it := range pk.Items {
items[i] = it.ToLatest()
}
pks[pkIndex] = &packet.ItemRegistry{Items: items}
case *legacypacket.StructureBlockUpdate:
pks[pkIndex] = &packet.StructureBlockUpdate{
Position: pk.Position,
StructureName: pk.StructureName,
FilteredStructureName: pk.FilteredStructureName,
DataField: pk.DataField,
IncludePlayers: pk.IncludePlayers,
ShowBoundingBox: pk.ShowBoundingBox,
StructureBlockType: pk.StructureBlockType,
Settings: pk.Settings,
RedstoneSaveMode: pk.RedstoneSaveMode,
ShouldTrigger: pk.ShouldTrigger,
Waterlogged: pk.Waterlogged,
}
case *legacypacket.UpdateAbilities:
pks[pkIndex] = &packet.UpdateAbilities{AbilityData: pk.AbilityData.ToLatest()}
} }
} }
return pks return pks

View File

@@ -2,12 +2,14 @@ package legacyver
import ( import (
_ "embed" _ "embed"
"github.com/akmalfairuz/legacy-version/internal/chunk"
"github.com/akmalfairuz/legacy-version/legacyver/proto"
"github.com/akmalfairuz/legacy-version/mapping" "github.com/akmalfairuz/legacy-version/mapping"
) )
const ( const (
// ItemVersion766 ... // ItemVersion766 ...
ItemVersion766 = 231 ItemVersion766 = 221
// BlockVersion766 ... // BlockVersion766 ...
BlockVersion766 int32 = (1 << 24) | (21 << 16) | (50 << 8) BlockVersion766 int32 = (1 << 24) | (21 << 16) | (50 << 8)
) )
@@ -20,6 +22,15 @@ var (
//go:embed data/block_states_766.nbt //go:embed data/block_states_766.nbt
blockStateData766 []byte blockStateData766 []byte
itemMappingLatest = mapping.NewItemMapping(itemRuntimeIDData766, requiredItemList766, ItemVersion766, false) itemMapping766 = mapping.NewItemMapping(itemRuntimeIDData766, requiredItemList766, ItemVersion766, false)
blockMappingLatest = mapping.NewBlockMapping(blockStateData766) blockMapping766 = mapping.NewBlockMapping(blockStateData766)
) )
func New766() *Protocol {
return &Protocol{
ver: "1.21.50",
id: proto.ID766,
blockTranslator: NewBlockTranslator(blockMapping766, latestBlockMapping, chunk.NewNetworkPersistentEncoding(blockMapping766, BlockVersion766), chunk.NewBlockPaletteEncoding(blockMapping766, BlockVersion766), false),
itemTranslator: NewItemTranslator(itemMapping766, itemMappingLatest, blockMapping766, blockMappingLatest),
}
}

25
legacyver/v776.go Normal file
View File

@@ -0,0 +1,25 @@
package legacyver
import (
_ "embed"
"github.com/akmalfairuz/legacy-version/mapping"
)
const (
// ItemVersion776 ...
ItemVersion776 = 241
// BlockVersion776 ...
BlockVersion776 int32 = (1 << 24) | (21 << 16) | (60 << 8)
)
var (
//go:embed data/item_runtime_ids_776.nbt
itemRuntimeIDData776 []byte
//go:embed data/required_item_list_776.json
requiredItemList776 []byte
//go:embed data/block_states_776.nbt
blockStateData776 []byte
itemMappingLatest = mapping.NewItemMapping(itemRuntimeIDData776, requiredItemList776, ItemVersion776, false)
blockMappingLatest = mapping.NewBlockMapping(blockStateData776)
)

View File

@@ -12,7 +12,7 @@ type Item interface {
// ItemNameToRuntimeID converts a string ID to an item runtime ID. // ItemNameToRuntimeID converts a string ID to an item runtime ID.
ItemNameToRuntimeID(string) (int32, bool) ItemNameToRuntimeID(string) (int32, bool)
RegisterEntry(string) int32 RegisterEntry(string) int32
RegisterEntryRID(string, int32) RegisterEntryRID(string, int32, uint8)
Air() int32 Air() int32
ItemVersion() uint16 ItemVersion() uint16
} }
@@ -23,13 +23,16 @@ type DefaultItemMapping struct {
itemRuntimeIDsToNames map[int32]string itemRuntimeIDsToNames map[int32]string
// itemNamesToRuntimeIDs holds a map to translate item string IDs to runtime IDs. // itemNamesToRuntimeIDs holds a map to translate item string IDs to runtime IDs.
itemNamesToRuntimeIDs map[string]int32 itemNamesToRuntimeIDs map[string]int32
airRID int32 // itemRuntimeIDToVersion holds a map to translate item runtime IDs to versions.
itemVersion uint16 itemRuntimeIDToVersion map[int32]uint8
airRID int32
itemVersion uint16
} }
func NewItemMapping(itemRuntimeIDData []byte, requiredItemList []byte, itemVersion uint16, direct bool) *DefaultItemMapping { func NewItemMapping(itemRuntimeIDData []byte, requiredItemList []byte, itemVersion uint16, direct bool) *DefaultItemMapping {
itemRuntimeIDsToNames := make(map[int32]string) itemRuntimeIDsToNames := make(map[int32]string)
itemNamesToRuntimeIDs := make(map[string]int32) itemNamesToRuntimeIDs := make(map[string]int32)
itemRuntimeIDToVersion := make(map[int32]uint8)
var airRID *int32 var airRID *int32
if direct { if direct {
@@ -47,8 +50,9 @@ func NewItemMapping(itemRuntimeIDData []byte, requiredItemList []byte, itemVersi
} }
} else { } else {
var m map[string]struct { var m map[string]struct {
RuntimeID int16 `json:"runtime_id"` RuntimeID int16 `json:"runtime_id"`
ComponentBased bool `json:"component_based"` ComponentBased bool `json:"component_based"`
Version *uint8 `json:"version"`
} }
if err := json.Unmarshal(requiredItemList, &m); err != nil { if err := json.Unmarshal(requiredItemList, &m); err != nil {
panic(err) panic(err)
@@ -61,6 +65,9 @@ func NewItemMapping(itemRuntimeIDData []byte, requiredItemList []byte, itemVersi
itemNamesToRuntimeIDs[name] = rid itemNamesToRuntimeIDs[name] = rid
itemRuntimeIDsToNames[rid] = name itemRuntimeIDsToNames[rid] = name
if data.Version != nil {
itemRuntimeIDToVersion[rid] = *data.Version
}
} }
} }
@@ -68,7 +75,7 @@ func NewItemMapping(itemRuntimeIDData []byte, requiredItemList []byte, itemVersi
panic("couldn't find air") panic("couldn't find air")
} }
return &DefaultItemMapping{itemRuntimeIDsToNames: itemRuntimeIDsToNames, itemNamesToRuntimeIDs: itemNamesToRuntimeIDs, itemVersion: itemVersion} return &DefaultItemMapping{itemRuntimeIDsToNames: itemRuntimeIDsToNames, itemNamesToRuntimeIDs: itemNamesToRuntimeIDs, itemRuntimeIDToVersion: itemRuntimeIDToVersion, airRID: *airRID, itemVersion: itemVersion}
} }
func (m *DefaultItemMapping) ItemRuntimeIDToName(runtimeID int32) (name string, found bool) { func (m *DefaultItemMapping) ItemRuntimeIDToName(runtimeID int32) (name string, found bool) {
@@ -85,6 +92,13 @@ func (m *DefaultItemMapping) ItemNameToRuntimeID(name string) (runtimeID int32,
return rid, ok return rid, ok
} }
func (m *DefaultItemMapping) ItemRuntimeIDToVersion(runtimeID int32) (version uint8, found bool) {
defer m.mu.Unlock()
m.mu.Lock()
v, ok := m.itemRuntimeIDToVersion[runtimeID]
return v, ok
}
func (m *DefaultItemMapping) RegisterEntry(name string) int32 { func (m *DefaultItemMapping) RegisterEntry(name string) int32 {
defer m.mu.Unlock() defer m.mu.Unlock()
m.mu.Lock() m.mu.Lock()
@@ -97,12 +111,13 @@ func (m *DefaultItemMapping) RegisterEntry(name string) int32 {
return nextRID return nextRID
} }
func (m *DefaultItemMapping) RegisterEntryRID(name string, rid int32) { func (m *DefaultItemMapping) RegisterEntryRID(name string, rid int32, version uint8) {
defer m.mu.Unlock() defer m.mu.Unlock()
m.mu.Lock() m.mu.Lock()
if _, ok := m.itemNamesToRuntimeIDs[name]; !ok { if _, ok := m.itemNamesToRuntimeIDs[name]; !ok {
m.itemNamesToRuntimeIDs[name] = rid m.itemNamesToRuntimeIDs[name] = rid
m.itemRuntimeIDsToNames[rid] = name m.itemRuntimeIDsToNames[rid] = name
m.itemRuntimeIDToVersion[rid] = version
} }
} }