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 }