diff --git a/.idea/dictionaries/cjd00.xml b/.idea/dictionaries/cjd00.xml index d65ce43..c07b8a8 100644 --- a/.idea/dictionaries/cjd00.xml +++ b/.idea/dictionaries/cjd00.xml @@ -7,6 +7,7 @@ minecraft mojang rcon + teleport \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..26ad532 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/README.md b/README.md index 140cb70..b759c58 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Go-MC -![](https://img.shields.io/badge/Minecraft-1.14.4-blue.svg) -![](https://img.shields.io/badge/Protocol-498-blue.svg) +![Version](https://img.shields.io/badge/Minecraft-1.14.4-blue.svg) +![Protocol](https://img.shields.io/badge/Protocol-498-blue.svg) [![GoDoc](https://godoc.org/github.com/Tnze/go-mc?status.svg)](https://godoc.org/github.com/Tnze/go-mc) [![Go Report Card](https://goreportcard.com/badge/github.com/Tnze/go-mc)](https://goreportcard.com/report/github.com/Tnze/go-mc) [![Build Status](https://travis-ci.org/Tnze/go-mc.svg?branch=master)](https://travis-ci.org/Tnze/go-mc) diff --git a/bot/ingame.go b/bot/ingame.go index 8f5d99a..23bf64b 100644 --- a/bot/ingame.go +++ b/bot/ingame.go @@ -465,6 +465,7 @@ type blockEntities []blockEntitie type blockEntitie struct { } +// Decode implement net.packet.FieldDecoder func (c *chunkData) Decode(r pk.DecodeReader) error { var Size pk.VarInt if err := Size.Decode(r); err != nil { @@ -477,14 +478,15 @@ func (c *chunkData) Decode(r pk.DecodeReader) error { return nil } +// Decode implement net.packet.FieldDecoder func (b *blockEntities) Decode(r pk.DecodeReader) error { - var NumberofBlockEntities pk.VarInt - if err := NumberofBlockEntities.Decode(r); err != nil { + var nobe pk.VarInt // Number of BlockEntities + if err := nobe.Decode(r); err != nil { return err } - *b = make(blockEntities, NumberofBlockEntities) + *b = make(blockEntities, nobe) decoder := nbt.NewDecoder(r) - for i := 0; i < int(NumberofBlockEntities); i++ { + for i := 0; i < int(nobe); i++ { if err := decoder.Decode(&(*b)[i]); err != nil { return err } diff --git a/nbt/read.go b/nbt/read.go index 2f7ce11..7c9bdda 100644 --- a/nbt/read.go +++ b/nbt/read.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "io" + "io/ioutil" "math" "reflect" ) @@ -16,46 +17,33 @@ func Unmarshal(data []byte, v interface{}) error { func (d *Decoder) Decode(v interface{}) error { val := reflect.ValueOf(v) if val.Kind() != reflect.Ptr { - return errors.New("non-pointer passed to Unmarshal") - } - - // check the head - compress, err := d.checkCompressed() - if err != nil { - return fmt.Errorf("check compressed fail: %v", err) - } - if compress != "" { - return fmt.Errorf("data may compressed with %s", compress) + return errors.New("nbt: non-pointer passed to Unmarshal") } //start read NBT tagType, tagName, err := d.readTag() if err != nil { - return err + return fmt.Errorf("nbt: %v", err) } - return d.unmarshal(val.Elem(), tagType, tagName) + + if c := d.checkCompressed(tagType); c != "" { + return fmt.Errorf("nbt: unknown Tag, maybe need %s", c) + } + + err = d.unmarshal(val.Elem(), tagType, tagName) + if err != nil { + return fmt.Errorf("nbt: %v", err) + } + return nil } // check the first byte and return if it use compress -func (d *Decoder) checkCompressed() (compress string, err error) { - var head byte - head, err = d.r.ReadByte() - if err != nil { - return - } - - if head <= 12 { //NBT - compress = "" - } else if head == 0x1f { //gzip +func (d *Decoder) checkCompressed(head byte) (compress string) { + if head == 0x1f { //gzip compress = "gzip" } else if head == 0x78 { //zlib compress = "zlib" - } else { - compress = "unknown" } - - err = d.r.UnreadByte() - return } @@ -187,12 +175,15 @@ func (d *Decoder) unmarshal(val reflect.Value, tagType byte, tagName string) err } case TagByteArray: - var ba []byte aryLen, err := d.readInt32() if err != nil { return err } - if ba, err = d.readNByte(int(aryLen)); err != nil { + if aryLen < 0 { + return errors.New("byte array len less than 0") + } + ba := make([]byte, aryLen) + if _, err = io.ReadFull(d.r, ba); err != nil { return err } @@ -366,6 +357,7 @@ func (d *Decoder) unmarshal(val reflect.Value, tagType byte, tagName string) err } func (d *Decoder) rawRead(tagType byte) error { + var buf [8]byte switch tagType { default: return fmt.Errorf("unknown to read 0x%02x", tagType) @@ -376,20 +368,21 @@ func (d *Decoder) rawRead(tagType byte) error { _, err := d.readString() return err case TagShort: - _, err := d.readNByte(2) + _, err := io.ReadFull(d.r, buf[:2]) return err case TagInt, TagFloat: - _, err := d.readNByte(4) + _, err := io.ReadFull(d.r, buf[:4]) return err case TagLong, TagDouble: - _, err := d.readNByte(8) + _, err := io.ReadFull(d.r, buf[:8]) return err case TagByteArray: aryLen, err := d.readInt32() if err != nil { return err } - if _, err = d.readNByte(int(aryLen)); err != nil { + + if _, err = io.CopyN(ioutil.Discard, d.r, int64(aryLen)); err != nil { return err } case TagIntArray: @@ -458,30 +451,22 @@ func (d *Decoder) readTag() (tagType byte, tagName string, err error) { return } -func (d *Decoder) readNByte(n int) (buf []byte, err error) { - if n < 0 { - return nil, errors.New("read n byte cannot less than 0") - } - - buf = make([]byte, n) - _, err = io.ReadFull(d.r, buf) - - return -} - func (d *Decoder) readInt16() (int16, error) { - data, err := d.readNByte(2) + var data [2]byte + _, err := io.ReadFull(d.r, data[:]) return int16(data[0])<<8 | int16(data[1]), err } func (d *Decoder) readInt32() (int32, error) { - data, err := d.readNByte(4) + var data [4]byte + _, err := io.ReadFull(d.r, data[:]) return int32(data[0])<<24 | int32(data[1])<<16 | int32(data[2])<<8 | int32(data[3]), err } func (d *Decoder) readInt64() (int64, error) { - data, err := d.readNByte(8) + var data [8]byte + _, err := io.ReadFull(d.r, data[:]) return int64(data[0])<<56 | int64(data[1])<<48 | int64(data[2])<<40 | int64(data[3])<<32 | int64(data[4])<<24 | int64(data[5])<<16 | @@ -492,7 +477,15 @@ func (d *Decoder) readString() (string, error) { length, err := d.readInt16() if err != nil { return "", err + } else if length < 0 { + return "", errors.New("string length less than 0") } - buf, err := d.readNByte(int(length)) - return string(buf), err + + var str string + if length > 0 { + buf := make([]byte, length) + _, err = io.ReadFull(d.r, buf) + str = string(buf) + } + return str, err } diff --git a/save/chunk.go b/save/chunk.go new file mode 100644 index 0000000..154931f --- /dev/null +++ b/save/chunk.go @@ -0,0 +1,71 @@ +package save + +import ( + "bytes" + "compress/gzip" + "compress/zlib" + "errors" + "github.com/Tnze/go-mc/nbt" + "io" +) + +// Column is 16* chunk +type Column struct { + DataVersion int + Level struct { + Heightmaps map[string][]int64 + Structures struct { + References map[string][]int64 + Starts map[string]struct { + ID string `nbt:"id"` + } + } + // Entities + // LiquidTicks + // PostProcessing + Sections []Chunk + // TileEntities + // TileTicks + InhabitedTime int64 + IsLightOn byte `nbt:"isLightOn"` + LastUpdate int64 + Status string + PosX int32 `nbt:"xPos"` + PosY int32 `nbt:"yPos"` + Biomes []int32 + } +} + +type Chunk struct { + Palette []Block + Y byte + BlockLight []byte + BlockStates []int64 + SkyLight []byte +} + +type Block struct { + Name string + Properties map[string]interface{} +} + +// Load read column data from []byte +func (c *Column) Load(data []byte) (err error) { + var r io.Reader = bytes.NewReader(data[1:]) + + switch data[0] { + default: + err = errors.New("unknown compression") + case 1: + r, err = gzip.NewReader(r) + case 2: + r, err = zlib.NewReader(r) + } + + if err != nil { + return err + } + + err = nbt.NewDecoder(r).Decode(c) + return +} diff --git a/save/chunk_test.go b/save/chunk_test.go new file mode 100644 index 0000000..faad19a --- /dev/null +++ b/save/chunk_test.go @@ -0,0 +1,53 @@ +package save + +import ( + "github.com/Tnze/go-mc/save/region" + "math/rand" + "testing" +) + +func TestColumn(t *testing.T) { + var c Column + r, err := region.Open("testdata/region/r.0.0.mca") + if err != nil { + t.Fatal(err) + } + defer r.Close() + + data, err := r.ReadSector(0, 0) + if err != nil { + t.Fatal(err) + } + + err = c.Load(data) + if err != nil { + t.Fatal(err) + } + + t.Logf("%+v", c) +} + +func BenchmarkColumn_Load(b *testing.B) { + // Test how many time we load a chunk + var c Column + r, err := region.Open("testdata/region/r.-1.-1.mca") + if err != nil { + b.Fatal(err) + } + defer r.Close() + + for i := 0; i < b.N; i++ { + //x, y := (i%1024)/32, (i%1024)%32 + x, y := rand.Intn(32), rand.Intn(32) + + data, err := r.ReadSector(x, y) + if err != nil { + b.Fatal(err) + } + + err = c.Load(data) + if err != nil { + b.Fatal(err) + } + } +} diff --git a/save/region/mca.go b/save/region/mca.go index 9ae3593..eb242de 100644 --- a/save/region/mca.go +++ b/save/region/mca.go @@ -8,6 +8,7 @@ import ( "time" ) +// Region contain 32*32 chunks in one .mca file type Region struct { f *os.File offsets [32][32]int32 @@ -26,9 +27,9 @@ func In(cx, cy int) (int, int) { return cx & 31, cy & 31 } -// OpenRegion open a .mca file and read the head. +// Open open a .mca file and read the head. // Close the Region after used. -func OpenRegion(name string) (r *Region, err error) { +func Open(name string) (r *Region, err error) { r = new(Region) r.sectors = make(map[int32]bool) @@ -67,6 +68,7 @@ func OpenRegion(name string) (r *Region, err error) { return r, nil } +// Close close the region file func (r *Region) Close() error { return r.f.Close() } @@ -75,6 +77,7 @@ func sectorLoc(offset int32) (o, s int32) { return offset >> 8, offset & 0xFF } +// ReadSector find and read the Chunk data from region func (r *Region) ReadSector(x, y int) (data []byte, err error) { offset, _ := sectorLoc(r.offsets[x][y]) @@ -99,6 +102,7 @@ func (r *Region) ReadSector(x, y int) (data []byte, err error) { return } +// WriteSector write Chunk data into region file func (r *Region) WriteSector(x, y int, data []byte) error { need := int32(len(data)+4)/4096 + 1 n, now := sectorLoc(r.offsets[x][y]) @@ -155,6 +159,11 @@ func (r *Region) WriteSector(x, y int, data []byte) error { return nil } +// ExistSector return if a sector is exist +func (r *Region) ExistSector(x, y int) bool { + return r.offsets[x][y] != 0 +} + func (r *Region) findSpace(need int32) (n int32) { for i := int32(0); i < need; i++ { if r.sectors[n+i] { diff --git a/save/region/mca_test.go b/save/region/mca_test.go index ca061a9..2ca400c 100644 --- a/save/region/mca_test.go +++ b/save/region/mca_test.go @@ -22,7 +22,7 @@ func TestIn(t *testing.T) { } func TestReadRegion(t *testing.T) { - r, err := OpenRegion("../testdata/region/r.0.-1.mca") + r, err := Open("../testdata/region/r.0.-1.mca") if err != nil { t.Fatal(err) } @@ -77,3 +77,21 @@ func TestFindSpace(t *testing.T) { } } } + +func TestCountChunks(t *testing.T) { + r, err := Open("../testdata/region/r.-1.-1.mca") + if err != nil { + t.Fatal(err) + } + defer r.Close() + + var count int + for i := 0; i < 32; i++ { + for j := 0; j < 32; j++ { + if r.ExistSector(i, j) { + count++ + } + } + } + t.Logf("chunk count: %d", count) +} diff --git a/save/testdata/region/r.-1.-1.mca b/save/testdata/region/r.-1.-1.mca index 9fe43b0..c6f670d 100644 Binary files a/save/testdata/region/r.-1.-1.mca and b/save/testdata/region/r.-1.-1.mca differ