diff --git a/bot/client.go b/bot/client.go index 6a20cac..8a7ac98 100644 --- a/bot/client.go +++ b/bot/client.go @@ -10,6 +10,7 @@ import ( "github.com/Tnze/go-mc/net" pk "github.com/Tnze/go-mc/net/packet" "github.com/Tnze/go-mc/net/queue" + "github.com/Tnze/go-mc/registry" ) // Client is used to access Minecraft server @@ -18,9 +19,9 @@ type Client struct { Auth Auth // These are filled when login process - Name string - UUID uuid.UUID - ConfigData + Name string + UUID uuid.UUID + Registries registry.NetworkCodec // Ingame packet handlers Events Events diff --git a/bot/configuration.go b/bot/configuration.go index 36e5ff9..9fb345c 100644 --- a/bot/configuration.go +++ b/bot/configuration.go @@ -6,6 +6,7 @@ import ( "github.com/Tnze/go-mc/chat" "github.com/Tnze/go-mc/data/packetid" + "github.com/Tnze/go-mc/nbt" "github.com/Tnze/go-mc/net" pk "github.com/Tnze/go-mc/net/packet" "github.com/Tnze/go-mc/registry" @@ -32,10 +33,6 @@ type ResourcePack struct { PromptMessage *chat.Message // Optional } -type ConfigData struct { - Registries registry.NetworkCodec -} - type ConfigErr struct { Stage string Err error @@ -143,10 +140,50 @@ func (c *Client) joinConfiguration(conn *net.Conn) error { // TODO case packetid.ClientboundConfigRegistryData: + const ErrStage = "registry" // err := p.Scan(pk.NBT(&c.ConfigData.Registries)) // if err != nil { // return ConfigErr{"registry data", err} // } + var registryID pk.Identifier + var length pk.VarInt + + r := bytes.NewReader(p.Data) + _, err := registryID.ReadFrom(r) + if err != nil { + return ConfigErr{ErrStage, err} + } + + _, err = length.ReadFrom(r) + if err != nil { + return ConfigErr{ErrStage, err} + } + + for i := 0; i < int(length); i++ { + var entryId pk.Identifier + var hasData pk.Boolean + var data nbt.RawMessage + _, err = entryId.ReadFrom(r) + if err != nil { + return ConfigErr{ErrStage, err} + } + + _, err = hasData.ReadFrom(r) + if err != nil { + return ConfigErr{ErrStage, err} + } + + if hasData { + _, err = pk.NBT(&data).ReadFrom(r) + if err != nil { + return ConfigErr{ErrStage, err} + } + err = registry.InsertNBTDataIntoRegistry(&c.Registries, string(registryID), string(entryId), data) + if err != nil { + return ConfigErr{ErrStage, err} + } + } + } case packetid.ClientboundConfigResourcePackPop: var id pk.Option[pk.UUID, *pk.UUID] diff --git a/bot/msg/chat.go b/bot/msg/chat.go index 8208cde..e14cfa6 100644 --- a/bot/msg/chat.go +++ b/bot/msg/chat.go @@ -82,15 +82,15 @@ func (m *Manager) handlePlayerChat(packet pk.Packet) error { unpackedMsg, err := body.Unpack(&m.SignatureCache) if err != nil { - return InvalidChatPacket + return InvalidChatPacket{err} } senderInfo, ok := m.pl.PlayerInfos[uuid.UUID(sender)] if !ok { - return InvalidChatPacket + return InvalidChatPacket{ErrUnknownPlayer} } ct := m.c.Registries.ChatType.FindByID(chatType.ID) if ct == nil { - return InvalidChatPacket + return InvalidChatPacket{ErrUnknwonChatType} } var message sign.Message @@ -115,7 +115,7 @@ func (m *Manager) handlePlayerChat(packet pk.Packet) error { var validated bool if senderInfo.ChatSession != nil { if !senderInfo.ChatSession.VerifyAndUpdate(&message) { - return ValidationFailed + return ErrValidationFailed } validated = true // store signature into signatureCache @@ -143,7 +143,7 @@ func (m *Manager) handleDisguisedChat(packet pk.Packet) error { ct := m.c.Registries.ChatType.FindByID(chatType.ID) if ct == nil { - return InvalidChatPacket + return InvalidChatPacket{ErrUnknwonChatType} } msg := chatType.Decorate(message, &ct.Chat) @@ -199,7 +199,23 @@ func (m *Manager) SendCommand(command string) error { return err } +type InvalidChatPacket struct { + err error +} + +func (i InvalidChatPacket) Error() string { + if i.err == nil { + return "invalid chat packet" + } + return "invalid chat packet: " + i.err.Error() +} + +func (i InvalidChatPacket) Unwrap() error { + return i.err +} + var ( - InvalidChatPacket = errors.New("invalid chat packet") - ValidationFailed error = bot.DisconnectErr(chat.TranslateMsg("multiplayer.disconnect.chat_validation_failed")) + ErrUnknownPlayer = errors.New("unknown player") + ErrUnknwonChatType = errors.New("unknown chat type") + ErrValidationFailed error = bot.DisconnectErr(chat.TranslateMsg("multiplayer.disconnect.chat_validation_failed")) ) diff --git a/bot/world/chunks.go b/bot/world/chunks.go index 6b44a07..ab36252 100644 --- a/bot/world/chunks.go +++ b/bot/world/chunks.go @@ -41,7 +41,7 @@ func (w *World) onPlayerSpawn(pk.Packet) error { func (w *World) handleLevelChunkWithLightPacket(packet pk.Packet) error { var pos level.ChunkPos - _, currentDimType := w.c.ConfigData.Registries.DimensionType.Find(w.p.DimensionType) + _, currentDimType := w.c.Registries.DimensionType.Find(w.p.DimensionType) if currentDimType == nil { return errors.New("dimension type " + w.p.DimensionType + " not found") } diff --git a/nbt/rawmsg.go b/nbt/rawmsg.go index 1235013..d8947b3 100644 --- a/nbt/rawmsg.go +++ b/nbt/rawmsg.go @@ -73,3 +73,13 @@ func (m RawMessage) Unmarshal(v any) error { } return d.unmarshal(val, m.Type) } + +func (m RawMessage) UnmarshalDisallowUnknownField(v any) error { + d := NewDecoder(bytes.NewReader(m.Data)) + d.DisallowUnknownFields() + val := reflect.ValueOf(v) + if val.Kind() != reflect.Ptr { + return errors.New("nbt: non-pointer passed to UnmarshalNBT") + } + return d.unmarshal(val, m.Type) +} diff --git a/registry/codec.go b/registry/codec.go index f1b880b..30a1282 100644 --- a/registry/codec.go +++ b/registry/codec.go @@ -1,17 +1,26 @@ package registry import ( + "errors" + "fmt" + "reflect" + "github.com/Tnze/go-mc/chat" "github.com/Tnze/go-mc/nbt" ) type NetworkCodec struct { - ChatType Registry[ChatType] `nbt:"minecraft:chat_type"` - DamageType Registry[DamageType] `nbt:"minecraft:damage_type"` - DimensionType Registry[Dimension] `nbt:"minecraft:dimension_type"` - TrimMaterial Registry[nbt.RawMessage] `nbt:"minecraft:trim_material"` - TrimPattern Registry[nbt.RawMessage] `nbt:"minecraft:trim_pattern"` - WorldGenBiome Registry[nbt.RawMessage] `nbt:"minecraft:worldgen/biome"` + ChatType Registry[ChatType] `registry:"minecraft:chat_type"` + DamageType Registry[DamageType] `registry:"minecraft:damage_type"` + DimensionType Registry[Dimension] `registry:"minecraft:dimension_type"` + TrimMaterial Registry[nbt.RawMessage] `registry:"minecraft:trim_material"` + TrimPattern Registry[nbt.RawMessage] `registry:"minecraft:trim_pattern"` + WorldGenBiome Registry[nbt.RawMessage] `registry:"minecraft:worldgen/biome"` + Wolfvariant Registry[nbt.RawMessage] `registry:"minecraft:wolf_variant"` + PaintingVariant Registry[nbt.RawMessage] `registry:"minecraft:painting_variant"` + BannerPattern Registry[nbt.RawMessage] `registry:"minecraft:banner_pattern"` + Enchantment Registry[nbt.RawMessage] `registry:"minecraft:enchantment"` + JukeboxSong Registry[nbt.RawMessage] `registry:"minecraft:jukebox_song"` } type ChatType struct { @@ -48,3 +57,42 @@ type Dimension struct { MonsterSpawnLightLevel nbt.RawMessage `nbt:"monster_spawn_light_level"` // Tag_Int or {type:"minecraft:uniform", value:{min_inclusive: Tag_Int, max_inclusive: Tag_Int}} MonsterSpawnBlockLightLimit int32 `nbt:"monster_spawn_block_light_limit"` } + +// InsertNBTDataIntoRegistry insert data (entry, data) into the registry. +// The codec should be a pointer of a struct. And the registry should be a field of the codec struct. +// +// This function is a temporary solution while the registry system isn't implemented well. +func InsertNBTDataIntoRegistry(codec any, registry, entry string, data nbt.RawMessage) error { + codecVal := reflect.ValueOf(codec) + if codecVal.Kind() != reflect.Pointer { + return errors.New("codec is not a pointer") + } + + codecVal = codecVal.Elem() + if codecVal.Kind() != reflect.Struct { + return errors.New("codec is not a pointer of struct") + } + + codecTyp := codecVal.Type() + + numField := codecVal.NumField() + for i := 0; i < numField; i++ { + registryID, ok := codecTyp.Field(i).Tag.Lookup("registry") + if !ok { + continue + } + if registryID == registry { + fieldVal := codecVal.Field(i).Addr() + args := []reflect.Value{reflect.ValueOf(entry), reflect.ValueOf(data)} + err := fieldVal.MethodByName("InsertNBT").Call(args)[0] + if !err.IsNil() { + return err.Interface().(error) + } + if registry == "minecraft:chat_type" { + fmt.Println(fieldVal.Interface()) + } + return nil + } + } + return errors.New("registry " + registry + " not found in the codec") +} diff --git a/registry/registry.go b/registry/registry.go index 5683490..065d53e 100644 --- a/registry/registry.go +++ b/registry/registry.go @@ -1,12 +1,16 @@ package registry +import "github.com/Tnze/go-mc/nbt" + type Registry[E any] struct { - Type string `nbt:"type"` - Value []struct { - Name string `nbt:"name"` - ID int32 `nbt:"id"` - Element E `nbt:"element"` - } `nbt:"value"` + Type string `nbt:"type"` + Value []Entry[E] `nbt:"value"` +} + +type Entry[E any] struct { + Name string `nbt:"name"` + ID int32 `nbt:"id"` + Element E `nbt:"element"` } func (r *Registry[E]) Find(name string) (int32, *E) { @@ -29,3 +33,18 @@ func (r *Registry[E]) FindByID(id int32) *E { } return nil } + +func (r *Registry[E]) Insert(name string, data E) { + r.Value = append(r.Value, Entry[E]{Name: name, Element: data}) +} + +func (r *Registry[E]) InsertNBT(name string, data nbt.RawMessage) error { + entry := Entry[E]{Name: name, ID: int32(len(r.Value))} + if data.Type != 0 { + if err := data.UnmarshalDisallowUnknownField(&entry.Element); err != nil { + return err + } + } + r.Value = append(r.Value, entry) + return nil +}