diff --git a/bot/basic/info.go b/bot/basic/info.go index 8f4afb0..d6347b7 100644 --- a/bot/basic/info.go +++ b/bot/basic/info.go @@ -3,16 +3,14 @@ package basic import ( "unsafe" - "github.com/Tnze/go-mc/chat" - "github.com/Tnze/go-mc/data/packetid" - "github.com/Tnze/go-mc/nbt" pk "github.com/Tnze/go-mc/net/packet" + "github.com/Tnze/go-mc/registry" ) // WorldInfo content player info in server. type WorldInfo struct { - RegistryCodec RegistryCodec + RegistryCodec registry.NetworkCodec DimensionType string DimensionNames []string // Identifiers for all worlds on the server. DimensionName string // Name of the world being spawned into. @@ -26,70 +24,6 @@ type WorldInfo struct { IsFlat bool // True if the world is a superflat world; flat worlds have different void fog and a horizon at y=0 instead of y=63. } -type Dimension struct { - FixedTime int64 `nbt:"fixed_time,omitempty"` - HasSkylight bool `nbt:"has_skylight"` - HasCeiling bool `nbt:"has_ceiling"` - Ultrawarm bool `nbt:"ultrawarm"` - Natural bool `nbt:"natural"` - CoordinateScale float64 `nbt:"coordinate_scale"` - BedWorks bool `nbt:"bed_works"` - RespawnAnchorWorks byte `nbt:"respawn_anchor_works"` - MinY int32 `nbt:"min_y"` - Height int32 `nbt:"height"` - LogicalHeight int32 `nbt:"logical_height"` - InfiniteBurn string `nbt:"infiniburn"` - Effects string `nbt:"effects"` - AmbientLight float64 `nbt:"ambient_light"` - - PiglinSafe byte `nbt:"piglin_safe"` - HasRaids byte `nbt:"has_raids"` - 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"` -} - -type ChatType struct { - Chat chat.Decoration `nbt:"chat"` - Narration chat.Decoration `nbt:"narration"` -} - -type RegistryCodec struct { - // What is Below? (wiki.vg) - ChatType Registry[ChatType] `nbt:"minecraft:chat_type"` - DimensionType Registry[Dimension] `nbt:"minecraft:dimension_type"` - WorldGenBiome Registry[nbt.RawMessage] `nbt:"minecraft:worldgen/biome"` -} - -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"` -} - -func (r *Registry[E]) Find(name string) *E { - for i := range r.Value { - if r.Value[i].Name == name { - return &r.Value[i].Element - } - } - return nil -} - -func (r *Registry[E]) FindByID(id int32) *E { - if id >= 0 && id < int32(len(r.Value)) && r.Value[id].ID == id { - return &r.Value[id].Element - } - for i := range r.Value { - if r.Value[i].ID == id { - return &r.Value[i].Element - } - } - return nil -} - type PlayerInfo struct { EID int32 // The player's Entity ID (EID). Hardcore bool // Is hardcore diff --git a/bot/msg/chat.go b/bot/msg/chat.go index 9a98f04..9a1923a 100644 --- a/bot/msg/chat.go +++ b/bot/msg/chat.go @@ -1,16 +1,15 @@ package msg import ( + "encoding/hex" "fmt" - "io" - "time" "github.com/Tnze/go-mc/bot" "github.com/Tnze/go-mc/bot/basic" "github.com/Tnze/go-mc/chat" + "github.com/Tnze/go-mc/chat/sign" "github.com/Tnze/go-mc/data/packetid" pk "github.com/Tnze/go-mc/net/packet" - "github.com/google/uuid" ) type Manager struct { @@ -24,149 +23,43 @@ func New(c *bot.Client, p *basic.Player, events EventsHandler) *Manager { } func attachPlayerMsg(c *bot.Client, p *basic.Player, handler func(msg chat.Message) error) { - c.Events.AddListener(bot.PacketHandler{ - Priority: 64, ID: packetid.ClientboundPlayerChat, - F: func(packet pk.Packet) error { - var message PlayerMessage - if err := packet.Scan(&message); err != nil { - return err - } - - var content chat.Message - if message.content.formatted != nil { - content = *message.content.formatted - } else { - content = chat.Text(message.content.plainMsg) - } - - ct := p.WorldInfo.RegistryCodec.ChatType.FindByID(message.chatType.ID) - if ct == nil { - return fmt.Errorf("chat type %d not found", message.chatType.ID) - } - - msg := (*chat.Type)(&message.chatType).Decorate(content, &ct.Chat) - return handler(msg) + c.Events.AddListener( + bot.PacketHandler{ + Priority: 64, ID: packetid.ClientboundPlayerChatHeader, + F: func(packet pk.Packet) error { + fmt.Println(packetid.ClientboundPacketID(packet.ID)) + fmt.Println(hex.Dump(packet.Data)) + return nil + }, }, - }) -} + bot.PacketHandler{ + Priority: 64, ID: packetid.ClientboundPlayerChat, + F: func(packet pk.Packet) error { + var message sign.PlayerMessage + var chatType chat.Type + fmt.Println(packetid.ClientboundPacketID(packet.ID)) + fmt.Println(hex.Dump(packet.Data)) + if err := packet.Scan(&message, &chatType); err != nil { + return err + } -type PlayerMessage struct { - // SignedMessageHeader - signature []byte - sender uuid.UUID - // MessageSignature - msgSignature []byte - // SignedMessageBody - content msgContent - timestamp time.Time - salt int64 - prevMessages []prevMsg - // Optional - unsignedContent *chat.Message - // FilterMask - filterType int32 - filterSet pk.BitSet - // ChatType - chatType chatType -} + var content chat.Message + if message.MessageBody.DecoratedMsg != nil { + data, _ := message.MessageBody.DecoratedMsg.MarshalJSON() + if err := content.UnmarshalJSON(data); err != nil { + return err + } + } else { + content = chat.Text(message.MessageBody.PlainMsg) + } -func (p *PlayerMessage) String() string { - return p.content.plainMsg -} + ct := p.WorldInfo.RegistryCodec.ChatType.FindByID(chatType.ID) + if ct == nil { + return fmt.Errorf("chat type %d not found", chatType.ID) + } -func (p *PlayerMessage) ReadFrom(r io.Reader) (n int64, err error) { - var hasMsgSign, hasUnsignedContent pk.Boolean - var timestamp pk.Long - var unsignedContent chat.Message - n, err = pk.Tuple{ - &hasMsgSign, - pk.Opt{ - Has: &hasMsgSign, - Field: (*pk.ByteArray)(&p.signature), - }, - (*pk.UUID)(&p.sender), - (*pk.ByteArray)(&p.msgSignature), - &p.content, - ×tamp, - (*pk.Long)(&p.salt), - pk.Array(&p.prevMessages), - &hasUnsignedContent, - pk.Opt{ - Has: &hasUnsignedContent, - Field: &unsignedContent, - }, - (*pk.VarInt)(&p.filterType), - pk.Opt{ - Has: func() bool { return p.filterType == 2 }, - Field: &p.filterSet, - }, - &p.chatType, - }.ReadFrom(r) - if err != nil { - return - } - p.timestamp = time.UnixMilli(int64(timestamp)) - if hasUnsignedContent { - p.unsignedContent = &unsignedContent - } - return n, err -} - -type msgContent struct { - plainMsg string - formatted *chat.Message -} - -func (m *msgContent) ReadFrom(r io.Reader) (n int64, err error) { - var hasFormatted pk.Boolean - n1, err := (*pk.String)(&m.plainMsg).ReadFrom(r) - if err != nil { - return n1, err - } - n2, err := hasFormatted.ReadFrom(r) - if err != nil { - return n1 + n2, err - } - if hasFormatted { - m.formatted = new(chat.Message) - n3, err := m.formatted.ReadFrom(r) - return n1 + n2 + n3, err - } - return n1 + n2, err -} - -type prevMsg struct { - sender uuid.UUID - signature []byte -} - -func (p *prevMsg) ReadFrom(r io.Reader) (n int64, err error) { - return pk.Tuple{ - (*pk.UUID)(&p.sender), - (*pk.ByteArray)(&p.signature), - }.ReadFrom(r) -} - -type chatType chat.Type - -func (c *chatType) ReadFrom(r io.Reader) (n int64, err error) { - var hasTargetName pk.Boolean - n1, err := (*pk.VarInt)(&c.ID).ReadFrom(r) - if err != nil { - return n1, err - } - n2, err := c.SenderName.ReadFrom(r) - if err != nil { - return n1 + n2, err - } - n3, err := hasTargetName.ReadFrom(r) - if err != nil { - return n1 + n2 + n3, err - } - if hasTargetName { - c.TargetName = new(chat.Message) - n4, err := c.TargetName.ReadFrom(r) - return n1 + n2 + n3 + n4, err - } - return n1 + n2 + n3, nil + msg := chatType.Decorate(content, &ct.Chat) + return handler(msg) + }, + }) } diff --git a/bot/world/chunks.go b/bot/world/chunks.go index 0bd1420..8e4ff1a 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.p.WorldInfo.RegistryCodec.DimensionType.Find(w.p.DimensionType) + _, currentDimType := w.p.WorldInfo.RegistryCodec.DimensionType.Find(w.p.DimensionType) if currentDimType == nil { return errors.New("dimension type " + w.p.DimensionType + " not found") } diff --git a/chat/decoration.go b/chat/decoration.go index 3f84eb3..8f2a436 100644 --- a/chat/decoration.go +++ b/chat/decoration.go @@ -1,18 +1,24 @@ package chat +import ( + "io" + + pk "github.com/Tnze/go-mc/net/packet" +) + type Decoration struct { TranslationKey string `nbt:"translation_key"` Parameters []string `nbt:"parameters"` Style struct { - Bold bool `nbt:"bold"` - Italic bool `nbt:"italic"` - UnderLined bool `nbt:"underlined"` - StrikeThrough bool `nbt:"strikethrough"` - Obfuscated bool `nbt:"obfuscated"` - Color string `nbt:"color"` - Insertion string `nbt:"insertion"` - Font string `nbt:"font"` - } `nbt:"style"` + Bold bool `nbt:"bold,omitempty"` + Italic bool `nbt:"italic,omitempty"` + UnderLined bool `nbt:"underlined,omitempty"` + StrikeThrough bool `nbt:"strikethrough,omitempty"` + Obfuscated bool `nbt:"obfuscated,omitempty"` + Color string `nbt:"color,omitempty"` + Insertion string `nbt:"insertion,omitempty"` + Font string `nbt:"font,omitempty"` + } `nbt:"style,omitempty"` } type Type struct { @@ -49,3 +55,46 @@ func (t *Type) Decorate(content Message, d *Decoration) (msg Message) { Insertion: d.Style.Insertion, } } + +func (t *Type) ReadFrom(r io.Reader) (n int64, err error) { + var hasTargetName pk.Boolean + n1, err := (*pk.VarInt)(&t.ID).ReadFrom(r) + if err != nil { + return n1, err + } + n2, err := t.SenderName.ReadFrom(r) + if err != nil { + return n1 + n2, err + } + n3, err := hasTargetName.ReadFrom(r) + if err != nil { + return n1 + n2 + n3, err + } + if hasTargetName { + t.TargetName = new(Message) + n4, err := t.TargetName.ReadFrom(r) + return n1 + n2 + n3 + n4, err + } + return n1 + n2 + n3, nil +} + +func (t *Type) WriteTo(w io.Writer) (n int64, err error) { + hasTargetName := pk.Boolean(t.TargetName != nil) + n1, err := (*pk.VarInt)(&t.ID).WriteTo(w) + if err != nil { + return n1, err + } + n2, err := t.SenderName.WriteTo(w) + if err != nil { + return n1 + n2, err + } + n3, err := hasTargetName.WriteTo(w) + if err != nil { + return n1 + n2 + n3, err + } + if hasTargetName { + n4, err := t.TargetName.WriteTo(w) + return n1 + n2 + n3 + n4, err + } + return n1 + n2 + n3, nil +} diff --git a/chat/message.go b/chat/message.go index 66d8ae0..2195900 100644 --- a/chat/message.go +++ b/chat/message.go @@ -132,13 +132,13 @@ func (m *Message) UnmarshalJSON(raw []byte) (err error) { // ReadFrom decode Message in a ChatMsg packet func (m *Message) ReadFrom(r io.Reader) (int64, error) { - var Len pk.VarInt - if n, err := Len.ReadFrom(r); err != nil { + var code pk.String + n, err := code.ReadFrom(r) + if err != nil { return n, err } - lr := &io.LimitedReader{R: r, N: int64(Len)} - err := json.NewDecoder(lr).Decode(m) - return int64(Len) - lr.N, err + err = json.Unmarshal([]byte(code), m) + return n, err } // WriteTo encode Message into a ChatMsg packet diff --git a/chat/sign/sign.go b/chat/sign/sign.go new file mode 100644 index 0000000..d7b15b4 --- /dev/null +++ b/chat/sign/sign.go @@ -0,0 +1,219 @@ +package sign + +import ( + "crypto/sha256" + "encoding/binary" + "encoding/json" + "io" + "time" + + "github.com/Tnze/go-mc/chat" + pk "github.com/Tnze/go-mc/net/packet" + "github.com/google/uuid" +) + +type MessageHeader struct { + PrevSignature []byte + Sender uuid.UUID +} + +func (m *MessageHeader) WriteTo(w io.Writer) (n int64, err error) { + hasSignature := pk.Boolean(len(m.PrevSignature) > 0) + n, err = hasSignature.WriteTo(w) + if err != nil { + return + } + if hasSignature { + n2, err := pk.ByteArray(m.PrevSignature).WriteTo(w) + n += n2 + if err != nil { + return n, err + } + } + n3, err := pk.UUID(m.Sender).WriteTo(w) + return n + n3, err +} + +func (m *MessageHeader) ReadFrom(r io.Reader) (n int64, err error) { + var hasSignature pk.Boolean + n, err = hasSignature.ReadFrom(r) + if err != nil { + return + } + if hasSignature { + n2, err := (*pk.ByteArray)(&m.PrevSignature).ReadFrom(r) + n += n2 + if err != nil { + return n, err + } + } + n3, err := (*pk.UUID)(&m.Sender).ReadFrom(r) + return n + n3, err +} + +func (m *MessageHeader) Hash(bodyHash []byte) []byte { + hash := sha256.New() + if m.PrevSignature != nil { + hash.Write(m.PrevSignature) + } + hash.Write(m.Sender[:]) + hash.Write(bodyHash) + return hash.Sum(nil) +} + +type MessageBody struct { + PlainMsg string + DecoratedMsg json.RawMessage + Timestamp time.Time + Salt int64 + History []HistoryMessage +} + +func (m *MessageBody) WriteTo(w io.Writer) (n int64, err error) { + return pk.Tuple{ + pk.String(m.PlainMsg), + pk.Boolean(m.DecoratedMsg != nil), + pk.Opt{ + Has: m.DecoratedMsg != nil, + Field: pk.ByteArray(m.DecoratedMsg), + }, + pk.Long(m.Timestamp.UnixMilli()), + pk.Long(m.Salt), + pk.Array(&m.History), + }.WriteTo(w) +} + +func (m *MessageBody) ReadFrom(r io.Reader) (n int64, err error) { + var hasContent pk.Boolean + var timestamp pk.Long + n, err = pk.Tuple{ + (*pk.String)(&m.PlainMsg), + &hasContent, + pk.Opt{ + Has: &hasContent, + Field: (*pk.ByteArray)(&m.DecoratedMsg), + }, + ×tamp, + (*pk.Long)(&m.Salt), + pk.Array(&m.History), + }.ReadFrom(r) + m.Timestamp = time.UnixMilli(int64(timestamp)) + return +} + +func (m *MessageBody) Hash() []byte { + hash := sha256.New() + _ = binary.Write(hash, binary.BigEndian, m.Salt) + _ = binary.Write(hash, binary.BigEndian, m.Timestamp.Unix()) + hash.Write([]byte(m.PlainMsg)) + hash.Write([]byte{70}) + if m.DecoratedMsg != nil { + decorated, _ := m.DecoratedMsg.MarshalJSON() + hash.Write(decorated) + } + for _, v := range m.History { + hash.Write([]byte{70}) + hash.Write(v.Sender[:]) + hash.Write(v.Signature) + } + return hash.Sum(nil) +} + +type FilterMask struct { + Type byte + Mask pk.BitSet +} + +func (f *FilterMask) WriteTo(w io.Writer) (n int64, err error) { + n, err = pk.VarInt(f.Type).WriteTo(w) + if err != nil { + return + } + if f.Type == 2 { + var n1 int64 + n1, err = f.Mask.WriteTo(w) + n += n1 + } + return +} + +func (f *FilterMask) ReadFrom(r io.Reader) (n int64, err error) { + var Type pk.VarInt + if n, err = Type.ReadFrom(r); err != nil { + return + } + f.Type = byte(Type) + if f.Type == 2 { + var n1 int64 + n1, err = f.Mask.ReadFrom(r) + n += n1 + } + return +} + +type PlayerMessage struct { + MessageHeader + MessageSignature []byte + MessageBody + UnsignedContent *chat.Message + FilterMask +} + +func (msg *PlayerMessage) ReadFrom(r io.Reader) (n int64, err error) { + var hasUnsignedContent pk.Boolean + return pk.Tuple{ + &msg.MessageHeader, + (*pk.ByteArray)(&msg.MessageSignature), + &msg.MessageBody, + &hasUnsignedContent, + pk.Opt{ + Has: &hasUnsignedContent, + Field: func() pk.FieldDecoder { + msg.UnsignedContent = new(chat.Message) + return msg.UnsignedContent + }, + }, + &msg.FilterMask, + }.ReadFrom(r) +} + +func (msg *PlayerMessage) WriteTo(w io.Writer) (n int64, err error) { + return pk.Tuple{ + &msg.MessageHeader, + pk.ByteArray(msg.MessageSignature), + &msg.MessageBody, + pk.Boolean(msg.UnsignedContent != nil), + pk.Opt{ + Has: msg.UnsignedContent != nil, + Field: msg.UnsignedContent, + }, + &msg.FilterMask, + }.WriteTo(w) +} + +func (msg *PlayerMessage) Hash() []byte { + return msg.MessageHeader.Hash(msg.MessageBody.Hash()) +} + +type HistoryMessage struct { + Sender uuid.UUID + Signature []byte +} + +func (p *HistoryMessage) ReadFrom(r io.Reader) (n int64, err error) { + n, err = (*pk.UUID)(&p.Sender).ReadFrom(r) + if err != nil { + return + } + n2, err := (*pk.ByteArray)(&p.Signature).ReadFrom(r) + return n + n2, err +} + +func (p HistoryMessage) WriteTo(w io.Writer) (n int64, err error) { + n, err = pk.UUID(p.Sender).WriteTo(w) + if err != nil { + return + } + n2, err := pk.ByteArray(p.Signature).WriteTo(w) + return n + n2, err +} diff --git a/chat/sign/sign_test.go b/chat/sign/sign_test.go new file mode 100644 index 0000000..dd7afe1 --- /dev/null +++ b/chat/sign/sign_test.go @@ -0,0 +1,55 @@ +package sign + +import ( + "bytes" + "crypto" + "crypto/rsa" + "encoding/base64" + "math/big" + "testing" + "time" + + "github.com/google/uuid" +) + +func TestMessageBody_Hash(t *testing.T) { + msg := PlayerMessage{ + MessageHeader: MessageHeader{ + PrevSignature: nil, + Sender: uuid.MustParse("58f6356e-b30c-4811-8bfc-d72a9ee99e73"), + }, + MessageSignature: fromBase64("L+gR1hrubxVdhPAe6J+nQCH4U+jsUvJDJE+dzsL8DJwpHfUwT4dgSwm7mI/u8rVxrjPVial1DU0sPZodaGVqcSqApyK38bJThWpmojYtieT63hcsgAZzhGNG0GUrykEzHIMPvAnO0bEBmOqPKWjp/kDxhUgF1kKgmQmiOb1fTazi9dVxGuepVkFXknhp7aZBvQ4oQ94bRY5lTMoWyvNcs3CUpVdeFuWwIVnRAIn+hQQ5rDBvWTgKpFTOuxcCOf6hbtPOO2HZ7TT0rsM1D1LV0R9oHUqlEe4nB0E/vT3GdcplSQTSWc7dDmwTjB+wFeGxrNjFP3FEt3he6a+8Q1svlw=="), + MessageBody: MessageBody{ + PlainMsg: "123", + DecoratedMsg: nil, + Timestamp: time.Unix(1669990398, 750000000), + Salt: -5503869105027681791, + History: nil, + }, + UnsignedContent: nil, + FilterMask: FilterMask{}, + } + hash := msg.MessageBody.Hash() + want := []byte{ + 0x40, 0x2f, 0x63, 0xf1, 0x41, 0x64, 0x83, 0xea, + 0x64, 0xbc, 0xe1, 0xab, 0x4f, 0xa1, 0xdd, 0xcf, + 0x31, 0x6b, 0xdf, 0x9, 0xd3, 0xe3, 0x0, 0xed, + 0xd9, 0x9d, 0x61, 0xb2, 0xfe, 0xe1, 0x23, 0x38, + } + if !bytes.Equal(hash, want) { + t.Errorf("hash not match: want %v, got: %v", want, hash) + } + N := new(big.Int) + N.SetString("19764335557512872060688171398766153113124870942516110890430525316014258628424563821100465499350547856280308462415364939681918846480558609475927282823778386281239015421014618416397562105532153533222767841904849714784545929150813290491806771374240538006775842793512508846836865954304676946257326405255232806869270545112507181469911882584371001804731679283550070980294409246775152428394531383836676761395396452034288295355452943368136472112108083541314359681019016586668426966809565492641015821429276348346645505643438759550398087660394335140584223095644803819139358171070866744685891950091018380254474327892983538632197", 10) + if err := rsa.VerifyPKCS1v15(&rsa.PublicKey{N: N, E: 65537}, crypto.SHA256, msg.Hash(), msg.MessageSignature); err != nil { + t.Error(err) + } +} + +func fromBase64(s string) []byte { + b, err := base64.StdEncoding.DecodeString(s) + if err != nil { + panic(err) + } + return b +} diff --git a/data/packetid/packetid.go b/data/packetid/packetid.go index b5db1ae..f799818 100644 --- a/data/packetid/packetid.go +++ b/data/packetid/packetid.go @@ -145,6 +145,7 @@ const ( ClientboundUpdateMobEffect ClientboundUpdateRecipes ClientboundUpdateTags + ClientboundPacketIDGuard ) // Game Serverbound @@ -200,4 +201,5 @@ const ( ServerboundTeleportToEntity ServerboundUseItemOn ServerboundUseItem + ServerboundPacketIDGuard ) diff --git a/examples/daze/daze.go b/examples/daze/daze.go index f5a2324..4c00557 100644 --- a/examples/daze/daze.go +++ b/examples/daze/daze.go @@ -22,7 +22,7 @@ import ( ) var ( - address = flag.String("address", "127.0.0.1", "The server address") + address = flag.String("address", "127.0.0.1:25565", "The server address") name = flag.String("name", "Daze", "The player's name") playerID = flag.String("uuid", "", "The player's UUID") accessToken = flag.String("token", "", "AccessToken") diff --git a/nbt/snbt_scanner.go b/nbt/snbt_scanner.go index 4ddafa2..392b34e 100644 --- a/nbt/snbt_scanner.go +++ b/nbt/snbt_scanner.go @@ -83,7 +83,7 @@ func (s *scanner) eof() int { return scanEnd } if s.errContext == "" { - s.errContext = "unexpected end of JSON input" + s.errContext = "unexpected end of SNBT input" } return scanError } diff --git a/net/packet/packet.go b/net/packet/packet.go index b5d86d4..1a3f7e5 100644 --- a/net/packet/packet.go +++ b/net/packet/packet.go @@ -28,10 +28,10 @@ func Marshal[ID ~int32 | int](id ID, fields ...FieldEncoder) (pk Packet) { // Scan decode the packet and fill data into fields func (p Packet) Scan(fields ...FieldDecoder) error { r := bytes.NewReader(p.Data) - for _, v := range fields { + for i, v := range fields { _, err := v.ReadFrom(r) if err != nil { - return err + return fmt.Errorf("scanning packet field[%d] error: %w", i, err) } } return nil diff --git a/net/packet/types.go b/net/packet/types.go index 56d3c30..4d522f1 100644 --- a/net/packet/types.go +++ b/net/packet/types.go @@ -95,13 +95,13 @@ func (b Boolean) WriteTo(w io.Writer) (int64, error) { } func (b *Boolean) ReadFrom(r io.Reader) (n int64, err error) { - v, err := readByte(r) + n, v, err := readByte(r) if err != nil { - return 1, err + return n, err } *b = v != 0 - return 1, nil + return n, nil } func (s String) WriteTo(w io.Writer) (int64, error) { @@ -134,13 +134,14 @@ func (s *String) ReadFrom(r io.Reader) (n int64, err error) { } // readByte read one byte from io.Reader -func readByte(r io.Reader) (byte, error) { +func readByte(r io.Reader) (int64, byte, error) { if r, ok := r.(io.ByteReader); ok { - return r.ReadByte() + v, err := r.ReadByte() + return 1, v, err } var v [1]byte - _, err := io.ReadFull(r, v[:]) - return v[0], err + n, err := r.Read(v[:]) + return int64(n), v[0], err } func (b Byte) WriteTo(w io.Writer) (n int64, err error) { @@ -149,12 +150,12 @@ func (b Byte) WriteTo(w io.Writer) (n int64, err error) { } func (b *Byte) ReadFrom(r io.Reader) (n int64, err error) { - v, err := readByte(r) + n, v, err := readByte(r) if err != nil { - return 0, err + return n, err } *b = Byte(v) - return 1, nil + return n, nil } func (u UnsignedByte) WriteTo(w io.Writer) (n int64, err error) { @@ -163,12 +164,12 @@ func (u UnsignedByte) WriteTo(w io.Writer) (n int64, err error) { } func (u *UnsignedByte) ReadFrom(r io.Reader) (n int64, err error) { - v, err := readByte(r) + n, v, err := readByte(r) if err != nil { - return 0, err + return n, err } *u = UnsignedByte(v) - return 1, nil + return n, nil } func (s Short) WriteTo(w io.Writer) (int64, error) { @@ -267,17 +268,19 @@ func (v VarInt) WriteTo(w io.Writer) (n int64, err error) { func (v *VarInt) ReadFrom(r io.Reader) (n int64, err error) { var V uint32 - for sec := byte(0x80); sec&0x80 != 0; n++ { - if n > MaxVarIntLen { + var num, n2 int64 + for sec := byte(0x80); sec&0x80 != 0; num++ { + if num > MaxVarIntLen { return n, errors.New("VarInt is too big") } - sec, err = readByte(r) + n2, sec, err = readByte(r) + n += n2 if err != nil { return n, err } - V |= uint32(sec&0x7F) << uint32(7*n) + V |= uint32(sec&0x7F) << uint32(7*num) } *v = VarInt(V) @@ -304,16 +307,18 @@ func (v VarLong) WriteTo(w io.Writer) (n int64, err error) { func (v *VarLong) ReadFrom(r io.Reader) (n int64, err error) { var V uint64 - for sec := byte(0x80); sec&0x80 != 0; n++ { - if n >= MaxVarLongLen { + var num, n2 int64 + for sec := byte(0x80); sec&0x80 != 0; num++ { + if num >= MaxVarLongLen { return n, errors.New("VarLong is too big") } - sec, err = readByte(r) + n2, sec, err = readByte(r) + n += n2 if err != nil { return } - V |= uint64(sec&0x7F) << uint64(7*n) + V |= uint64(sec&0x7F) << uint64(7*num) } *v = VarLong(V) @@ -455,11 +460,13 @@ func (b *ByteArray) ReadFrom(r io.Reader) (n int64, err error) { if err != nil { return n1, err } - buf := bytes.NewBuffer(*b) - buf.Reset() - n2, err := io.CopyN(buf, r, int64(Len)) - *b = buf.Bytes() - return n1 + n2, err + if cap(*b) < int(Len) { + *b = make(ByteArray, Len) + } else { + *b = (*b)[:Len] + } + n2, err := io.ReadFull(r, *b) + return n1 + int64(n2), err } func (u UUID) WriteTo(w io.Writer) (n int64, err error) { diff --git a/net/packet/util.go b/net/packet/util.go index 952f0ee..2ef0d17 100644 --- a/net/packet/util.go +++ b/net/packet/util.go @@ -81,8 +81,8 @@ func Array(ary any) Field { } type Opt struct { - Has interface{} // Pointer of bool, or `func() bool` - Field interface{} // FieldEncoder, FieldDecoder or both (Field) + Has any // Pointer of bool, or `func() bool` + Field any // FieldEncoder, FieldDecoder, `func() FieldEncoder`, `func() FieldDecoder` or `func() Field` } func (o Opt) has() bool { @@ -103,14 +103,32 @@ func (o Opt) has() bool { func (o Opt) WriteTo(w io.Writer) (int64, error) { if o.has() { - return o.Field.(FieldEncoder).WriteTo(w) + switch field := o.Field.(type) { + case FieldEncoder: + return field.WriteTo(w) + case func() FieldEncoder: + return field().WriteTo(w) + case func() Field: + return field().WriteTo(w) + default: + panic("unsupported Field type: " + reflect.TypeOf(o.Field).String()) + } } return 0, nil } func (o Opt) ReadFrom(r io.Reader) (int64, error) { if o.has() { - return o.Field.(FieldDecoder).ReadFrom(r) + switch field := o.Field.(type) { + case FieldDecoder: + return field.ReadFrom(r) + case func() FieldDecoder: + return field().ReadFrom(r) + case func() Field: + return field().ReadFrom(r) + default: + panic("unsupported Field type: " + reflect.TypeOf(o.Field).String()) + } } return 0, nil } diff --git a/registry/chattype.go b/registry/chattype.go new file mode 100644 index 0000000..765eb26 --- /dev/null +++ b/registry/chattype.go @@ -0,0 +1,8 @@ +package registry + +import "github.com/Tnze/go-mc/chat" + +type ChatType struct { + Chat chat.Decoration `nbt:"chat"` + Narration chat.Decoration `nbt:"narration"` +} diff --git a/registry/codec.go b/registry/codec.go new file mode 100644 index 0000000..afb90cb --- /dev/null +++ b/registry/codec.go @@ -0,0 +1,9 @@ +package registry + +import "github.com/Tnze/go-mc/nbt" + +type NetworkCodec struct { + ChatType Registry[ChatType] `nbt:"minecraft:chat_type"` + DimensionType Registry[Dimension] `nbt:"minecraft:dimension_type"` + WorldGenBiome Registry[nbt.RawMessage] `nbt:"minecraft:worldgen/biome"` +} diff --git a/registry/dimension.go b/registry/dimension.go new file mode 100644 index 0000000..9a8f653 --- /dev/null +++ b/registry/dimension.go @@ -0,0 +1,25 @@ +package registry + +import "github.com/Tnze/go-mc/nbt" + +type Dimension struct { + FixedTime int64 `nbt:"fixed_time,omitempty"` + HasSkylight bool `nbt:"has_skylight"` + HasCeiling bool `nbt:"has_ceiling"` + Ultrawarm bool `nbt:"ultrawarm"` + Natural bool `nbt:"natural"` + CoordinateScale float64 `nbt:"coordinate_scale"` + BedWorks bool `nbt:"bed_works"` + RespawnAnchorWorks byte `nbt:"respawn_anchor_works"` + MinY int32 `nbt:"min_y"` + Height int32 `nbt:"height"` + LogicalHeight int32 `nbt:"logical_height"` + InfiniteBurn string `nbt:"infiniburn"` + Effects string `nbt:"effects"` + AmbientLight float64 `nbt:"ambient_light"` + + PiglinSafe byte `nbt:"piglin_safe"` + HasRaids byte `nbt:"has_raids"` + 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"` +} diff --git a/registry/registry.go b/registry/registry.go new file mode 100644 index 0000000..5683490 --- /dev/null +++ b/registry/registry.go @@ -0,0 +1,31 @@ +package registry + +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"` +} + +func (r *Registry[E]) Find(name string) (int32, *E) { + for i := range r.Value { + if r.Value[i].Name == name { + return int32(i), &r.Value[i].Element + } + } + return -1, nil +} + +func (r *Registry[E]) FindByID(id int32) *E { + if id >= 0 && id < int32(len(r.Value)) && r.Value[id].ID == id { + return &r.Value[id].Element + } + for i := range r.Value { + if r.Value[i].ID == id { + return &r.Value[i].Element + } + } + return nil +} diff --git a/server/auth/pubkey.go b/server/auth/pubkey.go index 72836d7..0001b8c 100644 --- a/server/auth/pubkey.go +++ b/server/auth/pubkey.go @@ -1,6 +1,7 @@ package auth import ( + "crypto" "crypto/rsa" "crypto/x509" "errors" @@ -67,3 +68,7 @@ func (p *PublicKey) Verify() bool { } return VerifySignature(encoded, p.Signature) } + +func (p *PublicKey) VerifyMessage(hash, signature []byte) error { + return rsa.VerifyPKCS1v15(p.PubKey, crypto.SHA256, hash, signature) +} diff --git a/server/login.go b/server/login.go index db4a9bb..a256a62 100644 --- a/server/login.go +++ b/server/login.go @@ -84,19 +84,19 @@ func (d *MojangLoginHandler) AcceptLogin(conn *net.Conn, protocol int32) (name s return } - if hasPubKey { - if !pubKey.Verify() { - err = LoginFailErr{reason: chat.TranslateMsg("multiplayer.disconnect.invalid_public_key_signature")} - return - } - profilePubKey = &pubKey - } else if d.EnforceSecureProfile { - err = LoginFailErr{reason: chat.TranslateMsg("multiplayer.disconnect.missing_public_key")} - return - } - // auth if d.OnlineMode { + if hasPubKey { + if !pubKey.Verify() { + err = LoginFailErr{reason: chat.TranslateMsg("multiplayer.disconnect.invalid_public_key_signature")} + return + } + profilePubKey = &pubKey + } else if d.EnforceSecureProfile { + err = LoginFailErr{reason: chat.TranslateMsg("multiplayer.disconnect.missing_public_key")} + return + } + var resp *auth.Resp // Auth, Encrypt resp, err = auth.Encrypt(conn, name, pubKey.PubKey)