github.com/df-mc/dragonfly@v0.9.13/server/server.go (about) 1 package server 2 3 import ( 4 "bytes" 5 "context" 6 _ "embed" 7 "encoding/base64" 8 "fmt" 9 "github.com/df-mc/atomic" 10 "github.com/df-mc/dragonfly/server/cmd" 11 "github.com/df-mc/dragonfly/server/internal/blockinternal" 12 "github.com/df-mc/dragonfly/server/internal/iteminternal" 13 "github.com/df-mc/dragonfly/server/internal/sliceutil" 14 _ "github.com/df-mc/dragonfly/server/item" // Imported for maintaining correct initialisation order. 15 "github.com/df-mc/dragonfly/server/player" 16 "github.com/df-mc/dragonfly/server/player/skin" 17 "github.com/df-mc/dragonfly/server/session" 18 "github.com/df-mc/dragonfly/server/world" 19 "github.com/go-gl/mathgl/mgl32" 20 "github.com/go-gl/mathgl/mgl64" 21 "github.com/google/uuid" 22 "github.com/sandertv/gophertunnel/minecraft" 23 "github.com/sandertv/gophertunnel/minecraft/nbt" 24 "github.com/sandertv/gophertunnel/minecraft/protocol" 25 "github.com/sandertv/gophertunnel/minecraft/protocol/login" 26 "github.com/sandertv/gophertunnel/minecraft/protocol/packet" 27 "github.com/sandertv/gophertunnel/minecraft/text" 28 "github.com/sirupsen/logrus" 29 "golang.org/x/exp/maps" 30 "os" 31 "os/exec" 32 "os/signal" 33 "runtime" 34 "strings" 35 "sync" 36 "syscall" 37 ) 38 39 // Server implements a Dragonfly server. It runs the main server loop and 40 // handles the connections of players trying to join the server. 41 type Server struct { 42 conf Config 43 44 once sync.Once 45 started atomic.Bool 46 47 world, nether, end *world.World 48 49 customBlocks []protocol.BlockEntry 50 customItems []protocol.ItemComponentEntry 51 52 listeners []Listener 53 incoming chan *session.Session 54 55 pmu sync.RWMutex 56 // p holds a map of all players currently connected to the server. When they 57 // leave, they are removed from the map. 58 p map[uuid.UUID]*player.Player 59 // pwg is a sync.WaitGroup used to wait for all players to be disconnected 60 // before server shutdown, so that their data is saved properly. 61 pwg sync.WaitGroup 62 // wg is used to wait for all Listeners to be closed and their respective 63 // goroutines to be finished. 64 wg sync.WaitGroup 65 } 66 67 // HandleFunc is a function that may be passed to Server.Accept(). It can be 68 // used to prepare the session of a player before it can do anything. 69 type HandleFunc func(p *player.Player) 70 71 // New creates a Server using a default Config. The Server's worlds are created 72 // and connections from the Server's listeners may be accepted by calling 73 // Server.Listen() and Server.Accept() afterwards. 74 func New() *Server { 75 var conf Config 76 return conf.New() 77 } 78 79 // Listen starts running the server's listeners but does not block, unlike Run. 80 // Connections will be accepted on a different goroutine until the listeners 81 // are closed using a call to Close. Once started, players may be accepted 82 // using Server.Accept(). 83 func (srv *Server) Listen() { 84 if !srv.started.CAS(false, true) { 85 panic("start server: already started") 86 } 87 88 srv.conf.Log.Infof("Starting Dragonfly for Minecraft v%v...", protocol.CurrentVersion) 89 srv.startListening() 90 go srv.wait() 91 } 92 93 // Accept accepts an incoming player into the server. It blocks until a player 94 // connects to the server. A HandleFunc may be passed which is run immediately 95 // before a *player.Player is accepted to the Server. This function may be used 96 // to add a player.Handler to the player and prepare its session. The function 97 // may be nil if player joining does not need to be handled. Accept returns 98 // false if the Server is closed using a call to Close. 99 func (srv *Server) Accept(f HandleFunc) bool { 100 s, ok := <-srv.incoming 101 if !ok { 102 return false 103 } 104 p := s.Controllable().(*player.Player) 105 if f != nil { 106 f(p) 107 } 108 109 srv.pmu.Lock() 110 srv.p[p.UUID()] = p 111 srv.pmu.Unlock() 112 113 s.Start() 114 return true 115 } 116 117 // World returns the overworld of the server. Players will be spawned in this 118 // world and this world will be read from and written to when the world is 119 // edited. 120 func (srv *Server) World() *world.World { 121 return srv.world 122 } 123 124 // Nether returns the nether world of the server. Players are transported to it 125 // when entering a nether portal in the world returned by the World method. 126 func (srv *Server) Nether() *world.World { 127 return srv.nether 128 } 129 130 // End returns the end world of the server. Players are transported to it when 131 // entering an end portal in the world returned by the World method. 132 func (srv *Server) End() *world.World { 133 return srv.end 134 } 135 136 // MaxPlayerCount returns the maximum amount of players that are allowed to 137 // play on the server at the same time. Players trying to join when the server 138 // is full will be refused to enter. If the config has a maximum player count 139 // set to 0, MaxPlayerCount will return Server.PlayerCount + 1. 140 func (srv *Server) MaxPlayerCount() int { 141 if srv.conf.MaxPlayers == 0 { 142 return len(srv.Players()) + 1 143 } 144 return srv.conf.MaxPlayers 145 } 146 147 // Players returns a list of all players currently connected to the server. 148 // Note that the slice returned is not updated when new players join or leave, 149 // so it is only valid for as long as no new players join or players leave. 150 func (srv *Server) Players() []*player.Player { 151 srv.pmu.RLock() 152 defer srv.pmu.RUnlock() 153 return maps.Values(srv.p) 154 } 155 156 // Player looks for a player on the server with the UUID passed. If found, the 157 // player is returned and the bool returns holds a true value. If not, the bool 158 // returned is false and the player is nil. 159 func (srv *Server) Player(uuid uuid.UUID) (*player.Player, bool) { 160 srv.pmu.RLock() 161 defer srv.pmu.RUnlock() 162 p, ok := srv.p[uuid] 163 return p, ok 164 } 165 166 // PlayerByName looks for a player on the server with the name passed. If 167 // found, the player is returned and the bool returns holds a true value. If 168 // not, the bool is false and the player is nil 169 func (srv *Server) PlayerByName(name string) (*player.Player, bool) { 170 return sliceutil.SearchValue(srv.Players(), func(p *player.Player) bool { 171 return p.Name() == name 172 }) 173 } 174 175 // PlayerByXUID looks for a player on the server with the XUID passed. If found, 176 // the player is returned and the bool returned is true. If no player with the 177 // XUID was found, nil and false are returned. 178 func (srv *Server) PlayerByXUID(xuid string) (*player.Player, bool) { 179 return sliceutil.SearchValue(srv.Players(), func(p *player.Player) bool { 180 return p.XUID() == xuid 181 }) 182 } 183 184 // CloseOnProgramEnd closes the server right before the program ends, so that 185 // all data of the server are saved properly. 186 func (srv *Server) CloseOnProgramEnd() { 187 c := make(chan os.Signal, 2) 188 signal.Notify(c, syscall.SIGINT, syscall.SIGTERM) 189 go func() { 190 <-c 191 if err := srv.Close(); err != nil { 192 srv.conf.Log.Errorf("close server: %v", err) 193 } 194 }() 195 } 196 197 // Close closes the server, making any call to Run/Accept cancel immediately. 198 func (srv *Server) Close() error { 199 if !srv.started.Load() { 200 panic("server not yet running") 201 } 202 srv.once.Do(srv.close) 203 return nil 204 } 205 206 // close stops the server, storing player and world data to disk when 207 // necessary. 208 func (srv *Server) close() { 209 srv.conf.Log.Infof("Server shutting down...") 210 defer srv.conf.Log.Infof("Server stopped.") 211 212 srv.conf.Log.Debugf("Disconnecting players...") 213 for _, p := range srv.Players() { 214 p.Disconnect(text.Colourf("<yellow>%v</yellow>", srv.conf.ShutdownMessage)) 215 } 216 srv.pwg.Wait() 217 218 srv.conf.Log.Debugf("Closing player provider...") 219 if err := srv.conf.PlayerProvider.Close(); err != nil { 220 srv.conf.Log.Errorf("Error while closing player provider: %v", err) 221 } 222 223 srv.conf.Log.Debugf("Closing worlds...") 224 for _, w := range []*world.World{srv.end, srv.nether, srv.world} { 225 if err := w.Close(); err != nil { 226 srv.conf.Log.Errorf("Error closing %v: %v", w.Dimension(), err) 227 } 228 } 229 230 srv.conf.Log.Debugf("Closing listeners...") 231 for _, l := range srv.listeners { 232 if err := l.Close(); err != nil { 233 srv.conf.Log.Errorf("Error closing listener: %v", err) 234 } 235 } 236 } 237 238 // listen makes the Server listen for new connections from the Listener passed. 239 // This may be used to listen for players on different interfaces. Note that 240 // the maximum player count of additional Listeners added is not enforced 241 // automatically. The limit must be enforced by the Listener. 242 func (srv *Server) listen(l Listener) { 243 wg := new(sync.WaitGroup) 244 ctx, cancel := context.WithCancel(context.Background()) 245 for { 246 c, err := l.Accept() 247 if err != nil { 248 // Cancel the context so that any call to StartGameContext is 249 // cancelled rapidly. 250 cancel() 251 // First wait until all connections that are being handled are 252 // done inserting the player into the channel. Afterwards, when 253 // we're sure no more values will be inserted in the players 254 // channel, we can return so the player channel can be closed. 255 wg.Wait() 256 srv.wg.Done() 257 return 258 } 259 260 wg.Add(1) 261 go func() { 262 defer wg.Done() 263 if msg, ok := srv.conf.Allower.Allow(c.RemoteAddr(), c.IdentityData(), c.ClientData()); !ok { 264 _ = c.WritePacket(&packet.Disconnect{HideDisconnectionScreen: msg == "", Message: msg}) 265 _ = c.Close() 266 return 267 } 268 srv.finaliseConn(ctx, c, l) 269 }() 270 } 271 } 272 273 // startListening starts making the EncodeBlock listener listen, accepting new 274 // connections from players. 275 func (srv *Server) startListening() { 276 srv.makeBlockEntries() 277 srv.makeItemComponents() 278 279 srv.wg.Add(len(srv.conf.Listeners)) 280 for _, lf := range srv.conf.Listeners { 281 l, err := lf(srv.conf) 282 if err != nil { 283 srv.conf.Log.Fatalf("create listener: %v", err) 284 } 285 srv.listeners = append(srv.listeners, l) 286 go srv.listen(l) 287 } 288 } 289 290 // makeBlockEntries initializes the server's block components map using the registered custom blocks. It allows block 291 // components to be created only once at startup. 292 func (srv *Server) makeBlockEntries() { 293 custom := maps.Values(world.CustomBlocks()) 294 srv.customBlocks = make([]protocol.BlockEntry, len(custom)) 295 296 for i, b := range custom { 297 name, _ := b.EncodeBlock() 298 srv.customBlocks[i] = protocol.BlockEntry{ 299 Name: name, 300 Properties: blockinternal.Components(name, b, 10000+int32(i)), 301 } 302 } 303 } 304 305 // makeItemComponents initializes the server's item components map using the 306 // registered custom items. It allows item components to be created only once 307 // at startup 308 func (srv *Server) makeItemComponents() { 309 custom := world.CustomItems() 310 srv.customItems = make([]protocol.ItemComponentEntry, len(custom)) 311 312 for _, it := range custom { 313 name, _ := it.EncodeItem() 314 srv.customItems = append(srv.customItems, protocol.ItemComponentEntry{ 315 Name: name, 316 Data: iteminternal.Components(it), 317 }) 318 } 319 } 320 321 // wait awaits the closing of all Listeners added to the Server through a call 322 // to listen and closed the players channel once that happens. 323 func (srv *Server) wait() { 324 srv.wg.Wait() 325 close(srv.incoming) 326 } 327 328 // finaliseConn finalises the session.Conn passed and subtracts from the 329 // sync.WaitGroup once done. 330 func (srv *Server) finaliseConn(ctx context.Context, conn session.Conn, l Listener) { 331 id := uuid.MustParse(conn.IdentityData().Identity) 332 data := srv.defaultGameData() 333 334 var playerData *player.Data 335 if d, err := srv.conf.PlayerProvider.Load(id, srv.dimension); err == nil { 336 if d.World == nil { 337 d.World = srv.world 338 } 339 data.PlayerPosition = vec64To32(d.Position).Add(mgl32.Vec3{0, 1.62}) 340 dim, _ := world.DimensionID(d.World.Dimension()) 341 data.Dimension = int32(dim) 342 data.Yaw, data.Pitch = float32(d.Yaw), float32(d.Pitch) 343 344 playerData = &d 345 } 346 347 if err := conn.StartGameContext(ctx, data); err != nil { 348 _ = l.Disconnect(conn, "Connection timeout.") 349 350 srv.conf.Log.Debugf("connection %v failed spawning: %v\n", conn.RemoteAddr(), err) 351 return 352 } 353 _ = conn.WritePacket(&packet.ItemComponent{Items: srv.customItems}) 354 if p, ok := srv.Player(id); ok { 355 p.Disconnect("Logged in from another location.") 356 } 357 srv.incoming <- srv.createPlayer(id, conn, playerData) 358 } 359 360 // defaultGameData returns a minecraft.GameData as sent for a new player. It 361 // may later be modified if the player was saved in the player provider of the 362 // server. 363 func (srv *Server) defaultGameData() minecraft.GameData { 364 return minecraft.GameData{ 365 // We set these IDs to 1, because that's how the session will treat them. 366 EntityUniqueID: 1, 367 EntityRuntimeID: 1, 368 369 WorldName: srv.conf.Name, 370 BaseGameVersion: protocol.CurrentVersion, 371 372 Time: int64(srv.world.Time()), 373 Difficulty: 2, 374 375 PlayerGameMode: packet.GameTypeCreative, 376 PlayerPermissions: packet.PermissionLevelMember, 377 PlayerPosition: vec64To32(srv.world.Spawn().Vec3Centre().Add(mgl64.Vec3{0, 1.62})), 378 379 Items: srv.itemEntries(), 380 CustomBlocks: srv.customBlocks, 381 GameRules: []protocol.GameRule{{Name: "naturalregeneration", Value: false}}, 382 383 ServerAuthoritativeInventory: true, 384 PlayerMovementSettings: protocol.PlayerMovementSettings{ 385 MovementType: protocol.PlayerMovementModeServer, 386 ServerAuthoritativeBlockBreaking: true, 387 }, 388 } 389 } 390 391 // dimension returns a world by a dimension passed. 392 func (srv *Server) dimension(dimension world.Dimension) *world.World { 393 switch dimension { 394 default: 395 return srv.world 396 case world.Nether: 397 return srv.nether 398 case world.End: 399 return srv.end 400 } 401 } 402 403 // checkNetIsolation checks if a loopback exempt is in place to allow the 404 // hosting device to join the server. This is only relevant on Windows. It will 405 // never log anything for anything but Windows. 406 func (srv *Server) checkNetIsolation() { 407 if runtime.GOOS != "windows" { 408 // Only an issue on Windows. 409 return 410 } 411 data, _ := exec.Command("CheckNetIsolation", "LoopbackExempt", "-s", `-n="microsoft.minecraftuwp_8wekyb3d8bbwe"`).CombinedOutput() 412 if bytes.Contains(data, []byte("microsoft.minecraftuwp_8wekyb3d8bbwe")) { 413 return 414 } 415 const loopbackExemptCmd = `CheckNetIsolation LoopbackExempt -a -n="Microsoft.MinecraftUWP_8wekyb3d8bbwe"` 416 srv.conf.Log.Infof("You are currently unable to join the server on this machine. Run %v in an admin PowerShell session to resolve.\n", loopbackExemptCmd) 417 } 418 419 // handleSessionClose handles the closing of a session. It removes the player 420 // of the session from the server. 421 func (srv *Server) handleSessionClose(c session.Controllable) { 422 srv.pmu.Lock() 423 p, ok := srv.p[c.UUID()] 424 delete(srv.p, c.UUID()) 425 srv.pmu.Unlock() 426 if !ok { 427 // When a player disconnects immediately after a session is started, it might not be added to the players map 428 // yet. This is expected, but we need to be careful not to crash when this happens. 429 return 430 } 431 432 if err := srv.conf.PlayerProvider.Save(p.UUID(), p.Data()); err != nil { 433 srv.conf.Log.Errorf("Error while saving data: %v", err) 434 } 435 srv.pwg.Done() 436 } 437 438 // createPlayer creates a new player instance using the UUID and connection 439 // passed. 440 func (srv *Server) createPlayer(id uuid.UUID, conn session.Conn, data *player.Data) *session.Session { 441 w, gm, pos := srv.world, srv.world.DefaultGameMode(), srv.world.Spawn().Vec3Middle() 442 if data != nil { 443 w, gm, pos = data.World, data.GameMode, data.Position 444 } 445 s := session.New(conn, srv.conf.MaxChunkRadius, srv.conf.Log, srv.conf.JoinMessage, srv.conf.QuitMessage) 446 p := player.NewWithSession(conn.IdentityData().DisplayName, conn.IdentityData().XUID, id, srv.parseSkin(conn.ClientData()), s, pos, data) 447 448 s.Spawn(p, pos, w, gm, srv.handleSessionClose) 449 srv.pwg.Add(1) 450 return s 451 } 452 453 // createWorld loads a world of the server with a specific dimension, ending 454 // the program if the world could not be loaded. The layers passed are used to 455 // create a generator.Flat that is used as generator for the world. 456 func (srv *Server) createWorld(dim world.Dimension, nether, end **world.World) *world.World { 457 logger := srv.conf.Log 458 if v, ok := logger.(interface { 459 WithField(key string, field any) *logrus.Entry 460 }); ok { 461 // Add a dimension field to be able to distinguish between the different 462 // dimensions in the log. Dimensions implement fmt.Stringer so we can 463 // just fmt.Sprint them for a readable name. 464 logger = v.WithField("dimension", strings.ToLower(fmt.Sprint(dim))) 465 } 466 logger.Debugf("Loading world...") 467 468 conf := world.Config{ 469 Log: logger, 470 Dim: dim, 471 Provider: srv.conf.WorldProvider, 472 Generator: srv.conf.Generator(dim), 473 RandomTickSpeed: srv.conf.RandomTickSpeed, 474 ReadOnly: srv.conf.ReadOnlyWorld, 475 Entities: srv.conf.Entities, 476 PortalDestination: func(dim world.Dimension) *world.World { 477 if dim == world.Nether { 478 return *nether 479 } else if dim == world.End { 480 return *end 481 } 482 return nil 483 }, 484 } 485 w := conf.New() 486 logger.Infof(`Opened world "%v".`, w.Name()) 487 return w 488 } 489 490 // parseSkin parses a skin from the login.ClientData and returns it. 491 func (srv *Server) parseSkin(data login.ClientData) skin.Skin { 492 // Gophertunnel guarantees the following values are valid data and are of 493 // the correct size. 494 skinResourcePatch, _ := base64.StdEncoding.DecodeString(data.SkinResourcePatch) 495 496 playerSkin := skin.New(data.SkinImageWidth, data.SkinImageHeight) 497 playerSkin.Persona = data.PersonaSkin 498 playerSkin.Pix, _ = base64.StdEncoding.DecodeString(data.SkinData) 499 playerSkin.Model, _ = base64.StdEncoding.DecodeString(data.SkinGeometry) 500 playerSkin.ModelConfig, _ = skin.DecodeModelConfig(skinResourcePatch) 501 playerSkin.PlayFabID = data.PlayFabID 502 503 playerSkin.Cape = skin.NewCape(data.CapeImageWidth, data.CapeImageHeight) 504 playerSkin.Cape.Pix, _ = base64.StdEncoding.DecodeString(data.CapeData) 505 506 for _, animation := range data.AnimatedImageData { 507 var t skin.AnimationType 508 switch animation.Type { 509 case protocol.SkinAnimationHead: 510 t = skin.AnimationHead 511 case protocol.SkinAnimationBody32x32: 512 t = skin.AnimationBody32x32 513 case protocol.SkinAnimationBody128x128: 514 t = skin.AnimationBody128x128 515 } 516 517 anim := skin.NewAnimation(animation.ImageWidth, animation.ImageHeight, animation.AnimationExpression, t) 518 anim.FrameCount = int(animation.Frames) 519 anim.Pix, _ = base64.StdEncoding.DecodeString(animation.Image) 520 521 playerSkin.Animations = append(playerSkin.Animations, anim) 522 } 523 524 return playerSkin 525 } 526 527 // registerTargetFunc registers a cmd.TargetFunc to be able to get all players 528 // connected and all entities in the server's world. 529 func (srv *Server) registerTargetFunc() { 530 cmd.AddTargetFunc(func(src cmd.Source) (entities []cmd.Target, players []cmd.NamedTarget) { 531 return sliceutil.Convert[cmd.Target](src.World().Entities()), sliceutil.Convert[cmd.NamedTarget](srv.Players()) 532 }) 533 } 534 535 // vec64To32 converts a mgl64.Vec3 to a mgl32.Vec3. 536 func vec64To32(vec3 mgl64.Vec3) mgl32.Vec3 { 537 return mgl32.Vec3{float32(vec3[0]), float32(vec3[1]), float32(vec3[2])} 538 } 539 540 // itemEntries loads a list of all custom item entries of the server, ready to 541 // be sent in the StartGame packet. 542 func (srv *Server) itemEntries() []protocol.ItemEntry { 543 entries := make([]protocol.ItemEntry, 0, len(itemRuntimeIDs)) 544 545 for name, rid := range itemRuntimeIDs { 546 entries = append(entries, protocol.ItemEntry{ 547 Name: name, 548 RuntimeID: int16(rid), 549 }) 550 } 551 for _, it := range world.CustomItems() { 552 name, _ := it.EncodeItem() 553 rid, _, _ := world.ItemRuntimeID(it) 554 entries = append(entries, protocol.ItemEntry{ 555 Name: name, 556 ComponentBased: true, 557 RuntimeID: int16(rid), 558 }) 559 } 560 return entries 561 } 562 563 // ashyBiome represents a biome that has any form of ash. 564 type ashyBiome interface { 565 // Ash returns the ash and white ash of the biome. 566 Ash() (ash float64, whiteAsh float64) 567 } 568 569 // sporingBiome represents a biome that has blue or red spores. 570 type sporingBiome interface { 571 // Spores returns the blue and red spores of the biome. 572 Spores() (blueSpores float64, redSpores float64) 573 } 574 575 // biomes builds a mapping of all biome definitions of the server, ready to be set in the biomes field of the server 576 // listener. 577 func biomes() map[string]any { 578 definitions := make(map[string]any) 579 for _, b := range world.Biomes() { 580 definition := map[string]any{ 581 "name_hash": b.String(), // This isn't actually a hash despite what the field name may suggest. 582 "temperature": float32(b.Temperature()), 583 "downfall": float32(b.Rainfall()), 584 "rain": b.Rainfall() > 0, 585 } 586 if a, ok := b.(ashyBiome); ok { 587 ash, whiteAsh := a.Ash() 588 definition["ash"], definition["white_ash"] = float32(ash), float32(whiteAsh) 589 } 590 if s, ok := b.(sporingBiome); ok { 591 blueSpores, redSpores := s.Spores() 592 definition["blue_spores"], definition["red_spores"] = float32(blueSpores), float32(redSpores) 593 } 594 definitions[b.String()] = definition 595 } 596 return definitions 597 } 598 599 var ( 600 //go:embed world/item_runtime_ids.nbt 601 itemRuntimeIDData []byte 602 itemRuntimeIDs = map[string]int32{} 603 ) 604 605 // init reads all item entries from the resource JSON, and sets the according 606 // values in the runtime ID maps. init also seeds the global `rand` with the 607 // current time. 608 func init() { 609 _ = nbt.Unmarshal(itemRuntimeIDData, &itemRuntimeIDs) 610 }