better nbt error message & dimensioncodec decoder

This commit is contained in:
Tnze
2022-06-19 23:05:10 +08:00
parent 30479d6ea5
commit d94993f34f
6 changed files with 67 additions and 142 deletions

View File

@ -4,15 +4,16 @@ import (
"unsafe" "unsafe"
"github.com/Tnze/go-mc/data/packetid" "github.com/Tnze/go-mc/data/packetid"
"github.com/Tnze/go-mc/nbt"
pk "github.com/Tnze/go-mc/net/packet" pk "github.com/Tnze/go-mc/net/packet"
) )
// WorldInfo content player info in server. // WorldInfo content player info in server.
type WorldInfo struct { type WorldInfo struct {
DimensionCodec DimensionCodec DimensionCodec DimensionCodec
Dimension Dimension DimensionType string
WorldNames []string // Identifiers for all worlds on the server. DimensionNames []string // Identifiers for all worlds on the server.
WorldName string // Name of the world being spawned into. DimensionName string // Name of the world being spawned into.
HashedSeed int64 // First 8 bytes of the SHA-256 hash of the world's seed. Used client side for biome noise HashedSeed int64 // First 8 bytes of the SHA-256 hash of the world's seed. Used client side for biome noise
MaxPlayers int32 // Was once used by the client to draw the player list, but now is ignored. MaxPlayers int32 // Was once used by the client to draw the player list, but now is ignored.
ViewDistance int32 // Render distance (2-32). ViewDistance int32 // Render distance (2-32).
@ -22,112 +23,52 @@ type WorldInfo struct {
IsDebug bool // True if the world is a debug mode world; debug mode worlds cannot be modified and have predefined blocks. IsDebug bool // True if the world is a debug mode world; debug mode worlds cannot be modified and have predefined blocks.
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. 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 { type Dimension struct {
Name string `nbt:"name"` FixedTime int64 `nbt:"fixed_time,omitempty"`
Id int `nbt:"id"` HasSkylight bool `nbt:"has_skylight"`
Element struct { HasCeiling bool `nbt:"has_ceiling"`
PiglinSafe byte `nbt:"piglin_safe"` Ultrawarm bool `nbt:"ultrawarm"`
Natural byte `nbt:"natural"` Natural bool `nbt:"natural"`
AmbientLight float64 `nbt:"ambient_light"` CoordinateScale float64 `nbt:"coordinate_scale"`
MonsterSpawnBlockLightLimit int `nbt:"monster_spawn_block_light_limit"` BedWorks bool `nbt:"bed_works"`
Infiniburn string `nbt:"infiniburn"` RespawnAnchorWorks byte `nbt:"respawn_anchor_works"`
RespawnAnchorWorks byte `nbt:"respawn_anchor_works"` MinY int32 `nbt:"min_y"`
HasSkylight byte `nbt:"has_skylight"` Height int32 `nbt:"height"`
BedWorks byte `nbt:"bed_works"` LogicalHeight int32 `nbt:"logical_height"`
Effects string `nbt:"effects"` InfiniteBurn string `nbt:"infiniburn"`
HasRaids byte `nbt:"has_raids"` Effects string `nbt:"effects"`
Shrunk byte `nbt:"shrunk"` AmbientLight float64 `nbt:"ambient_light"`
LogicalHeight int `nbt:"logical_height"`
CoordinateScale float64 `nbt:"coordinate_scale"` PiglinSafe byte `nbt:"piglin_safe"`
MinY int `nbt:"min_y"` HasRaids byte `nbt:"has_raids"`
MonsterSpawnLightLevel int `nbt:"monster_spawn_light_level"` MonsterSpawnLightLevel nbt.RawMessage `nbt:"monster_spawn_light_level"` // Tag_Int or {type:"minecraft:uniform", value:{min_inclusive: Tag_Int, max_inclusive: Tag_Int}}
Ultrawarm byte `nbt:"ultrawarm"` MonsterSpawnBlockLightLimit int32 `nbt:"monster_spawn_block_light_limit"`
HasCeiling byte `nbt:"has_ceiling"`
Height int `nbt:"height"`
FixedTime int64 `nbt:"fixed_time,omitempty"`
} `nbt:"element"`
} }
type DimensionCodec struct { type DimensionCodec struct {
// What is Below? (wiki.vg) // What is Below? (wiki.vg)
ChatType struct { ChatType Registry[nbt.RawMessage] `nbt:"minecraft:chat_type"`
Type string `nbt:"type"` DimensionType Registry[Dimension] `nbt:"minecraft:dimension_type"`
Value []struct { WorldGenBiome Registry[nbt.RawMessage] `nbt:"minecraft:worldgen/biome"`
Name string `nbt:"name"` }
Id int `nbt:"id"`
Element struct { type Registry[E any] struct {
Chat struct { Type string `nbt:"type"`
Decoration struct { Value []struct {
TranslationKey interface{} `nbt:"translation_key"` Name string `nbt:"name"`
Style struct { ID int32 `nbt:"id"`
Color interface{} `nbt:"color,omitempty"` Element E `nbt:"element"`
Italic interface{} `nbt:"italic,omitempty"` } `nbt:"value"`
} `nbt:"style"` }
Parameters []interface{} `nbt:"parameters"`
} `nbt:"decoration,omitempty"` func (r *Registry[E]) Find(name string) *E {
} `nbt:"chat,omitempty"` for i := range r.Value {
Narration struct { if r.Value[i].Name == name {
Priority interface{} `nbt:"priority"` return &r.Value[i].Element
Decoration struct { }
TranslationKey interface{} `nbt:"translation_key"` }
Style struct { return nil
} `nbt:"style"`
Parameters []interface{} `nbt:"parameters"`
} `nbt:"decoration,omitempty"`
} `nbt:"narration,omitempty"`
Overlay struct {
} `nbt:"overlay,omitempty"`
} `nbt:"element"`
} `nbt:"value"`
} `nbt:"minecraft:chat_type"`
DimensionType struct {
Type string `nbt:"type"`
Value []Dimension `nbt:"value"`
} `nbt:"minecraft:dimension_type"`
WorldGenBiome struct {
Type string `nbt:"type"`
Value []struct {
Name string `nbt:"name"`
Id int `nbt:"id"`
Element struct {
Precipitation interface{} `nbt:"precipitation"`
Effects struct {
SkyColor int `nbt:"sky_color"`
WaterFogColor int `nbt:"water_fog_color"`
FogColor int `nbt:"fog_color"`
WaterColor int `nbt:"water_color"`
MoodSound struct {
TickDelay int `nbt:"tick_delay"`
Offset interface{} `nbt:"offset"`
Sound string `nbt:"sound"`
BlockSearchExtent int `nbt:"block_search_extent"`
} `nbt:"mood_sound"`
GrassColorModifier interface{} `nbt:"grass_color_modifier,omitempty"`
Music struct {
ReplaceCurrentMusic interface{} `nbt:"replace_current_music"`
MaxDelay int `nbt:"max_delay"`
Sound string `nbt:"sound"`
MinDelay int `nbt:"min_delay"`
} `nbt:"music,omitempty"`
FoliageColor int `nbt:"foliage_color,omitempty"`
GrassColor int `nbt:"grass_color,omitempty"`
AmbientSound string `nbt:"ambient_sound,omitempty"`
AdditionsSound struct {
Sound string `nbt:"sound"`
TickChance interface{} `nbt:"tick_chance"`
} `nbt:"additions_sound,omitempty"`
Particle struct {
Probability interface{} `nbt:"probability"`
Options struct {
Type string `nbt:"type"`
} `nbt:"options"`
} `nbt:"particle,omitempty"`
} `nbt:"effects"`
Temperature interface{} `nbt:"temperature"`
Downfall interface{} `nbt:"downfall"`
TemperatureModifier interface{} `nbt:"temperature_modifier,omitempty"`
} `nbt:"element"`
} `nbt:"value"`
} `nbt:"minecraft:worldgen/biome"`
} }
type PlayerInfo struct { type PlayerInfo struct {
@ -143,17 +84,15 @@ type ServInfo struct {
} }
func (p *Player) handleLoginPacket(packet pk.Packet) error { func (p *Player) handleLoginPacket(packet pk.Packet) error {
var WorldNames = make([]pk.Identifier, 0)
var currentDimension pk.Identifier
err := packet.Scan( err := packet.Scan(
(*pk.Int)(&p.EID), (*pk.Int)(&p.EID),
(*pk.Boolean)(&p.Hardcore), (*pk.Boolean)(&p.Hardcore),
(*pk.UnsignedByte)(&p.Gamemode), (*pk.UnsignedByte)(&p.Gamemode),
(*pk.Byte)(&p.PrevGamemode), (*pk.Byte)(&p.PrevGamemode),
pk.Array(&WorldNames), pk.Array((*[]pk.Identifier)(unsafe.Pointer(&p.DimensionNames))),
pk.NBT(&p.WorldInfo.DimensionCodec), pk.NBT(&p.WorldInfo.DimensionCodec),
&currentDimension, (*pk.Identifier)(&p.WorldInfo.DimensionType),
(*pk.Identifier)(&p.WorldName), (*pk.Identifier)(&p.DimensionName),
(*pk.Long)(&p.HashedSeed), (*pk.Long)(&p.HashedSeed),
(*pk.VarInt)(&p.MaxPlayers), (*pk.VarInt)(&p.MaxPlayers),
(*pk.VarInt)(&p.ViewDistance), (*pk.VarInt)(&p.ViewDistance),
@ -166,18 +105,6 @@ func (p *Player) handleLoginPacket(packet pk.Packet) error {
if err != nil { if err != nil {
return Error{err} return Error{err}
} }
// This line should work "like" the following code but without copy things
// p.WorldNames = make([]string, len(WorldNames))
// for i, v := range WorldNames {
// p.WorldNames[i] = string(v)
// }
p.WorldNames = *(*[]string)(unsafe.Pointer(&WorldNames))
for _, d := range p.DimensionCodec.DimensionType.Value {
if d.Name == string(currentDimension) {
p.Dimension = d
}
}
err = p.c.Conn.WritePacket(pk.Marshal( //PluginMessage packet err = p.c.Conn.WritePacket(pk.Marshal( //PluginMessage packet
packetid.ServerboundCustomPayload, packetid.ServerboundCustomPayload,
pk.Identifier("minecraft:brand"), pk.Identifier("minecraft:brand"),
@ -205,10 +132,9 @@ func (p *Player) handleLoginPacket(packet pk.Packet) error {
} }
func (p *Player) handleRespawnPacket(packet pk.Packet) error { func (p *Player) handleRespawnPacket(packet pk.Packet) error {
var copyMeta bool var copyMeta bool
var currentDimension pk.Identifier
err := packet.Scan( err := packet.Scan(
&currentDimension, (*pk.String)(&p.DimensionType),
(*pk.Identifier)(&p.WorldName), (*pk.Identifier)(&p.DimensionName),
(*pk.Long)(&p.HashedSeed), (*pk.Long)(&p.HashedSeed),
(*pk.UnsignedByte)(&p.Gamemode), (*pk.UnsignedByte)(&p.Gamemode),
(*pk.Byte)(&p.PrevGamemode), (*pk.Byte)(&p.PrevGamemode),
@ -219,10 +145,5 @@ func (p *Player) handleRespawnPacket(packet pk.Packet) error {
if err != nil { if err != nil {
return Error{err} return Error{err}
} }
for _, d := range p.DimensionCodec.DimensionType.Value {
if d.Name == string(currentDimension) {
p.Dimension = d
}
}
return nil return nil
} }

View File

@ -39,8 +39,8 @@ func (w *World) onPlayerSpawn(pk.Packet) error {
func (w *World) handleLevelChunkWithLightPacket(packet pk.Packet) error { func (w *World) handleLevelChunkWithLightPacket(packet pk.Packet) error {
var pos level.ChunkPos var pos level.ChunkPos
currentDimType := w.p.WorldInfo.DimensionCodec.DimensionType.Find(w.p.DimensionType)
chunk := level.EmptyChunk(int(w.p.WorldInfo.Dimension.Element.Height / 16)) chunk := level.EmptyChunk(int(currentDimType.Height) / 16)
if err := packet.Scan(&pos, chunk); err != nil { if err := packet.Scan(&pos, chunk); err != nil {
return err return err
} }

View File

@ -39,10 +39,6 @@ func (d *Decoder) Decode(v interface{}) (string, error) {
return tagName, fmt.Errorf("nbt: %w", err) return tagName, fmt.Errorf("nbt: %w", err)
} }
if c := d.checkCompressed(tagType); c != "" {
return tagName, fmt.Errorf("nbt: unknown Tag, maybe compressed by %s, uncompress it first", c)
}
// We decode val not val.Elem because the Unmarshaler interface // We decode val not val.Elem because the Unmarshaler interface
// test must be applied at the top level of the value. // test must be applied at the top level of the value.
err = d.unmarshal(val, tagType) err = d.unmarshal(val, tagType)
@ -78,7 +74,7 @@ func (d *Decoder) unmarshal(val reflect.Value, tagType byte) error {
switch tagType { switch tagType {
default: default:
return fmt.Errorf("unknown Tag 0x%02x", tagType) return fmt.Errorf("unknown Tag %#02x", tagType)
case TagEnd: case TagEnd:
return ErrEND return ErrEND
@ -505,7 +501,7 @@ func (d *Decoder) rawRead(tagType byte) error {
var buf [8]byte var buf [8]byte
switch tagType { switch tagType {
default: default:
return fmt.Errorf("unknown to read 0x%02x", tagType) return fmt.Errorf("unknown to read %#02x", tagType)
case TagByte: case TagByte:
_, err := d.readByte() _, err := d.readByte()
return err return err
@ -590,7 +586,12 @@ func (d *Decoder) readTag() (tagType byte, tagName string, err error) {
return return
} }
if tagType != TagEnd { //Read Tag switch tagType {
case 0x1f, 0x78:
c := d.checkCompressed(tagType)
err = fmt.Errorf("nbt: unknown Tag %#02x, which seems like %s header and you should uncompress it first", tagType, c)
case TagEnd:
default: //Read Tag
tagName, err = d.readString() tagName, err = d.readString()
} }
return return

View File

@ -200,6 +200,9 @@ func (e *Encoder) writeValue(val reflect.Value, tagType byte) error {
if tagProps.OmitEmpty && isEmptyValue(v) { if tagProps.OmitEmpty && isEmptyValue(v) {
continue continue
} }
if tagProps.Type == TagNone {
return fmt.Errorf("encode %q error: unsupport type %v", tagProps.Name, v.Type())
}
if err := e.marshal(val.Field(i), tagProps.Type, tagProps.Name); err != nil { if err := e.marshal(val.Field(i), tagProps.Type, tagProps.Name); err != nil {
return err return err
@ -216,7 +219,7 @@ func (e *Encoder) writeValue(val reflect.Value, tagType byte) error {
} }
tagType, tagValue := getTagType(r.Value()) tagType, tagValue := getTagType(r.Value())
if tagType == TagNone { if tagType == TagNone {
return errors.New("unsupported value " + tagValue.String()) return fmt.Errorf("encoding %q error: unsupport type %v", tagName, tagValue.Type())
} }
if err := e.marshal(tagValue, tagType, tagName); err != nil { if err := e.marshal(tagValue, tagType, tagName); err != nil {

View File

@ -109,7 +109,7 @@ func ExamplePacket_Scan_joinGame() {
Hardcore pk.Boolean Hardcore pk.Boolean
Gamemode pk.UnsignedByte Gamemode pk.UnsignedByte
PreGamemode pk.Byte PreGamemode pk.Byte
WorldNames = make([]pk.Identifier, 0) // This cannot replace with "var WorldNames []pk.Identifier" because "nil" has no type information WorldNames = make([]pk.Identifier, 0) // This cannot replace with "var DimensionNames []pk.Identifier" because "nil" has no type information
DimensionCodec struct { DimensionCodec struct {
DimensionType interface{} `nbt:"minecraft:dimension_type"` DimensionType interface{} `nbt:"minecraft:dimension_type"`
WorldgenBiome interface{} `nbt:"minecraft:worldgen/biome"` WorldgenBiome interface{} `nbt:"minecraft:worldgen/biome"`

View File

@ -26,7 +26,7 @@ type Game struct {
components []Component components []Component
} }
func (g *Game) AcceptPlayer(name string, id uuid.UUID, protocol int32, conn *net.Conn) { func (g *Game) AcceptPlayer(name string, id uuid.UUID, profilePubKey *rsa.PublicKey, protocol int32, conn *net.Conn) {
conn.Close() conn.Close()
} }