diff --git a/pkg/edition/java/proxy/events.go b/pkg/edition/java/proxy/events.go index 9422a277..dbf77166 100644 --- a/pkg/edition/java/proxy/events.go +++ b/pkg/edition/java/proxy/events.go @@ -408,6 +408,69 @@ func (e *PostLoginEvent) Player() Player { return e.player } +// +// +// +// +// +// +// +// + +// PlayerConfigurationStartEvent is fired when the backend starts the configuration stage +// +// This is the earliest proxy-side hook to send config-state packets (e.g. CodeOfConductPacket) +// before configuration finishes. +type PlayerConfigurationStartEvent struct { + player Player + server RegisteredServer + codeOfConduct []byte +} + +// Player returns the player entering configuration stage. +func (e *PlayerConfigurationStartEvent) Player() Player { + return e.player +} + +// Server returns the backend server the player is currently configuring against. +func (e *PlayerConfigurationStartEvent) Server() RegisteredServer { + return e.server +} + +// SendCodeOfConduct asks the proxy to send CodeOfConductPacket to the client +// and hold configuration completion until the player accepts. +func (e *PlayerConfigurationStartEvent) SendCodeOfConduct(payload []byte) { + e.codeOfConduct = payload +} + +// ShouldSendCodeOfConduct reports whether sending code-of-conduct packet was requested. +func (e *PlayerConfigurationStartEvent) ShouldSendCodeOfConduct() bool { + return e.codeOfConduct != nil +} + +// CodeOfConductPayload returns the payload previously provided via SendCodeOfConduct. +func (e *PlayerConfigurationStartEvent) CodeOfConductPayload() []byte { + return e.codeOfConduct +} + +// +// +// +// +// +// + +// CodeOfConductAcceptEvent is fired when a player accepts the code of conduct +// in config state (Minecraft 1.21.9+). +type CodeOfConductAcceptEvent struct { + player Player +} + +// Player returns the player that accepted the code of conduct. +func (e *CodeOfConductAcceptEvent) Player() Player { + return e.player +} + // // // diff --git a/pkg/edition/java/proxy/session_backend_config.go b/pkg/edition/java/proxy/session_backend_config.go index 610675cb..ce6e0c6f 100644 --- a/pkg/edition/java/proxy/session_backend_config.go +++ b/pkg/edition/java/proxy/session_backend_config.go @@ -26,6 +26,7 @@ type backendConfigSessionHandler struct { serverConn *serverConnection requestCtx *connRequestCxt state backendConfigSessionState + codeOfConductHold bool resourcePackToApply *ResourcePackInfo log logr.Logger @@ -66,11 +67,22 @@ func (b *backendConfigSessionHandler) HandlePacket(pc *proto.PacketContext) { if !b.shouldHandle() { return } + if b.codeOfConductHold { + switch p := pc.Packet.(type) { + case *packet.KeepAlive: + b.handleKeepAlive(p) + case *packet.Disconnect: + b.handleDisconnect(p) + } + return + } switch p := pc.Packet.(type) { case *packet.KeepAlive: b.handleKeepAlive(p) case *config.StartUpdate: b.forwardToServer(pc, nil) + case *config.CodeOfConductPacket: + b.handleCodeOfConductPacket(pc) case *packet.ResourcePackRequest: b.handleResourcePackRequest(p) case *packet.RemoveResourcePack: @@ -84,17 +96,7 @@ func (b *backendConfigSessionHandler) HandlePacket(pc *proto.PacketContext) { case *plugin.Message: b.handlePluginMessage(pc, p) case *packet.Disconnect: - b.serverConn.disconnect() - // If the player receives a DisconnectPacket without a connection to a server in progress, - // it means that the backend server has kicked the player during reconfiguration - if b.serverConn.player.connectionInFlight() != nil { - result := disconnectResultForPacket(b.log.V(1), p, - b.serverConn.player.Protocol(), b.serverConn.server, true, - ) - b.requestCtx.result(result, nil) - } else { - b.serverConn.player.handleDisconnect(b.serverConn.server, p, true) - } + b.handleDisconnect(p) case *packet.Transfer: b.handleTransfer(p) case *cookie.CookieStore: @@ -123,6 +125,16 @@ func (b *backendConfigSessionHandler) Activated() { b.resourcePackToApply = player.resourcePackHandler.FirstAppliedPack() player.resourcePackHandler.ClearAppliedResourcePacks() } + + // Emit Configuration event and initiate CoC + e := &PlayerConfigurationStartEvent{ + player: b.serverConn.player, + server: b.serverConn.server, + } + b.proxy().event.Fire(e) + if player.Protocol().Greater(version.Minecraft_1_21_9) && e.ShouldSendCodeOfConduct() { + b.initiateCodeOfConduct(e.CodeOfConductPayload()) + } } // Disconnected is called when the session handler is disconnected. @@ -211,6 +223,48 @@ func (b *backendConfigSessionHandler) handleFinishedUpdate(p *config.FinishedUpd }) } +func (b *backendConfigSessionHandler) handleDisconnect(p *packet.Disconnect) { + b.serverConn.disconnect() + // If the player receives a DisconnectPacket without a connection to a server in progress, + // it means that the backend server has kicked the player during reconfiguration + if b.serverConn.player.connectionInFlight() != nil { + result := disconnectResultForPacket(b.log.V(1), p, + b.serverConn.player.Protocol(), b.serverConn.server, true, + ) + b.requestCtx.result(result, nil) + } else { + b.serverConn.player.handleDisconnect(b.serverConn.server, p, true) + } +} + +func (b *backendConfigSessionHandler) handleCodeOfConductPacket(pc *proto.PacketContext) { + b.requireCodeOfConduct() + b.forwardToPlayer(pc, nil) +} + +func (b *backendConfigSessionHandler) initiateCodeOfConduct(payload []byte) { + b.requireCodeOfConduct() + if err := b.serverConn.player.WritePacket(&config.CodeOfConductPacket{Data: payload}); err != nil { + b.log.V(1).Error(err, "error writing CodeOfConduct packet") + } +} + +func (b *backendConfigSessionHandler) requireCodeOfConduct() { + if b.codeOfConductHold { + return + } + b.codeOfConductHold = true + b.serverConn.connection.SetAutoReading(false) +} + +func (b *backendConfigSessionHandler) releaseCodeOfConductHold() { + if !b.codeOfConductHold { + return + } + b.codeOfConductHold = false + b.serverConn.connection.SetAutoReading(true) +} + func (b *backendConfigSessionHandler) handleTransfer(p *packet.Transfer) { handleTransfer(p, b.serverConn.player, b.log, b.proxy().Event()) } diff --git a/pkg/edition/java/proxy/session_client_config.go b/pkg/edition/java/proxy/session_client_config.go index 23ce5a81..9a0bdd39 100644 --- a/pkg/edition/java/proxy/session_client_config.go +++ b/pkg/edition/java/proxy/session_client_config.go @@ -53,6 +53,9 @@ func (h *clientConfigSessionHandler) HandlePacket(pc *proto.PacketContext) { forwardKeepAlive(p, h.player) case *packet.ClientSettings: h.player.setClientSettings(p) + case *config.CodeOfConductAcceptPacket: + h.markCodeOfConductAccepted() + forwardToServer(pc, h.player) case *packet.ResourcePackResponse: if !handleResourcePackResponse(p, h.player.resourcePackHandler, h.log) { forwardToServer(pc, h.player) @@ -104,6 +107,16 @@ func (h *clientConfigSessionHandler) handleBackendFinishUpdate(serverConn *serve return &h.configSwitchDone } +func (h *clientConfigSessionHandler) markCodeOfConductAccepted() { + if serverConn := h.player.connectionInFlightOrConnectedServer(); serverConn != nil { + if smc, ok := serverConn.ensureConnected(); ok { + if backendConfig, ok := smc.ActiveSessionHandler().(*backendConfigSessionHandler); ok { + backendConfig.releaseCodeOfConductHold() + } + } + } +} + func handleResourcePackResponse(p *packet.ResourcePackResponse, handler resourcepack.Handler, log logr.Logger) bool { handled, err := handler.OnResourcePackResponse( resourcepack.BundleForResponse(p))