github.com/df-mc/dragonfly@v0.9.13/server/world/mcdb/db.go (about)

     1  package mcdb
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/binary"
     6  	"errors"
     7  	"fmt"
     8  	"github.com/df-mc/dragonfly/server/block/cube"
     9  	"github.com/df-mc/dragonfly/server/world"
    10  	"github.com/df-mc/dragonfly/server/world/chunk"
    11  	"github.com/df-mc/dragonfly/server/world/mcdb/leveldat"
    12  	"github.com/df-mc/goleveldb/leveldb"
    13  	"github.com/google/uuid"
    14  	"github.com/sandertv/gophertunnel/minecraft/nbt"
    15  	"golang.org/x/exp/maps"
    16  	"os"
    17  	"path/filepath"
    18  	"time"
    19  )
    20  
    21  // DB implements a world provider for the Minecraft world format, which
    22  // is based on a leveldb database.
    23  type DB struct {
    24  	conf Config
    25  	ldb  *leveldb.DB
    26  	dir  string
    27  	ldat *leveldat.Data
    28  	set  *world.Settings
    29  }
    30  
    31  // Open creates a new provider reading and writing from/to files under the path
    32  // passed using default options. If a world is present at the path, Open will
    33  // parse its data and initialise the world with it. If the data cannot be
    34  // parsed, an error is returned.
    35  func Open(dir string) (*DB, error) {
    36  	var conf Config
    37  	return conf.Open(dir)
    38  }
    39  
    40  // Settings returns the world.Settings of the world loaded by the DB.
    41  func (db *DB) Settings() *world.Settings {
    42  	return db.set
    43  }
    44  
    45  // SaveSettings saves the world.Settings passed to the level.dat.
    46  func (db *DB) SaveSettings(s *world.Settings) {
    47  	db.ldat.PutSettings(s)
    48  }
    49  
    50  // playerData holds the fields that indicate where player data is stored for a player with a specific UUID.
    51  type playerData struct {
    52  	UUID         string `nbt:"MsaId"`
    53  	ServerID     string `nbt:"ServerId"`
    54  	SelfSignedID string `nbt:"SelfSignedId"`
    55  }
    56  
    57  // LoadPlayerSpawnPosition loads the players spawn position stored in the level.dat from their UUID.
    58  func (db *DB) LoadPlayerSpawnPosition(id uuid.UUID) (pos cube.Pos, exists bool, err error) {
    59  	serverData, _, exists, err := db.loadPlayerData(id)
    60  	if !exists || err != nil {
    61  		return cube.Pos{}, exists, err
    62  	}
    63  	x, y, z := serverData["SpawnX"], serverData["SpawnY"], serverData["SpawnZ"]
    64  	if x == nil || y == nil || z == nil {
    65  		return cube.Pos{}, true, fmt.Errorf("error reading spawn fields from server data for player %v", id)
    66  	}
    67  	return cube.Pos{int(x.(int32)), int(y.(int32)), int(z.(int32))}, true, nil
    68  }
    69  
    70  // loadPlayerData loads the data stored in a LevelDB database for a specific UUID.
    71  func (db *DB) loadPlayerData(id uuid.UUID) (serverData map[string]interface{}, key string, exists bool, err error) {
    72  	data, err := db.ldb.Get([]byte("player_"+id.String()), nil)
    73  	if err == leveldb.ErrNotFound {
    74  		return nil, "", false, nil
    75  	} else if err != nil {
    76  		return nil, "", true, fmt.Errorf("error reading player data for uuid %v: %w", id, err)
    77  	}
    78  
    79  	var d playerData
    80  	if err := nbt.UnmarshalEncoding(data, &d, nbt.LittleEndian); err != nil {
    81  		return nil, "", true, fmt.Errorf("error decoding player data for uuid %v: %w", id, err)
    82  	}
    83  	if d.UUID != id.String() || d.ServerID == "" {
    84  		return nil, d.ServerID, true, fmt.Errorf("invalid player data for uuid %v: %v", id, d)
    85  	}
    86  	serverDB, err := db.ldb.Get([]byte(d.ServerID), nil)
    87  	if err != nil {
    88  		return nil, d.ServerID, true, fmt.Errorf("error reading server data for player %v (%v): %w", id, d.ServerID, err)
    89  	}
    90  
    91  	if err := nbt.UnmarshalEncoding(serverDB, &serverData, nbt.LittleEndian); err != nil {
    92  		return nil, d.ServerID, true, fmt.Errorf("error decoding server data for player %v", id)
    93  	}
    94  	return serverData, d.ServerID, true, nil
    95  }
    96  
    97  // SavePlayerSpawnPosition saves the player spawn position passed to the levelDB database.
    98  func (db *DB) SavePlayerSpawnPosition(id uuid.UUID, pos cube.Pos) error {
    99  	_, err := db.ldb.Get([]byte("player_"+id.String()), nil)
   100  	d := make(map[string]interface{})
   101  	k := "player_server_" + id.String()
   102  
   103  	if errors.Is(err, leveldb.ErrNotFound) {
   104  		data, err := nbt.MarshalEncoding(playerData{
   105  			UUID:     id.String(),
   106  			ServerID: k,
   107  		}, nbt.LittleEndian)
   108  		if err != nil {
   109  			panic(err)
   110  		}
   111  		if err := db.ldb.Put([]byte("player_"+id.String()), data, nil); err != nil {
   112  			return fmt.Errorf("error writing player data for id %v: %w", id, err)
   113  		}
   114  	} else {
   115  		if d, k, _, err = db.loadPlayerData(id); err != nil {
   116  			return err
   117  		}
   118  	}
   119  	d["SpawnX"] = int32(pos.X())
   120  	d["SpawnY"] = int32(pos.Y())
   121  	d["SpawnZ"] = int32(pos.Z())
   122  
   123  	data, err := nbt.MarshalEncoding(d, nbt.LittleEndian)
   124  	if err != nil {
   125  		panic(err)
   126  	}
   127  	if err = db.ldb.Put([]byte(k), data, nil); err != nil {
   128  		return fmt.Errorf("error writing server data for player %v: %w", id, err)
   129  	}
   130  	return nil
   131  }
   132  
   133  // LoadColumn reads a world.Column from the DB at a position and dimension in
   134  // the DB. If no column at that position exists, errors.Is(err,
   135  // leveldb.ErrNotFound) equals true.
   136  func (db *DB) LoadColumn(pos world.ChunkPos, dim world.Dimension) (*world.Column, error) {
   137  	k := dbKey{pos: pos, dim: dim}
   138  	col, err := db.column(k)
   139  	if err != nil {
   140  		return nil, fmt.Errorf("load column %v (%v): %w", pos, dim, err)
   141  	}
   142  	return col, nil
   143  }
   144  
   145  const chunkVersion = 40
   146  
   147  func (db *DB) column(k dbKey) (*world.Column, error) {
   148  	var cdata chunk.SerialisedData
   149  	col := new(world.Column)
   150  
   151  	ver, err := db.version(k)
   152  	if err != nil {
   153  		return nil, fmt.Errorf("read version: %w", err)
   154  	}
   155  	if ver != chunkVersion {
   156  		db.conf.Log.Debugf("column %v (%v): unsupported chunk version %v, trying to load anyway", k.pos, k.dim, ver)
   157  	}
   158  	cdata.Biomes, err = db.biomes(k)
   159  	if err != nil && !errors.Is(err, leveldb.ErrNotFound) {
   160  		// Some chunks still use 2D chunk data and might not have this field, in
   161  		// which case we can just move on.
   162  		return nil, fmt.Errorf("read biomes: %w", err)
   163  	}
   164  	cdata.SubChunks, err = db.subChunks(k)
   165  	if err != nil {
   166  		return nil, fmt.Errorf("read sub chunks: %w", err)
   167  	}
   168  	col.Chunk, err = chunk.DiskDecode(cdata, k.dim.Range())
   169  	if err != nil {
   170  		return nil, fmt.Errorf("decode chunk data: %w", err)
   171  	}
   172  	col.Entities, err = db.entities(k)
   173  	if err != nil && !errors.Is(err, leveldb.ErrNotFound) {
   174  		// Not all chunks need to have entities, so an ErrNotFound is fine here.
   175  		return nil, fmt.Errorf("read entities: %w", err)
   176  	}
   177  	col.BlockEntities, err = db.blockEntities(k, col.Chunk)
   178  	if err != nil && !errors.Is(err, leveldb.ErrNotFound) {
   179  		// Same as with entities, an ErrNotFound is fine here.
   180  		return nil, fmt.Errorf("read block entities: %w", err)
   181  	}
   182  	return col, nil
   183  }
   184  
   185  func (db *DB) version(k dbKey) (byte, error) {
   186  	p, err := db.ldb.Get(k.Sum(keyVersion), nil)
   187  	switch err {
   188  	default:
   189  		return 0, err
   190  	case leveldb.ErrNotFound:
   191  		// Although the version at `keyVersion` may not be found, there is
   192  		// another `keyVersionOld` where the version may be found.
   193  		if p, err = db.ldb.Get(k.Sum(keyVersionOld), nil); err != nil {
   194  			return 0, err
   195  		}
   196  		fallthrough
   197  	case nil:
   198  		if n := len(p); n != 1 {
   199  			return 0, fmt.Errorf("expected 1 version byte, found %v", n)
   200  		}
   201  		return p[0], nil
   202  	}
   203  }
   204  
   205  func (db *DB) biomes(k dbKey) ([]byte, error) {
   206  	biomes, err := db.ldb.Get(k.Sum(key3DData), nil)
   207  	if err != nil {
   208  		return nil, err
   209  	}
   210  	// The first 512 bytes is a heightmap (16*16 int16s), the biomes follow. We
   211  	// calculate a heightmap on startup so the heightmap is discarded.
   212  	if n := len(biomes); n <= 512 {
   213  		return nil, fmt.Errorf("expected at least 513 bytes for 3D data, got %v", n)
   214  	}
   215  	return biomes[512:], nil
   216  }
   217  
   218  func (db *DB) subChunks(k dbKey) ([][]byte, error) {
   219  	r := k.dim.Range()
   220  	sub := make([][]byte, (r.Height()>>4)+1)
   221  
   222  	var err error
   223  	for i := range sub {
   224  		y := uint8(i + (r[0] >> 4))
   225  		sub[i], err = db.ldb.Get(k.Sum(keySubChunkData, y), nil)
   226  		if err == leveldb.ErrNotFound {
   227  			// No sub chunk present at this Y level. We skip this one and move
   228  			// to the next, which might still be present.
   229  			continue
   230  		} else if err != nil {
   231  			return nil, fmt.Errorf("sub chunk %v: %w", int8(i), err)
   232  		}
   233  	}
   234  	return sub, nil
   235  }
   236  
   237  func (db *DB) entities(k dbKey) ([]world.Entity, error) {
   238  	data, err := db.ldb.Get(k.Sum(keyEntities), nil)
   239  	if err != nil {
   240  		return nil, err
   241  	}
   242  	var entities []world.Entity
   243  
   244  	buf := bytes.NewBuffer(data)
   245  	dec := nbt.NewDecoderWithEncoding(buf, nbt.LittleEndian)
   246  
   247  	var m map[string]any
   248  	for buf.Len() != 0 {
   249  		maps.Clear(m)
   250  		if err := dec.Decode(&m); err != nil {
   251  			return nil, fmt.Errorf("decode nbt: %w", err)
   252  		}
   253  		id, ok := m["identifier"]
   254  		if !ok {
   255  			db.conf.Log.Errorf("missing identifier field in %v", m)
   256  			continue
   257  		}
   258  		name, _ := id.(string)
   259  		t, ok := db.conf.Entities.Lookup(name)
   260  		if !ok {
   261  			db.conf.Log.Errorf("entity %v was not registered (%v)", name, m)
   262  			continue
   263  		}
   264  		if s, ok := t.(world.SaveableEntityType); ok {
   265  			if v := s.DecodeNBT(m); v != nil {
   266  				entities = append(entities, v)
   267  			}
   268  		}
   269  	}
   270  	return entities, nil
   271  }
   272  
   273  func (db *DB) blockEntities(k dbKey, c *chunk.Chunk) (map[cube.Pos]world.Block, error) {
   274  	blockEntities := make(map[cube.Pos]world.Block)
   275  
   276  	data, err := db.ldb.Get(k.Sum(keyBlockEntities), nil)
   277  	if err != nil {
   278  		return blockEntities, err
   279  	}
   280  
   281  	buf := bytes.NewBuffer(data)
   282  	dec := nbt.NewDecoderWithEncoding(buf, nbt.LittleEndian)
   283  
   284  	var m map[string]any
   285  	for buf.Len() != 0 {
   286  		maps.Clear(m)
   287  		if err := dec.Decode(&m); err != nil {
   288  			return blockEntities, fmt.Errorf("decode nbt: %w", err)
   289  		}
   290  		pos := blockPosFromNBT(m)
   291  
   292  		id := c.Block(uint8(pos[0]), int16(pos[1]), uint8(pos[2]), 0)
   293  		b, ok := world.BlockByRuntimeID(id)
   294  		if !ok {
   295  			db.conf.Log.Errorf("no block registered with runtime id %v", id)
   296  			continue
   297  		}
   298  		nbter, ok := b.(world.NBTer)
   299  		if !ok {
   300  			db.conf.Log.Errorf("block %#v has nbt but does not implement world.nbter", b)
   301  			continue
   302  		}
   303  		blockEntities[pos] = nbter.DecodeNBT(m).(world.Block)
   304  	}
   305  	return blockEntities, nil
   306  }
   307  
   308  // StoreColumn stores a world.Column at a position and dimension in the DB. An
   309  // error is returned if storing was unsuccessful.
   310  func (db *DB) StoreColumn(pos world.ChunkPos, dim world.Dimension, col *world.Column) error {
   311  	k := dbKey{pos: pos, dim: dim}
   312  	if err := db.storeColumn(k, col); err != nil {
   313  		return fmt.Errorf("store column %v (%v): %w", pos, dim, err)
   314  	}
   315  	return nil
   316  }
   317  
   318  func (db *DB) storeColumn(k dbKey, col *world.Column) error {
   319  	data := chunk.Encode(col.Chunk, chunk.DiskEncoding)
   320  	n := 5 + len(data.SubChunks)
   321  	batch := leveldb.MakeBatch(n)
   322  
   323  	db.storeVersion(batch, k, chunkVersion)
   324  	db.storeBiomes(batch, k, data.Biomes)
   325  	db.storeSubChunks(batch, k, data.SubChunks, col.Chunk.Range())
   326  	db.storeFinalisation(batch, k, finalisationPopulated)
   327  	db.storeEntities(batch, k, col.Entities)
   328  	db.storeBlockEntities(batch, k, col.BlockEntities)
   329  
   330  	return db.ldb.Write(batch, nil)
   331  }
   332  
   333  func (db *DB) storeVersion(batch *leveldb.Batch, k dbKey, ver uint8) {
   334  	batch.Put(k.Sum(keyVersion), []byte{ver})
   335  }
   336  
   337  var emptyHeightmap = make([]byte, 512)
   338  
   339  func (db *DB) storeBiomes(batch *leveldb.Batch, k dbKey, biomes []byte) {
   340  	batch.Put(k.Sum(key3DData), append(emptyHeightmap, biomes...))
   341  }
   342  
   343  func (db *DB) storeSubChunks(batch *leveldb.Batch, k dbKey, subChunks [][]byte, r cube.Range) {
   344  	for i, sub := range subChunks {
   345  		batch.Put(k.Sum(keySubChunkData, byte(i+(r[0]>>4))), sub)
   346  	}
   347  }
   348  
   349  func (db *DB) storeFinalisation(batch *leveldb.Batch, k dbKey, finalisation uint32) {
   350  	p := make([]byte, 4)
   351  	binary.LittleEndian.PutUint32(p, finalisation)
   352  	batch.Put(k.Sum(keyFinalisation), p)
   353  }
   354  
   355  func (db *DB) storeEntities(batch *leveldb.Batch, k dbKey, entities []world.Entity) {
   356  	if len(entities) == 0 {
   357  		batch.Delete(k.Sum(keyEntities))
   358  		return
   359  	}
   360  
   361  	buf := bytes.NewBuffer(nil)
   362  	enc := nbt.NewEncoderWithEncoding(buf, nbt.LittleEndian)
   363  	for _, e := range entities {
   364  		t, ok := e.Type().(world.SaveableEntityType)
   365  		if !ok {
   366  			continue
   367  		}
   368  		x := t.EncodeNBT(e)
   369  		x["identifier"] = t.EncodeEntity()
   370  		if err := enc.Encode(x); err != nil {
   371  			db.conf.Log.Errorf("store entities: error encoding NBT: %w", err)
   372  		}
   373  	}
   374  	batch.Put(k.Sum(keyEntities), buf.Bytes())
   375  }
   376  
   377  func (db *DB) storeBlockEntities(batch *leveldb.Batch, k dbKey, blockEntities map[cube.Pos]world.Block) {
   378  	if len(blockEntities) == 0 {
   379  		batch.Delete(k.Sum(keyBlockEntities))
   380  		return
   381  	}
   382  
   383  	buf := bytes.NewBuffer(nil)
   384  	enc := nbt.NewEncoderWithEncoding(buf, nbt.LittleEndian)
   385  	for pos, b := range blockEntities {
   386  		n, ok := b.(world.NBTer)
   387  		if !ok {
   388  			continue
   389  		}
   390  		data := n.EncodeNBT()
   391  		data["x"], data["y"], data["z"] = int32(pos[0]), int32(pos[1]), int32(pos[2])
   392  		if err := enc.Encode(data); err != nil {
   393  			db.conf.Log.Errorf("store block entities: error encoding NBT: %w", err)
   394  		}
   395  	}
   396  	batch.Put(k.Sum(keyBlockEntities), buf.Bytes())
   397  }
   398  
   399  // NewColumnIterator returns a ColumnIterator that may be used to iterate over all
   400  // position/chunk pairs in a database.
   401  // An IteratorRange r may be passed to specify limits in terms of what chunks
   402  // should be read. r may be set to nil to read all chunks from the DB.
   403  func (db *DB) NewColumnIterator(r *IteratorRange) *ColumnIterator {
   404  	if r == nil {
   405  		r = &IteratorRange{}
   406  	}
   407  	return newColumnIterator(db, r)
   408  }
   409  
   410  // Close closes the provider, saving any file that might need to be saved, such as the level.dat.
   411  func (db *DB) Close() error {
   412  	db.ldat.LastPlayed = time.Now().Unix()
   413  
   414  	var ldat leveldat.LevelDat
   415  	if err := ldat.Marshal(*db.ldat); err != nil {
   416  		return fmt.Errorf("close: %w", err)
   417  	}
   418  	if err := ldat.WriteFile(filepath.Join(db.dir, "level.dat")); err != nil {
   419  		return fmt.Errorf("close: %w", err)
   420  	}
   421  	if err := os.WriteFile(filepath.Join(db.dir, "levelname.txt"), []byte(db.ldat.LevelName), 0644); err != nil {
   422  		return fmt.Errorf("close: write levelname.txt: %w", err)
   423  	}
   424  	return db.ldb.Close()
   425  }
   426  
   427  // dbKey holds a position and dimension.
   428  type dbKey struct {
   429  	pos world.ChunkPos
   430  	dim world.Dimension
   431  }
   432  
   433  // Sum converts k to its []byte representation and appends p.
   434  func (k dbKey) Sum(p ...byte) []byte {
   435  	return append(index(k.pos, k.dim), p...)
   436  }
   437  
   438  // index returns a byte buffer holding the written index of the chunk position passed. If the dimension passed
   439  // is not world.Overworld, the length of the index returned is 12. It is 8 otherwise.
   440  func index(position world.ChunkPos, d world.Dimension) []byte {
   441  	dim, _ := world.DimensionID(d)
   442  	x, z := uint32(position[0]), uint32(position[1])
   443  	b := make([]byte, 12)
   444  
   445  	binary.LittleEndian.PutUint32(b, x)
   446  	binary.LittleEndian.PutUint32(b[4:], z)
   447  	if dim == 0 {
   448  		return b[:8]
   449  	}
   450  	binary.LittleEndian.PutUint32(b[8:], uint32(dim))
   451  	return b
   452  }
   453  
   454  // blockPosFromNBT returns a position from the X, Y and Z components stored in the NBT data map passed. The
   455  // map is assumed to have an 'x', 'y' and 'z' key.
   456  func blockPosFromNBT(data map[string]any) cube.Pos {
   457  	x, _ := data["x"].(int32)
   458  	y, _ := data["y"].(int32)
   459  	z, _ := data["z"].(int32)
   460  	return cube.Pos{int(x), int(y), int(z)}
   461  }