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

     1  package session
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"github.com/df-mc/atomic"
     8  	"github.com/df-mc/dragonfly/server/block"
     9  	"github.com/df-mc/dragonfly/server/block/cube"
    10  	"github.com/df-mc/dragonfly/server/internal/sliceutil"
    11  	"github.com/df-mc/dragonfly/server/item"
    12  	"github.com/df-mc/dragonfly/server/item/inventory"
    13  	"github.com/df-mc/dragonfly/server/item/recipe"
    14  	"github.com/df-mc/dragonfly/server/player/chat"
    15  	"github.com/df-mc/dragonfly/server/player/form"
    16  	"github.com/df-mc/dragonfly/server/world"
    17  	"github.com/go-gl/mathgl/mgl64"
    18  	"github.com/sandertv/gophertunnel/minecraft"
    19  	"github.com/sandertv/gophertunnel/minecraft/nbt"
    20  	"github.com/sandertv/gophertunnel/minecraft/protocol"
    21  	"github.com/sandertv/gophertunnel/minecraft/protocol/login"
    22  	"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
    23  	"github.com/sandertv/gophertunnel/minecraft/text"
    24  	"io"
    25  	"net"
    26  	"sync"
    27  	"time"
    28  )
    29  
    30  // Session handles incoming packets from connections and sends outgoing packets by providing a thin layer
    31  // of abstraction over direct packets. A Session basically 'controls' an entity.
    32  type Session struct {
    33  	log            Logger
    34  	once, connOnce sync.Once
    35  
    36  	c        Controllable
    37  	conn     Conn
    38  	handlers map[uint32]packetHandler
    39  
    40  	// onStop is called when the session is stopped. The controllable passed is the controllable that the
    41  	// session controls.
    42  	onStop func(controllable Controllable)
    43  
    44  	currentScoreboard atomic.Value[string]
    45  	currentLines      atomic.Value[[]string]
    46  
    47  	chunkLoader                 *world.Loader
    48  	chunkRadius, maxChunkRadius int32
    49  
    50  	teleportPos atomic.Value[*mgl64.Vec3]
    51  
    52  	entityMutex sync.RWMutex
    53  	// currentEntityRuntimeID holds the runtime ID assigned to the last entity. It is incremented for every
    54  	// entity spawned to the session.
    55  	currentEntityRuntimeID uint64
    56  	// entityRuntimeIDs holds a list of all runtime IDs of entities spawned to the session.
    57  	entityRuntimeIDs map[world.Entity]uint64
    58  	entities         map[uint64]world.Entity
    59  	hiddenEntities   map[world.Entity]struct{}
    60  
    61  	// heldSlot is the slot in the inventory that the controllable is holding.
    62  	heldSlot                     *atomic.Uint32
    63  	inv, offHand, enderChest, ui *inventory.Inventory
    64  	armour                       *inventory.Armour
    65  
    66  	breakingPos cube.Pos
    67  
    68  	inTransaction, containerOpened atomic.Bool
    69  	openedWindowID                 atomic.Uint32
    70  	openedContainerID              atomic.Uint32
    71  	openedWindow                   atomic.Value[*inventory.Inventory]
    72  	openedPos                      atomic.Value[cube.Pos]
    73  	swingingArm                    atomic.Bool
    74  
    75  	recipes map[uint32]recipe.Recipe
    76  
    77  	blobMu                sync.Mutex
    78  	blobs                 map[uint64][]byte
    79  	openChunkTransactions []map[uint64]struct{}
    80  	invOpened             bool
    81  
    82  	joinMessage, quitMessage string
    83  
    84  	closeBackground chan struct{}
    85  }
    86  
    87  // Conn represents a connection that packets are read from and written to by a Session. In addition, it holds some
    88  // information on the identity of the Session.
    89  type Conn interface {
    90  	io.Closer
    91  	// IdentityData returns the login.IdentityData of a Conn. It contains the UUID, XUID and username of the connection.
    92  	IdentityData() login.IdentityData
    93  	// ClientData returns the login.ClientData of a Conn. This includes less sensitive data of the player like its skin,
    94  	// language code and other non-essential information.
    95  	ClientData() login.ClientData
    96  	// ClientCacheEnabled specifies if the Conn has the client cache, used for caching chunks client-side, enabled or
    97  	// not. Some platforms, like the Nintendo Switch, have this disabled at all times.
    98  	ClientCacheEnabled() bool
    99  	// ChunkRadius returns the chunk radius as requested by the client at the other end of the Conn.
   100  	ChunkRadius() int
   101  	// Latency returns the current latency measured over the Conn.
   102  	Latency() time.Duration
   103  	// Flush flushes the packets buffered by the Conn, sending all of them out immediately.
   104  	Flush() error
   105  	// RemoteAddr returns the remote network address.
   106  	RemoteAddr() net.Addr
   107  	// ReadPacket reads a packet.Packet from the Conn. An error is returned if a deadline was set that was
   108  	// exceeded or if the Conn was closed while awaiting a packet.
   109  	ReadPacket() (pk packet.Packet, err error)
   110  	// WritePacket writes a packet.Packet to the Conn. An error is returned if the Conn was closed before sending the
   111  	// packet.
   112  	WritePacket(pk packet.Packet) error
   113  	// StartGameContext starts the game for the Conn with a context to cancel it.
   114  	StartGameContext(ctx context.Context, data minecraft.GameData) error
   115  }
   116  
   117  // Logger is used to write debug messages to. These messages are sent whenever handling of a packet of a client fails.
   118  type Logger interface {
   119  	Errorf(format string, a ...any)
   120  	Debugf(format string, a ...any)
   121  }
   122  
   123  // Nop represents a no-operation session. It does not do anything when sending a packet to it.
   124  var Nop = &Session{}
   125  
   126  // session is a slice of all open sessions. It is protected by the sessionMu, which must be locked whenever
   127  // accessing the value.
   128  var sessions []*Session
   129  var sessionMu sync.Mutex
   130  
   131  // selfEntityRuntimeID is the entity runtime (or unique) ID of the controllable that the session holds.
   132  const selfEntityRuntimeID = 1
   133  
   134  // errSelfRuntimeID is an error returned during packet handling for fields that refer to the player itself and
   135  // must therefore always be 1.
   136  var errSelfRuntimeID = errors.New("invalid entity runtime ID: runtime ID for self must always be 1")
   137  
   138  // New returns a new session using a controllable entity. The session will control this entity using the
   139  // packets that it receives.
   140  // New takes the connection from which to accept packets. It will start handling these packets after a call to
   141  // Session.Spawn().
   142  func New(conn Conn, maxChunkRadius int, log Logger, joinMessage, quitMessage string) *Session {
   143  	r := conn.ChunkRadius()
   144  	if r > maxChunkRadius {
   145  		r = maxChunkRadius
   146  		_ = conn.WritePacket(&packet.ChunkRadiusUpdated{ChunkRadius: int32(r)})
   147  	}
   148  
   149  	s := &Session{}
   150  	*s = Session{
   151  		openChunkTransactions:  make([]map[uint64]struct{}, 0, 8),
   152  		closeBackground:        make(chan struct{}),
   153  		ui:                     inventory.New(53, s.handleInterfaceUpdate),
   154  		handlers:               map[uint32]packetHandler{},
   155  		entityRuntimeIDs:       map[world.Entity]uint64{},
   156  		entities:               map[uint64]world.Entity{},
   157  		hiddenEntities:         map[world.Entity]struct{}{},
   158  		blobs:                  map[uint64][]byte{},
   159  		chunkRadius:            int32(r),
   160  		maxChunkRadius:         int32(maxChunkRadius),
   161  		conn:                   conn,
   162  		log:                    log,
   163  		currentEntityRuntimeID: 1,
   164  		heldSlot:               atomic.NewUint32(0),
   165  		joinMessage:            joinMessage,
   166  		quitMessage:            quitMessage,
   167  		openedWindow:           *atomic.NewValue(inventory.New(1, nil)),
   168  	}
   169  
   170  	s.registerHandlers()
   171  	return s
   172  }
   173  
   174  // Spawn makes the Controllable passed spawn in the world.World.
   175  // The function passed will be called when the session stops running.
   176  func (s *Session) Spawn(c Controllable, pos mgl64.Vec3, w *world.World, gm world.GameMode, onStop func(controllable Controllable)) {
   177  	s.onStop = onStop
   178  	s.c = c
   179  	s.recipes = make(map[uint32]recipe.Recipe)
   180  	s.entityRuntimeIDs[c] = selfEntityRuntimeID
   181  	s.entities[selfEntityRuntimeID] = c
   182  
   183  	s.chunkLoader = world.NewLoader(int(s.chunkRadius), w, s)
   184  	s.chunkLoader.Move(pos)
   185  	s.writePacket(&packet.NetworkChunkPublisherUpdate{
   186  		Position: protocol.BlockPos{int32(pos[0]), int32(pos[1]), int32(pos[2])},
   187  		Radius:   uint32(s.chunkRadius) << 4,
   188  	})
   189  
   190  	s.sendAvailableEntities(w)
   191  
   192  	s.initPlayerList()
   193  
   194  	world_add(c, w)
   195  	s.c.SetGameMode(gm)
   196  	s.SendSpeed(0.1)
   197  	for _, e := range s.c.Effects() {
   198  		s.SendEffect(e)
   199  	}
   200  
   201  	chat.Global.Subscribe(c)
   202  	if s.joinMessage != "" {
   203  		_, _ = fmt.Fprintln(chat.Global, text.Colourf("<yellow>%v</yellow>", fmt.Sprintf(s.joinMessage, s.conn.IdentityData().DisplayName)))
   204  	}
   205  
   206  	s.sendInv(s.inv, protocol.WindowIDInventory)
   207  	s.sendInv(s.ui, protocol.WindowIDUI)
   208  	s.sendInv(s.offHand, protocol.WindowIDOffHand)
   209  	s.sendInv(s.armour.Inventory(), protocol.WindowIDArmour)
   210  	s.writePacket(&packet.CreativeContent{Items: creativeItems()})
   211  	s.sendRecipes()
   212  }
   213  
   214  // Start makes the session start handling incoming packets from the client.
   215  func (s *Session) Start() {
   216  	s.c.World().AddEntity(s.c)
   217  	go s.handlePackets()
   218  }
   219  
   220  // Controllable returns the Controllable entity that the Session controls.
   221  func (s *Session) Controllable() Controllable {
   222  	return s.c
   223  }
   224  
   225  // Close closes the session, which in turn closes the controllable and the connection that the session
   226  // manages. Close ensures the method only runs code on the first call.
   227  func (s *Session) Close() error {
   228  	s.once.Do(s.close)
   229  	return nil
   230  }
   231  
   232  // close closes the session, which in turn closes the controllable and the connection that the session
   233  // manages.
   234  func (s *Session) close() {
   235  	_ = s.c.Close()
   236  
   237  	// Move UI inventory items to the main inventory.
   238  	for _, it := range s.ui.Items() {
   239  		if _, err := s.inv.AddItem(it); err != nil {
   240  			// We couldn't add the item to the main inventory (probably because it was full), so we drop it instead.
   241  			s.c.Drop(it)
   242  		}
   243  	}
   244  
   245  	s.onStop(s.c)
   246  
   247  	// Clear the inventories so that they no longer hold references to the connection.
   248  	_ = s.inv.Close()
   249  	_ = s.offHand.Close()
   250  	_ = s.armour.Close()
   251  
   252  	s.closeCurrentContainer()
   253  	_ = s.chunkLoader.Close()
   254  	s.c.World().RemoveEntity(s.c)
   255  
   256  	// This should always be called last due to the timing of the removal of entity runtime IDs.
   257  	s.closePlayerList()
   258  	s.entityMutex.Lock()
   259  	s.entityRuntimeIDs, s.entities = map[world.Entity]uint64{}, map[uint64]world.Entity{}
   260  	s.entityMutex.Unlock()
   261  
   262  	if s.quitMessage != "" {
   263  		_, _ = fmt.Fprintln(chat.Global, text.Colourf("<yellow>%v</yellow>", fmt.Sprintf(s.quitMessage, s.conn.IdentityData().DisplayName)))
   264  	}
   265  	chat.Global.Unsubscribe(s.c)
   266  }
   267  
   268  // CloseConnection closes the underlying connection of the session so that the session ends up being closed
   269  // eventually.
   270  func (s *Session) CloseConnection() {
   271  	s.connOnce.Do(func() {
   272  		_ = s.conn.Close()
   273  		s.closeBackground <- struct{}{}
   274  	})
   275  }
   276  
   277  // Addr returns the net.Addr of the client.
   278  func (s *Session) Addr() net.Addr {
   279  	return s.conn.RemoteAddr()
   280  }
   281  
   282  // Latency returns the latency of the connection.
   283  func (s *Session) Latency() time.Duration {
   284  	return s.conn.Latency()
   285  }
   286  
   287  // ClientData returns the login.ClientData of the underlying *minecraft.Conn.
   288  func (s *Session) ClientData() login.ClientData {
   289  	return s.conn.ClientData()
   290  }
   291  
   292  // handlePackets continuously handles incoming packets from the connection. It processes them accordingly.
   293  // Once the connection is closed, handlePackets will return.
   294  func (s *Session) handlePackets() {
   295  	go s.background()
   296  
   297  	defer func() {
   298  		// If this function ends up panicking, we don't want to call s.Close() as it may cause the entire
   299  		// server to freeze without printing the actual panic message.
   300  		// Instead, we check if there is a panic to recover, and just propagate the panic if this does happen
   301  		// to be the case.
   302  		if err := recover(); err != nil {
   303  			panic(err)
   304  		}
   305  		_ = s.Close()
   306  	}()
   307  	for {
   308  		pk, err := s.conn.ReadPacket()
   309  		if err != nil {
   310  			return
   311  		}
   312  		if err := s.handlePacket(pk); err != nil {
   313  			// An error occurred during the handling of a packet. Print the error and stop handling any more
   314  			// packets.
   315  			s.log.Debugf("failed processing packet from %v (%v): %v\n", s.conn.RemoteAddr(), s.c.Name(), err)
   316  			return
   317  		}
   318  	}
   319  }
   320  
   321  // background performs background tasks of the Session. This includes chunk sending and automatic command updating.
   322  // background returns when the Session's connection is closed using CloseConnection.
   323  func (s *Session) background() {
   324  	var (
   325  		t                 = time.NewTicker(time.Second / 20)
   326  		r                 = s.sendAvailableCommands()
   327  		enums, enumValues = s.enums()
   328  		ok                bool
   329  		i                 int
   330  	)
   331  	defer t.Stop()
   332  
   333  	for {
   334  		select {
   335  		case <-t.C:
   336  			s.sendChunks()
   337  
   338  			if i++; i%20 == 0 {
   339  				// Enum resending happens relatively often and frequent updates are more important than with full
   340  				// command changes. Those are generally only related to permission changes, which doesn't happen often.
   341  				s.resendEnums(enums, enumValues)
   342  			}
   343  			if i%100 == 0 {
   344  				// Try to resend commands only every 5 seconds.
   345  				if r, ok = s.resendCommands(r); ok {
   346  					enums, enumValues = s.enums()
   347  				}
   348  			}
   349  		case <-s.closeBackground:
   350  			return
   351  		}
   352  	}
   353  }
   354  
   355  // sendChunks sends the next up to 4 chunks to the connection. What chunks are loaded depends on the connection of
   356  // the chunk loader and the chunks that were previously loaded.
   357  func (s *Session) sendChunks() {
   358  	pos := s.c.Position()
   359  	s.chunkLoader.Move(pos)
   360  	s.writePacket(&packet.NetworkChunkPublisherUpdate{
   361  		Position: protocol.BlockPos{int32(pos[0]), int32(pos[1]), int32(pos[2])},
   362  		Radius:   uint32(s.chunkRadius) << 4,
   363  	})
   364  
   365  	const maxChunkTransactions = 8
   366  
   367  	if w := s.c.World(); s.chunkLoader.World() != w && w != nil {
   368  		s.handleWorldSwitch(w)
   369  	}
   370  
   371  	s.blobMu.Lock()
   372  	toLoad := maxChunkTransactions - len(s.openChunkTransactions)
   373  	s.blobMu.Unlock()
   374  	if toLoad > 4 {
   375  		toLoad = 4
   376  	}
   377  	s.chunkLoader.Load(toLoad)
   378  }
   379  
   380  // handleWorldSwitch handles the player of the Session switching worlds.
   381  func (s *Session) handleWorldSwitch(w *world.World) {
   382  	if s.conn.ClientCacheEnabled() {
   383  		s.blobMu.Lock()
   384  		// Force out all blobs before changing worlds. This ensures no outdated chunk loading in the new world.
   385  		resp := &packet.ClientCacheMissResponse{Blobs: make([]protocol.CacheBlob, 0, len(s.blobs))}
   386  		for h, blob := range s.blobs {
   387  			resp.Blobs = append(resp.Blobs, protocol.CacheBlob{Hash: h, Payload: blob})
   388  		}
   389  		s.writePacket(resp)
   390  
   391  		s.blobs = map[uint64][]byte{}
   392  		s.openChunkTransactions = nil
   393  		s.blobMu.Unlock()
   394  	}
   395  
   396  	dim, _ := world.DimensionID(w.Dimension())
   397  	same := w.Dimension() == s.chunkLoader.World().Dimension()
   398  	if !same {
   399  		s.changeDimension(int32(dim), false)
   400  	}
   401  	s.ViewEntityTeleport(s.c, s.c.Position())
   402  	s.chunkLoader.ChangeWorld(w)
   403  }
   404  
   405  // changeDimension changes the dimension of the client. If silent is set to true, the portal noise will be stopped
   406  // immediately.
   407  func (s *Session) changeDimension(dim int32, silent bool) {
   408  	s.writePacket(&packet.ChangeDimension{Dimension: dim, Position: vec64To32(s.c.Position().Add(entityOffset(s.c)))})
   409  	s.writePacket(&packet.StopSound{StopAll: silent})
   410  	s.writePacket(&packet.PlayStatus{Status: packet.PlayStatusPlayerSpawn})
   411  
   412  	// As of v1.19.50, the dimension ack that is meant to be sent by the client is now sent by the server. The client
   413  	// still sends the ack, but after the server has sent it. Thanks to Mojang for another groundbreaking change.
   414  	s.writePacket(&packet.PlayerAction{
   415  		EntityRuntimeID: selfEntityRuntimeID,
   416  		ActionType:      protocol.PlayerActionDimensionChangeDone,
   417  	})
   418  }
   419  
   420  // handlePacket handles an incoming packet, processing it accordingly. If the packet had invalid data or was
   421  // otherwise not valid in its context, an error is returned.
   422  func (s *Session) handlePacket(pk packet.Packet) error {
   423  	handler, ok := s.handlers[pk.ID()]
   424  	if !ok {
   425  		s.log.Debugf("unhandled packet %T%v from %v\n", pk, fmt.Sprintf("%+v", pk)[1:], s.conn.RemoteAddr())
   426  		return nil
   427  	}
   428  	if handler == nil {
   429  		// A nil handler means it was explicitly unhandled.
   430  		return nil
   431  	}
   432  	if err := handler.Handle(pk, s); err != nil {
   433  		return fmt.Errorf("%T: %w", pk, err)
   434  	}
   435  	return nil
   436  }
   437  
   438  // registerHandlers registers all packet handlers found in the packetHandler package.
   439  func (s *Session) registerHandlers() {
   440  	s.handlers = map[uint32]packetHandler{
   441  		packet.IDActorEvent:            nil,
   442  		packet.IDAdventureSettings:     nil, // Deprecated, the client still sends this though.
   443  		packet.IDAnimate:               nil,
   444  		packet.IDAnvilDamage:           nil,
   445  		packet.IDBlockActorData:        &BlockActorDataHandler{},
   446  		packet.IDBlockPickRequest:      &BlockPickRequestHandler{},
   447  		packet.IDBookEdit:              &BookEditHandler{},
   448  		packet.IDBossEvent:             nil,
   449  		packet.IDClientCacheBlobStatus: &ClientCacheBlobStatusHandler{},
   450  		packet.IDCommandRequest:        &CommandRequestHandler{},
   451  		packet.IDContainerClose:        &ContainerCloseHandler{},
   452  		packet.IDEmote:                 &EmoteHandler{},
   453  		packet.IDEmoteList:             nil,
   454  		packet.IDFilterText:            nil,
   455  		packet.IDInteract:              &InteractHandler{},
   456  		packet.IDInventoryTransaction:  &InventoryTransactionHandler{},
   457  		packet.IDItemFrameDropItem:     nil,
   458  		packet.IDItemStackRequest:      &ItemStackRequestHandler{changes: map[byte]map[byte]changeInfo{}, responseChanges: map[int32]map[*inventory.Inventory]map[byte]responseChange{}},
   459  		packet.IDLecternUpdate:         &LecternUpdateHandler{},
   460  		packet.IDMobEquipment:          &MobEquipmentHandler{},
   461  		packet.IDModalFormResponse:     &ModalFormResponseHandler{forms: make(map[uint32]form.Form)},
   462  		packet.IDMovePlayer:            nil,
   463  		packet.IDPlayerAction:          &PlayerActionHandler{},
   464  		packet.IDPlayerAuthInput:       &PlayerAuthInputHandler{},
   465  		packet.IDPlayerSkin:            &PlayerSkinHandler{},
   466  		packet.IDRequestAbility:        &RequestAbilityHandler{},
   467  		packet.IDRequestChunkRadius:    &RequestChunkRadiusHandler{},
   468  		packet.IDRespawn:               &RespawnHandler{},
   469  		packet.IDSubChunkRequest:       &SubChunkRequestHandler{},
   470  		packet.IDText:                  &TextHandler{},
   471  		packet.IDTickSync:              nil,
   472  	}
   473  }
   474  
   475  // handleInterfaceUpdate handles an update to the UI inventory, used for updating enchantment options and possibly more
   476  // in the future.
   477  func (s *Session) handleInterfaceUpdate(slot int, _, item item.Stack) {
   478  	if slot == enchantingInputSlot && s.containerOpened.Load() {
   479  		pos := s.openedPos.Load()
   480  		b := s.c.World().Block(pos)
   481  		if _, enchanting := b.(block.EnchantingTable); enchanting {
   482  			s.sendEnchantmentOptions(s.c.World(), pos, item)
   483  		}
   484  	}
   485  }
   486  
   487  // writePacket writes a packet to the session's connection if it is not Nop.
   488  func (s *Session) writePacket(pk packet.Packet) {
   489  	if s == Nop {
   490  		return
   491  	}
   492  	_ = s.conn.WritePacket(pk)
   493  }
   494  
   495  // initPlayerList initialises the player list of the session and sends the session itself to all other
   496  // sessions currently open.
   497  func (s *Session) initPlayerList() {
   498  	sessionMu.Lock()
   499  	sessions = append(sessions, s)
   500  	for _, session := range sessions {
   501  		// AddStack the player of the session to all sessions currently open, and add the players of all sessions
   502  		// currently open to the player list of the new session.
   503  		session.addToPlayerList(s)
   504  		if s != session {
   505  			s.addToPlayerList(session)
   506  		}
   507  	}
   508  	sessionMu.Unlock()
   509  }
   510  
   511  // closePlayerList closes the player list of the session and removes the session from the player list of all
   512  // other sessions.
   513  func (s *Session) closePlayerList() {
   514  	sessionMu.Lock()
   515  	for _, session := range sessions {
   516  		// Remove the player of the session from the player list of all other sessions.
   517  		session.removeFromPlayerList(s)
   518  	}
   519  	sessions = sliceutil.DeleteVal(sessions, s)
   520  	sessionMu.Unlock()
   521  }
   522  
   523  // actorIdentifier represents the structure of an actor identifier sent over the network.
   524  type actorIdentifier struct {
   525  	// ID is a unique namespaced identifier for the entity.
   526  	ID string `nbt:"id"`
   527  }
   528  
   529  // sendAvailableEntities sends all registered entities to the player.
   530  func (s *Session) sendAvailableEntities(w *world.World) {
   531  	var identifiers []actorIdentifier
   532  	for _, t := range w.EntityRegistry().Types() {
   533  		identifiers = append(identifiers, actorIdentifier{ID: t.EncodeEntity()})
   534  	}
   535  	serializedEntityData, err := nbt.Marshal(map[string]any{"idlist": identifiers})
   536  	if err != nil {
   537  		panic("should never happen")
   538  	}
   539  	s.writePacket(&packet.AvailableActorIdentifiers{SerialisedEntityIdentifiers: serializedEntityData})
   540  }