diff --git a/examples/frameworkServer/main.go b/examples/frameworkServer/main.go index f359e09..246e36e 100644 --- a/examples/frameworkServer/main.go +++ b/examples/frameworkServer/main.go @@ -20,7 +20,6 @@ type MyServer struct { playerList *server.PlayerList } -const ServerName = "MyServer" const MaxPlayer = 20 const IconPath = "./server-icon.png" @@ -28,7 +27,7 @@ var motd = chat.Message{Text: "A Minecraft Server ", Extra: []chat.Message{{Text func main() { playerList := server.NewPlayerList(MaxPlayer) - serverInfo, err := server.NewPingInfo(playerList, ServerName, server.ProtocolVersion, motd, readIcon()) + serverInfo, err := server.NewPingInfo(playerList, server.ProtocolName, server.ProtocolVersion, motd, readIcon()) if err != nil { log.Fatalf("Set server info error: %v", err) } diff --git a/save/chunk_test.go b/save/chunk_test.go index 5d5a186..386dac8 100644 --- a/save/chunk_test.go +++ b/save/chunk_test.go @@ -27,7 +27,7 @@ func TestColumn(t *testing.T) { } func BenchmarkColumn_Load(b *testing.B) { - // Test how many time we load a chunk + // Test how many times we load a chunk var c Column r, err := region.Open("testdata/region/r.-1.-1.mca") if err != nil { diff --git a/save/palette.go b/save/palette.go index 8be70db..8106e7e 100644 --- a/save/palette.go +++ b/save/palette.go @@ -13,8 +13,8 @@ type BlockState interface { type palette interface { id(v BlockState) int value(i int) BlockState - io.ReaderFrom - io.WriterTo + pk.FieldEncoder + pk.FieldDecoder read(r nbt.DecoderReader) (int, error) } diff --git a/server/auth/auth.go b/server/auth/auth.go index 0f495c3..311e1b7 100644 --- a/server/auth/auth.go +++ b/server/auth/auth.go @@ -59,7 +59,7 @@ func Encrypt(conn *net.Conn, name string) (*Resp, error) { return nil, err } - //confirm the verify token + //confirm to verify token if !bytes.Equal(VT1, VT2) { return nil, errors.New("verify token not match") } diff --git a/server/gameplay.go b/server/gameplay.go index 4cfe553..efccff1 100644 --- a/server/gameplay.go +++ b/server/gameplay.go @@ -6,5 +6,9 @@ import ( ) type GamePlay interface { + // AcceptPlayer handle everything after "LoginSuccess" is sent. + // + // Note: the connection will be closed after this function returned. + // You don't need to close the connection, but to keep not returning while the player is playing. AcceptPlayer(name string, id uuid.UUID, protocol int32, conn *net.Conn) } diff --git a/server/login.go b/server/login.go index 18b472d..5defdb1 100644 --- a/server/login.go +++ b/server/login.go @@ -3,23 +3,49 @@ package server import ( "fmt" + "github.com/google/uuid" + + "github.com/Tnze/go-mc/chat" "github.com/Tnze/go-mc/data/packetid" "github.com/Tnze/go-mc/net" pk "github.com/Tnze/go-mc/net/packet" "github.com/Tnze/go-mc/offline" "github.com/Tnze/go-mc/server/auth" - "github.com/google/uuid" ) +// LoginHandler is used to handle player login process, that is, +// from clientbound "LoginStart" packet to serverbound "LoginSuccess" packet. type LoginHandler interface { AcceptLogin(conn *net.Conn, protocol int32) (name string, id uuid.UUID, err error) } -type MojangLoginHandler struct { - OnlineMode bool - Threshold int +// LoginChecker is the interface to check if a player is allowed to log in the server. +// The checking could be anything, server player number, blacklist or whitelist. +// If a player is not allowed to, the reason should be returned and will be sent to client by "LoginDisconnect" packet. +type LoginChecker interface { + CheckPlayer(name string, id uuid.UUID) (ok bool, reason chat.Message) } +// MojangLoginHandler is a standard LoginHandler that implement both online and offline login progress. +// This implementation also support custom LoginChecker. +// None of Custom login packet (also called LoginPluginRequest/Response) is support by this implementation. +// To do that, implement your own LoginHandler imitate this code. +type MojangLoginHandler struct { + // OnlineMode enables to check player's account. + // And also encrypt the connection after login. + OnlineMode bool + + // Threshold set the smallest size of raw network payload to compress. + // Set to 0 to compress all packets. Set to -1 to disable compression. + Threshold int + + // LoginChecker is used to apply some checks before sending "LoginSuccess" packet + // (e.g. blacklist or is server full). + // This is optional field and can be set to nil. + LoginChecker +} + +// AcceptLogin implement LoginHandler for MojangLoginHandler func (d *MojangLoginHandler) AcceptLogin(conn *net.Conn, protocol int32) (name string, id uuid.UUID, err error) { //login start var p pk.Packet @@ -55,7 +81,8 @@ func (d *MojangLoginHandler) AcceptLogin(conn *net.Conn, protocol int32) (name s //set compression if d.Threshold >= 0 { err = conn.WritePacket(pk.Marshal( - packetid.SetCompression, pk.VarInt(d.Threshold), + packetid.SetCompression, + pk.VarInt(d.Threshold), )) if err != nil { return @@ -63,8 +90,25 @@ func (d *MojangLoginHandler) AcceptLogin(conn *net.Conn, protocol int32) (name s conn.SetThreshold(d.Threshold) } + // check if player can join (whitelist, blacklist, server full or something else) + if d.LoginChecker != nil { + if ok, result := d.CheckPlayer(name, id); !ok { + // player is not allowed to join the server + err = conn.WritePacket(pk.Marshal( + packetid.LoginDisconnect, + result, + )) + if err != nil { + return + } + err = loginFailErr{reason: result} + return + } + } + // send login success - err = conn.WritePacket(pk.Marshal(packetid.LoginSuccess, + err = conn.WritePacket(pk.Marshal( + packetid.LoginSuccess, pk.UUID(id), pk.String(name), )) @@ -78,3 +122,11 @@ type wrongPacketErr struct { func (w wrongPacketErr) Error() string { return fmt.Sprintf("wrong packet id: expect %#02X, get %#02X", w.expect, w.get) } + +type loginFailErr struct { + reason chat.Message +} + +func (l loginFailErr) Error() string { + return "login error: " + l.reason.ClearString() +} diff --git a/server/ping.go b/server/ping.go index ce23fa2..6ca8e6f 100644 --- a/server/ping.go +++ b/server/ping.go @@ -14,14 +14,24 @@ import ( "strings" ) +// ListPingHandler collect server running status info +// which is used to handle client ping and list progress. type ListPingHandler interface { + // Name of the server version Name() string + // Protocol number Protocol() int MaxPlayer() int OnlinePlayer() int + // PlayerSamples is a short list of some player in the server PlayerSamples() []PlayerSample Description() *chat.Message + // FavIcon should be a PNG image that is Base64 encoded + // (without newlines: \n, new lines no longer work since 1.13) + // and prepended with "data:image/png;base64,". + // + // This method can return empty string if no icon is set. FavIcon() string } diff --git a/server/ping_test.go b/server/ping_test.go deleted file mode 100644 index fc9c2ba..0000000 --- a/server/ping_test.go +++ /dev/null @@ -1,36 +0,0 @@ -package server - -import ( - "github.com/Tnze/go-mc/chat" - "image" - "os" -) - -func ExamplePingInfo_standardUsage() { - // Read server icon - f, err := os.Open("./server-icon.png") - if err != nil { - panic(err) - } - defer f.Close() - icon, _, err := image.Decode(f) - if err != nil { - panic(err) - } - // Set server info - playerList := NewPlayerList(20) - pingInfo, err := NewPingInfo(playerList, "1.18", 757, chat.Text("A Minecraft Server"), icon) - if err != nil { - panic(err) - } - // Start listening - s := Server{ - ListPingHandler: pingInfo, - LoginHandler: nil, - GamePlay: nil, - } - err = s.Listen("0.0.0.0:25565") - if err != nil { - return - } -} diff --git a/server/server.go b/server/server.go index 639f47d..2418663 100644 --- a/server/server.go +++ b/server/server.go @@ -2,16 +2,16 @@ // You can build the server you want by combining the various functional modules provided here. // An example can be found in examples/frameworkServer. // -// A server is roughly divided into two parts: +// A server is roughly divided into two parts: Gate and GamePlay // // +-----------------------------------------------------------------+ // | Go-MC Server Framework | // +--------------------------------------+--------------------------+ // | Gate | GamePlay | // +--------------------+-----------------+ | -// | LoginHandler | ListPingHandler | | -// +--------------------+--------+--------+-----------+--------------+ -// | MojangLoginHandler | Info | PlayerList | Others.... | +// | LoginHandler | ListPingHandler | | +// +--------------------+--------+--------+-----------+--------------+ +// | MojangLoginHandler | Info | PlayerList | Others.... | // +--------------------+--------+--------------------+--------------+ // // Gate, which is used to respond to the client login request, provide login verification, @@ -26,6 +26,7 @@ package server import "github.com/Tnze/go-mc/net" +const ProtocolName = "1.18.1" const ProtocolVersion = 757 type Server struct {