├── .gitignore ├── .idea ├── inspectionProfiles │ └── Project_Default.xml ├── modules.xml ├── plasma.iml └── vcs.xml ├── .testfiles └── test-icon-64x64.png ├── LICENSE ├── README.md ├── _examples └── basic-status-server │ └── main.go ├── authenticator.go ├── conn.go ├── encrypter.go ├── go.mod ├── go.sum ├── mux.go ├── player.go ├── protocol ├── cfb8 │ ├── cfb8.go │ └── cfb8_test.go ├── errors.go ├── packet.go ├── packets │ ├── handshaking │ │ ├── serverbound_handshake.go │ │ └── serverbound_handshake_test.go │ ├── login │ │ ├── clientbound_disconnect.go │ │ ├── clientbound_encryptionrequest.go │ │ ├── clientbound_loginpluginrequest.go │ │ ├── clientbound_loginsuccess.go │ │ ├── clientbound_setcompression.go │ │ ├── serverbound_encryptionresponse.go │ │ ├── serverbound_loginpluginresponse.go │ │ └── serverbound_loginstart.go │ ├── play │ │ ├── clientbound_declarecommands.go │ │ ├── clientbound_declarerecipes.go │ │ ├── clientbound_disconnect.go │ │ ├── clientbound_entitystatus.go │ │ ├── clientbound_helditemchange.go │ │ ├── clientbound_joingame.go │ │ ├── clientbound_playerabilities.go │ │ ├── clientbound_playerpositionandlook.go │ │ ├── clientbound_pluginmessage.go │ │ ├── clientbound_serverdifficulty.go │ │ ├── clientbound_spawnentity.go │ │ ├── clientbound_spawnexperienceorb.go │ │ ├── clientbound_tags.go │ │ ├── clientbound_unlockrecipes.go │ │ ├── difficulty.go │ │ ├── dimension.go │ │ ├── gamemode.go │ │ ├── ingredient.go │ │ ├── recipe.go │ │ ├── serverbound_clientsettings.go │ │ ├── serverbound_pluginmessage.go │ │ └── slot.go │ └── status │ │ ├── clientbound_pong.go │ │ ├── clientbound_response.go │ │ ├── serverbound_ping.go │ │ └── serverbound_request.go ├── peeker.go ├── state.go ├── types.go └── zlib │ └── zlib.go ├── server.go ├── server_test.go ├── sha1.go └── sha1_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | 17 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 18 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 19 | 20 | # User-specific stuff 21 | .idea/**/workspace.xml 22 | .idea/**/tasks.xml 23 | .idea/**/usage.statistics.xml 24 | .idea/**/dictionaries 25 | .idea/**/shelf 26 | 27 | # Generated files 28 | .idea/**/contentModel.xml 29 | 30 | # Sensitive or high-churn files 31 | .idea/**/dataSources/ 32 | .idea/**/dataSources.ids 33 | .idea/**/dataSources.local.xml 34 | .idea/**/sqlDataSources.xml 35 | .idea/**/dynamic.xml 36 | .idea/**/uiDesigner.xml 37 | .idea/**/dbnavigator.xml 38 | 39 | # Gradle 40 | .idea/**/gradle.xml 41 | .idea/**/libraries 42 | 43 | # Gradle and Maven with auto-import 44 | # When using Gradle or Maven with auto-import, you should exclude module files, 45 | # since they will be recreated, and may cause churn. Uncomment if using 46 | # auto-import. 47 | # .idea/artifacts 48 | # .idea/compiler.xml 49 | # .idea/jarRepositories.xml 50 | # .idea/modules.xml 51 | # .idea/*.iml 52 | # .idea/modules 53 | # *.iml 54 | # *.ipr 55 | 56 | # CMake 57 | cmake-build-*/ 58 | 59 | # Mongo Explorer plugin 60 | .idea/**/mongoSettings.xml 61 | 62 | # File-based project format 63 | *.iws 64 | 65 | # IntelliJ 66 | out/ 67 | 68 | # mpeltonen/sbt-idea plugin 69 | .idea_modules/ 70 | 71 | # JIRA plugin 72 | atlassian-ide-plugin.xml 73 | 74 | # Cursive Clojure plugin 75 | .idea/replstate.xml 76 | 77 | # Crashlytics plugin (for Android Studio and IntelliJ) 78 | com_crashlytics_export_strings.xml 79 | crashlytics.properties 80 | crashlytics-build.properties 81 | fabric.properties 82 | 83 | # Editor-based Rest Client 84 | .idea/httpRequests 85 | 86 | # Android studio 3.1+ serialized cache file 87 | .idea/caches/build_file_checksums.ser 88 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 25 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/plasma.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.testfiles/test-icon-64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/specspace/plasma/02301d38bb1aaf8f1b6ba7b7c0ac3c463bc35e65/.testfiles/test-icon-64x64.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Spectrum Spatial 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Plasma 2 | Minecraft server framework written in Go. Having an API similar to the standard http package, 3 | this framework allows you to write Minecraft servers from scratch without needing too much 4 | boilerplate. 5 | 6 | ## Basic Server 7 | ```go 8 | package main 9 | 10 | import ( 11 | "github.com/specspace/plasma" 12 | "github.com/specspace/plasma/protocol" 13 | "github.com/specspace/plasma/protocol/packets/handshaking" 14 | "github.com/specspace/plasma/protocol/packets/status" 15 | "log" 16 | ) 17 | 18 | func main() { 19 | plasma.HandleFunc(protocol.StateHandshaking, handshaking.ServerBoundHandshakePacketID, handshakeHandler) 20 | plasma.HandleFunc(protocol.StateStatus, status.ServerBoundPingPacketID, pingHandler) 21 | plasma.HandleFunc(protocol.StateStatus, status.ServerBoundRequestPacketID, responseHandler) 22 | 23 | log.Fatal(plasma.ListenAndServe(":25565", nil)) 24 | } 25 | 26 | func handshakeHandler(w plasma.ResponseWriter, r *plasma.Request) { 27 | // You can access and unmarshal the handshake packet like this. 28 | hs, err := handshaking.UnmarshalServerBoundHandshake(r.Packet) 29 | if err != nil { 30 | return 31 | } 32 | 33 | if hs.IsStatusRequest() { 34 | // We can now update the connection state according to the 35 | // handshake request packet. 36 | w.SetState(protocol.StateStatus) 37 | } else if hs.IsLoginRequest() { 38 | w.SetState(protocol.StateLogin) 39 | } 40 | } 41 | 42 | func pingHandler(w plasma.ResponseWriter, r *plasma.Request) { 43 | ping, err := status.UnmarshalServerBoundPing(r.Packet) 44 | if err != nil { 45 | return 46 | } 47 | 48 | pong := status.ClientBoundPong{ 49 | Payload: ping.Payload, 50 | } 51 | 52 | w.WritePacket(pong.Marshal()) 53 | } 54 | 55 | func responseHandler(w plasma.ResponseWriter, r *plasma.Request) { 56 | statusResponse := plasma.StatusResponse{ 57 | Version: plasma.Version{ 58 | Name: "Plasma 1.16.4", 59 | ProtocolNumber: 754, 60 | }, 61 | PlayersInfo: r.Server.PlayersInfo(), 62 | IconPath: "", 63 | MOTD: "Hello World", 64 | } 65 | 66 | bb, err := statusResponse.JSON() 67 | if err != nil { 68 | return 69 | } 70 | 71 | w.WritePacket(status.ClientBoundResponse{ 72 | JSONResponse: protocol.String(bb), 73 | }.Marshal()) 74 | } 75 | ``` -------------------------------------------------------------------------------- /_examples/basic-status-server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/specspace/plasma" 5 | "github.com/specspace/plasma/protocol" 6 | "github.com/specspace/plasma/protocol/packets/handshaking" 7 | "github.com/specspace/plasma/protocol/packets/status" 8 | "log" 9 | ) 10 | 11 | func main() { 12 | plasma.HandleFunc(protocol.StateHandshaking, handshaking.ServerBoundHandshakePacketID, handshakeHandler) 13 | plasma.HandleFunc(protocol.StateStatus, status.ServerBoundPingPacketID, pingHandler) 14 | plasma.HandleFunc(protocol.StateStatus, status.ServerBoundRequestPacketID, responseHandler) 15 | 16 | log.Fatal(plasma.ListenAndServe(":25565", nil)) 17 | } 18 | 19 | func handshakeHandler(w plasma.ResponseWriter, r *plasma.Request) { 20 | // You can access and unmarshal the handshake packet like this. 21 | hs, err := handshaking.UnmarshalServerBoundHandshake(r.Packet) 22 | if err != nil { 23 | return 24 | } 25 | 26 | if hs.IsStatusRequest() { 27 | // We can now update the connection state according to the 28 | // handshake request packet. 29 | w.SetState(protocol.StateStatus) 30 | } else if hs.IsLoginRequest() { 31 | w.SetState(protocol.StateLogin) 32 | } 33 | } 34 | 35 | func pingHandler(w plasma.ResponseWriter, r *plasma.Request) { 36 | ping, err := status.UnmarshalServerBoundPing(r.Packet) 37 | if err != nil { 38 | return 39 | } 40 | 41 | pong := status.ClientBoundPong{ 42 | Payload: ping.Payload, 43 | } 44 | 45 | w.WritePacket(pong.Marshal()) 46 | } 47 | 48 | func responseHandler(w plasma.ResponseWriter, r *plasma.Request) { 49 | statusResponse := plasma.StatusResponse{ 50 | Version: plasma.Version{ 51 | Name: "Plasma 1.16.4", 52 | ProtocolNumber: 754, 53 | }, 54 | PlayersInfo: r.Server().PlayersInfo(), 55 | IconPath: "", 56 | MOTD: "Hello World", 57 | } 58 | 59 | bb, err := statusResponse.JSON() 60 | if err != nil { 61 | return 62 | } 63 | 64 | w.WritePacket(status.ClientBoundResponse{ 65 | JSONResponse: protocol.String(bb), 66 | }.Marshal()) 67 | } 68 | -------------------------------------------------------------------------------- /authenticator.go: -------------------------------------------------------------------------------- 1 | package plasma 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "github.com/gofrs/uuid" 8 | "net/http" 9 | ) 10 | 11 | func MojangSessionServerURLHasJoined(username, sessionHash string) string { 12 | return fmt.Sprintf( 13 | "https://sessionserver.mojang.com/session/minecraft/hasJoined?username=%s&serverId=%s", 14 | username, 15 | sessionHash, 16 | ) 17 | } 18 | 19 | func MojangSessionServerURLHasJoinedWithIP(username, sessionHash, ip string) string { 20 | return fmt.Sprintf("%s&ip=%s", 21 | MojangSessionServerURLHasJoined(username, sessionHash), 22 | ip, 23 | ) 24 | } 25 | 26 | func GenerateSessionHash(serverID string, sharedSecret, publicKey []byte) string { 27 | notchHash := NewSha1Hash() 28 | notchHash.Update([]byte(serverID)) 29 | notchHash.Update(sharedSecret) 30 | notchHash.Update(publicKey) 31 | return notchHash.HexDigest() 32 | } 33 | 34 | type Session struct { 35 | PlayerUUID uuid.UUID 36 | PlayerSkin Skin 37 | } 38 | 39 | type SessionAuthenticator interface { 40 | AuthenticateSession(username, sessionHash string) (Session, error) 41 | AuthenticateSessionPreventProxy(username, sessionHash, ip string) (Session, error) 42 | } 43 | 44 | type MojangSessionAuthenticator struct{} 45 | 46 | func (auth *MojangSessionAuthenticator) AuthenticateSession(username, sessionHash string) (Session, error) { 47 | return auth.AuthenticateSessionPreventProxy(username, sessionHash, "") 48 | } 49 | 50 | func (auth *MojangSessionAuthenticator) AuthenticateSessionPreventProxy(username, sessionHash, ip string) (Session, error) { 51 | var url string 52 | if ip == "" { 53 | url = MojangSessionServerURLHasJoined(username, sessionHash) 54 | } else { 55 | url = MojangSessionServerURLHasJoinedWithIP(username, sessionHash, ip) 56 | } 57 | 58 | resp, err := http.Get(url) 59 | if err != nil { 60 | return Session{}, err 61 | } 62 | 63 | if resp.StatusCode != http.StatusOK { 64 | return Session{}, fmt.Errorf("unable to authenticate session (%s)", resp.Status) 65 | } 66 | 67 | var p struct { 68 | ID string `json:"id"` 69 | Name string `json:"name"` 70 | Properties []struct { 71 | Name string `json:"name"` 72 | Value string `json:"value"` 73 | Signature string `json:"signature"` 74 | } `json:"properties"` 75 | } 76 | 77 | if err := json.NewDecoder(resp.Body).Decode(&p); err != nil { 78 | return Session{}, err 79 | } 80 | _ = resp.Body.Close() 81 | 82 | playerUUID, err := uuid.FromString(p.ID) 83 | if err != nil { 84 | return Session{}, err 85 | } 86 | 87 | var skinValue string 88 | var skinSignature string 89 | for _, property := range p.Properties { 90 | if property.Name == "textures" { 91 | skinValue = property.Value 92 | skinSignature = property.Signature 93 | break 94 | } 95 | } 96 | 97 | if skinValue == "" { 98 | return Session{}, errors.New("no skinValue in request") 99 | } 100 | 101 | return Session{ 102 | PlayerUUID: playerUUID, 103 | PlayerSkin: Skin{ 104 | Value: skinValue, 105 | Signature: skinSignature, 106 | }, 107 | }, nil 108 | } 109 | -------------------------------------------------------------------------------- /conn.go: -------------------------------------------------------------------------------- 1 | package plasma 2 | 3 | import ( 4 | "bufio" 5 | "crypto/cipher" 6 | "github.com/specspace/plasma/protocol" 7 | "io" 8 | "net" 9 | "time" 10 | ) 11 | 12 | type PacketWriter interface { 13 | WritePacket(p protocol.Packet) error 14 | } 15 | 16 | type PacketReader interface { 17 | ReadPacket() (protocol.Packet, error) 18 | } 19 | 20 | type PacketPeeker interface { 21 | PeekPacket() (protocol.Packet, error) 22 | } 23 | 24 | type conn struct { 25 | net.Conn 26 | 27 | r *bufio.Reader 28 | w io.Writer 29 | state protocol.State 30 | threshold int 31 | } 32 | 33 | type Listener struct { 34 | net.Listener 35 | } 36 | 37 | func Listen(addr string) (Listener, error) { 38 | l, err := net.Listen("tcp", addr) 39 | return Listener{Listener: l}, err 40 | } 41 | 42 | func (l Listener) Accept() (Conn, error) { 43 | conn, err := l.Listener.Accept() 44 | if err != nil { 45 | return nil, err 46 | } 47 | return wrapConn(conn), nil 48 | } 49 | 50 | // Conn is a minecraft Connection 51 | type Conn interface { 52 | net.Conn 53 | PacketWriter 54 | PacketReader 55 | PacketPeeker 56 | 57 | State() protocol.State 58 | Threshold() int 59 | } 60 | 61 | // wrapConn warp an net.Conn to plasma.Conn 62 | func wrapConn(c net.Conn) *conn { 63 | return &conn{ 64 | Conn: c, 65 | r: bufio.NewReader(c), 66 | w: c, 67 | state: protocol.StateHandshaking, 68 | threshold: 0, 69 | } 70 | } 71 | 72 | // Dial create a Minecraft connection 73 | func Dial(addr string) (Conn, error) { 74 | conn, err := net.Dial("tcp", addr) 75 | if err != nil { 76 | return nil, err 77 | } 78 | 79 | return wrapConn(conn), nil 80 | } 81 | 82 | // DialTimeout acts like DialMC but takes a timeout. 83 | func DialTimeout(addr string, timeout time.Duration) (Conn, error) { 84 | conn, err := net.DialTimeout("tcp", addr, timeout) 85 | if err != nil { 86 | return nil, err 87 | } 88 | 89 | return wrapConn(conn), nil 90 | } 91 | 92 | func (c conn) State() protocol.State { 93 | return c.state 94 | } 95 | 96 | // Threshold returns the number of bytes a Packet can be long before it will be compressed 97 | func (c conn) Threshold() int { 98 | return c.threshold 99 | } 100 | 101 | func (c *conn) Read(b []byte) (int, error) { 102 | return c.r.Read(b) 103 | } 104 | 105 | func (c *conn) Write(b []byte) (int, error) { 106 | return c.w.Write(b) 107 | } 108 | 109 | // ReadPacket read a Packet from Conn. 110 | func (c *conn) ReadPacket() (protocol.Packet, error) { 111 | return protocol.ReadPacket(c.r, c.threshold > 0) 112 | } 113 | 114 | // PeekPacket peeks a Packet from Conn. 115 | func (c *conn) PeekPacket() (protocol.Packet, error) { 116 | return protocol.PeekPacket(c.r, c.threshold > 0) 117 | } 118 | 119 | //WritePacket write a Packet to Conn. 120 | func (c *conn) WritePacket(p protocol.Packet) error { 121 | pk, err := p.Marshal(c.threshold) 122 | if err != nil { 123 | return err 124 | } 125 | _, err = c.w.Write(pk) 126 | return err 127 | } 128 | 129 | // SetCipher sets the decode/encode stream for this Conn 130 | func (c *conn) SetCipher(ecoStream, decoStream cipher.Stream) { 131 | c.r = bufio.NewReader(cipher.StreamReader{ 132 | S: decoStream, 133 | R: c.Conn, 134 | }) 135 | c.w = cipher.StreamWriter{ 136 | S: ecoStream, 137 | W: c.Conn, 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /encrypter.go: -------------------------------------------------------------------------------- 1 | package plasma 2 | 3 | import ( 4 | "bytes" 5 | "crypto/rand" 6 | "crypto/rsa" 7 | "crypto/x509" 8 | "errors" 9 | "sync" 10 | ) 11 | 12 | const ( 13 | keyBitSize = 1024 14 | verifyTokenLength = 4 15 | ) 16 | 17 | type SessionEncrypter interface { 18 | PublicKey() []byte 19 | GenerateVerifyToken(r *Request) ([]byte, error) 20 | DecryptAndVerifySharedSecret(r *Request, sharedSecret, verifyToken []byte) ([]byte, error) 21 | } 22 | 23 | type DefaultSessionEncrypter struct { 24 | privateKey *rsa.PrivateKey 25 | 26 | mu sync.Mutex 27 | publicKey []byte 28 | verifyTokens map[*conn][]byte 29 | } 30 | 31 | func NewDefaultSessionEncrypter() (SessionEncrypter, error) { 32 | key, err := rsa.GenerateKey(rand.Reader, keyBitSize) 33 | if err != nil { 34 | return nil, err 35 | } 36 | 37 | publicKey, err := x509.MarshalPKIXPublicKey(&key.PublicKey) 38 | if err != nil { 39 | return nil, err 40 | } 41 | 42 | return &DefaultSessionEncrypter{ 43 | privateKey: key, 44 | publicKey: publicKey, 45 | mu: sync.Mutex{}, 46 | verifyTokens: map[*conn][]byte{}, 47 | }, nil 48 | } 49 | 50 | func (encrypter *DefaultSessionEncrypter) PublicKey() []byte { 51 | encrypter.mu.Lock() 52 | defer encrypter.mu.Unlock() 53 | return encrypter.publicKey 54 | } 55 | 56 | func (encrypter *DefaultSessionEncrypter) GenerateVerifyToken(r *Request) ([]byte, error) { 57 | encrypter.mu.Lock() 58 | defer encrypter.mu.Unlock() 59 | 60 | verifyToken := make([]byte, verifyTokenLength) 61 | if _, err := rand.Read(verifyToken); err != nil { 62 | return nil, err 63 | } 64 | 65 | encrypter.verifyTokens[r.conn] = verifyToken 66 | return verifyToken, nil 67 | } 68 | 69 | func (encrypter *DefaultSessionEncrypter) DecryptAndVerifySharedSecret(r *Request, sharedSecret, verifyToken []byte) ([]byte, error) { 70 | encrypter.mu.Lock() 71 | defer encrypter.mu.Unlock() 72 | 73 | token, ok := encrypter.verifyTokens[r.conn] 74 | if !ok { 75 | return nil, errors.New("no verify token registered") 76 | } 77 | delete(encrypter.verifyTokens, r.conn) 78 | 79 | verifyToken, err := encrypter.privateKey.Decrypt(rand.Reader, verifyToken, nil) 80 | if err != nil { 81 | return nil, err 82 | } 83 | 84 | if !bytes.Equal(verifyToken, token) { 85 | return nil, errors.New("verify token did not match") 86 | } 87 | 88 | return encrypter.privateKey.Decrypt(rand.Reader, sharedSecret, nil) 89 | } 90 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/specspace/plasma 2 | 3 | go 1.15 4 | 5 | require ( 6 | github.com/4kills/go-libdeflate/v2 v2.0.2 7 | github.com/Tnze/go-mc v1.16.1 8 | github.com/gofrs/uuid v3.3.0+incompatible 9 | ) 10 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/4kills/go-libdeflate/v2 v2.0.2 h1:UnjnCl96u8NSGBnbJSrKoOhEPu8ex+OItG4rmSuO2Yo= 2 | github.com/4kills/go-libdeflate/v2 v2.0.2/go.mod h1:hyouZv4OAhHaaMpYuejstUN0xOg8mA+yy75WE3Ty6SM= 3 | github.com/Tnze/go-mc v1.16.1 h1:KX2h8pLjqz6bJVVJpMa0TE6lkcPmAyzhlfhFp8AwyEs= 4 | github.com/Tnze/go-mc v1.16.1/go.mod h1:LKj1KKSj4m58fpJaTmOHC3DdQKJ1RSfEGHFR1zMLSF8= 5 | github.com/gofrs/uuid v3.3.0+incompatible h1:8K4tyRfvU1CYPgJsveYFQMhpFd/wXNM7iK6rR7UHz84= 6 | github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= 7 | github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 8 | -------------------------------------------------------------------------------- /mux.go: -------------------------------------------------------------------------------- 1 | package plasma 2 | 3 | import ( 4 | "github.com/specspace/plasma/protocol" 5 | "sync" 6 | ) 7 | 8 | type ServeMux struct { 9 | handlers map[protocol.State]map[byte]Handler 10 | mu sync.RWMutex 11 | } 12 | 13 | func NewServeMux() *ServeMux { 14 | return &ServeMux{ 15 | handlers: map[protocol.State]map[byte]Handler{ 16 | protocol.StateHandshaking: {}, 17 | protocol.StateStatus: {}, 18 | protocol.StateLogin: {}, 19 | protocol.StatePlay: {}, 20 | }, 21 | mu: sync.RWMutex{}, 22 | } 23 | } 24 | 25 | func (mux *ServeMux) Handle(state protocol.State, packetID byte, handler Handler) { 26 | mux.mu.Lock() 27 | defer mux.mu.Unlock() 28 | 29 | if handler == nil { 30 | panic("plasma: nil handler") 31 | } 32 | 33 | mux.handlers[state][packetID] = handler 34 | } 35 | 36 | func (mux *ServeMux) HandleFunc(state protocol.State, packetID byte, handler func(w ResponseWriter, r *Request)) { 37 | if handler == nil { 38 | panic("plasma: nil handler") 39 | } 40 | 41 | mux.Handle(state, packetID, HandlerFunc(handler)) 42 | } 43 | 44 | func (mux *ServeMux) Handler(r *Request) (Handler, byte) { 45 | mux.mu.RLock() 46 | defer mux.mu.RUnlock() 47 | 48 | handler, ok := mux.handlers[r.conn.state][r.Packet.ID] 49 | if !ok { 50 | return nil, r.Packet.ID 51 | } 52 | 53 | return handler, r.Packet.ID 54 | } 55 | 56 | func (mux *ServeMux) ServeProtocol(w ResponseWriter, r *Request) { 57 | handler, _ := mux.Handler(r) 58 | if handler == nil { 59 | return 60 | } 61 | 62 | handler.ServeProtocol(w, r) 63 | } 64 | 65 | var DefaultServeMux = NewServeMux() 66 | 67 | func Handle(state protocol.State, packetID byte, handler Handler) { 68 | DefaultServeMux.Handle(state, packetID, handler) 69 | } 70 | 71 | func HandleFunc(state protocol.State, packetID byte, handler func(w ResponseWriter, r *Request)) { 72 | DefaultServeMux.HandleFunc(state, packetID, handler) 73 | } 74 | -------------------------------------------------------------------------------- /player.go: -------------------------------------------------------------------------------- 1 | package plasma 2 | 3 | import ( 4 | "github.com/gofrs/uuid" 5 | ) 6 | 7 | type Skin struct { 8 | Value string 9 | Signature string 10 | } 11 | 12 | type player struct { 13 | *conn 14 | uuid uuid.UUID 15 | username string 16 | skin Skin 17 | } 18 | 19 | func (p player) UUID() uuid.UUID { 20 | return p.uuid 21 | } 22 | 23 | func (p player) Username() string { 24 | return p.username 25 | } 26 | 27 | func (p player) Skin() Skin { 28 | return p.skin 29 | } 30 | 31 | type Player interface { 32 | Conn 33 | 34 | // UUID returns the uuid of the player 35 | UUID() uuid.UUID 36 | 37 | // Username returns the username of the player 38 | Username() string 39 | 40 | // Skin returns the Skin of the player 41 | Skin() Skin 42 | } 43 | -------------------------------------------------------------------------------- /protocol/cfb8/cfb8.go: -------------------------------------------------------------------------------- 1 | // All credits go to Ilmari Karonen 2 | // Source: https://stackoverflow.com/questions/23897809/different-results-in-go-and-pycrypto-when-using-aes-cfb/37234233#37234233 3 | package cfb8 4 | 5 | import ( 6 | "crypto/cipher" 7 | ) 8 | 9 | // CFB stream with 8 bit segment size 10 | // See http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf 11 | type cfb8 struct { 12 | b cipher.Block 13 | blockSize int 14 | in []byte 15 | out []byte 16 | 17 | decrypt bool 18 | } 19 | 20 | func (x *cfb8) XORKeyStream(dst, src []byte) { 21 | for i := range src { 22 | x.b.Encrypt(x.out, x.in) 23 | copy(x.in[:x.blockSize-1], x.in[1:]) 24 | if x.decrypt { 25 | x.in[x.blockSize-1] = src[i] 26 | } 27 | dst[i] = src[i] ^ x.out[0] 28 | if !x.decrypt { 29 | x.in[x.blockSize-1] = dst[i] 30 | } 31 | } 32 | } 33 | 34 | // NewCFB8Encrypter returns a Stream which encrypts with cipher feedback mode 35 | // (segment size = 8), using the given Block. The iv must be the same length as 36 | // the Block's block size. 37 | func NewEncrypter(block cipher.Block, iv []byte) cipher.Stream { 38 | return newCFB8(block, iv, false) 39 | } 40 | 41 | // NewCFB8Decrypter returns a Stream which decrypts with cipher feedback mode 42 | // (segment size = 8), using the given Block. The iv must be the same length as 43 | // the Block's block size. 44 | func NewDecrypter(block cipher.Block, iv []byte) cipher.Stream { 45 | return newCFB8(block, iv, true) 46 | } 47 | 48 | func newCFB8(block cipher.Block, iv []byte, decrypt bool) cipher.Stream { 49 | blockSize := block.BlockSize() 50 | if len(iv) != blockSize { 51 | // stack trace will indicate whether it was de or encryption 52 | panic("cipher.newCFB: IV length must equal block size") 53 | } 54 | x := &cfb8{ 55 | b: block, 56 | blockSize: blockSize, 57 | out: make([]byte, blockSize), 58 | in: make([]byte, blockSize), 59 | decrypt: decrypt, 60 | } 61 | copy(x.in, iv) 62 | 63 | return x 64 | } 65 | -------------------------------------------------------------------------------- /protocol/cfb8/cfb8_test.go: -------------------------------------------------------------------------------- 1 | package cfb8 2 | 3 | import ( 4 | "bytes" 5 | "crypto/aes" 6 | "crypto/cipher" 7 | "crypto/rand" 8 | "testing" 9 | ) 10 | 11 | func initCipherBlock(secret []byte) (cipher.Block, error) { 12 | block, err := aes.NewCipher(secret) 13 | if err != nil { 14 | return nil, err 15 | } 16 | 17 | return block, nil 18 | } 19 | 20 | func TestCfb8_XORKeyStream_Encrypt(t *testing.T) { 21 | secret := []byte("qwdhyte62kjneThg") 22 | block, err := initCipherBlock(secret) 23 | if err != nil { 24 | t.Error(err) 25 | } 26 | 27 | encrypter := newCFB8(block, secret, false) 28 | message := "Hello" 29 | should := []byte{68, 159, 26, 206, 126} 30 | is := make([]byte, len(should)) 31 | 32 | encrypter.XORKeyStream(is, []byte(message)) 33 | 34 | if bytes.Compare(is, should) != 0 { 35 | t.Fail() 36 | } 37 | } 38 | 39 | func TestCfb8_XORKeyStream_Decrypt(t *testing.T) { 40 | secret := []byte("qwdhyte62kjneThg") 41 | block, err := initCipherBlock(secret) 42 | if err != nil { 43 | t.Error(err) 44 | } 45 | 46 | decrypter := newCFB8(block, secret, true) 47 | message := []byte{68, 159, 26, 206, 126} 48 | should := "Hello" 49 | is := make([]byte, len(should)) 50 | 51 | decrypter.XORKeyStream(is, message) 52 | 53 | if bytes.Compare(is, []byte(should)) != 0 { 54 | t.Fail() 55 | } 56 | } 57 | 58 | func initRandomCipherBlock() ([]byte, cipher.Block, error) { 59 | secret := make([]byte, 16) 60 | if _, err := rand.Read(secret); err != nil { 61 | return nil, nil, err 62 | } 63 | 64 | block, err := initCipherBlock(secret) 65 | return secret, block, err 66 | } 67 | 68 | func BenchmarkEncrypt10000Bytes(b *testing.B) { 69 | secret, block, err := initRandomCipherBlock() 70 | if err != nil { 71 | b.Error(err) 72 | } 73 | 74 | c := NewEncrypter(block, secret) 75 | dst := make([]byte, 10000) 76 | src := make([]byte, 10000) 77 | if _, err := rand.Read(src); err != nil { 78 | b.Error(err) 79 | } 80 | 81 | for n := 0; n < b.N; n++ { 82 | c.XORKeyStream(dst, src) 83 | } 84 | } 85 | 86 | func BenchmarkDecrypt10000Bytes(b *testing.B) { 87 | secret, block, err := initRandomCipherBlock() 88 | if err != nil { 89 | b.Error(err) 90 | } 91 | 92 | c := NewDecrypter(block, secret) 93 | dst := make([]byte, 10000) 94 | src := make([]byte, 10000) 95 | if _, err := rand.Read(src); err != nil { 96 | b.Error(err) 97 | } 98 | 99 | for n := 0; n < b.N; n++ { 100 | c.XORKeyStream(dst, src) 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /protocol/errors.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | var ( 8 | ErrInvalidPacketID = errors.New("invalid packet id") 9 | ) 10 | -------------------------------------------------------------------------------- /protocol/packet.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "github.com/specspace/plasma/protocol/zlib" 7 | "io" 8 | ) 9 | 10 | // A Packet is the raw representation of message that is send between the client and the server 11 | type Packet struct { 12 | ID byte 13 | Data []byte 14 | } 15 | 16 | // ScanFields decodes and copies the Packet data into the fields 17 | func (pk Packet) Scan(fields ...FieldDecoder) error { 18 | return ScanFields(bytes.NewReader(pk.Data), fields...) 19 | } 20 | 21 | // MarshalPacket encodes the packet and compresses it if it is larger then the given threshold 22 | func (pk *Packet) Marshal(threshold int) ([]byte, error) { 23 | var packedData []byte 24 | data := []byte{pk.ID} 25 | data = append(data, pk.Data...) 26 | 27 | if threshold > 0 { 28 | if len(data) > threshold { 29 | length := VarInt(len(data)).Encode() 30 | var err error 31 | data, err = zlib.Encode(data) 32 | if err != nil { 33 | return nil, err 34 | } 35 | 36 | packedData = append(packedData, VarInt(len(length)+len(data)).Encode()...) 37 | packedData = append(packedData, length...) 38 | } else { 39 | packedData = append(packedData, VarInt(int32(len(data)+1)).Encode()...) 40 | packedData = append(packedData, 0x00) 41 | } 42 | } else { 43 | packedData = append(packedData, VarInt(int32(len(data))).Encode()...) 44 | } 45 | 46 | return append(packedData, data...), nil 47 | } 48 | 49 | // ScanFields decodes a byte stream into fields 50 | func ScanFields(r DecodeReader, fields ...FieldDecoder) error { 51 | for _, field := range fields { 52 | if err := field.Decode(r); err != nil { 53 | return err 54 | } 55 | } 56 | return nil 57 | } 58 | 59 | func MarshalFields(fields ...FieldEncoder) []byte { 60 | var b []byte 61 | for _, field := range fields { 62 | b = append(b, field.Encode()...) 63 | } 64 | return b 65 | } 66 | 67 | // MarshalPacket transforms an ID and Fields into a Packet 68 | func MarshalPacket(ID byte, fields ...FieldEncoder) Packet { 69 | var pkt Packet 70 | pkt.ID = ID 71 | 72 | for _, v := range fields { 73 | pkt.Data = append(pkt.Data, v.Encode()...) 74 | } 75 | 76 | return pkt 77 | } 78 | 79 | // ParsePacket decodes and decompresses a byte array into a Packet 80 | func ParsePacket(data []byte) (Packet, error) { 81 | reader := bytes.NewBuffer(data) 82 | 83 | var dataLength VarInt 84 | if err := dataLength.Decode(reader); err != nil { 85 | return Packet{}, err 86 | } 87 | 88 | decompressedData := make([]byte, dataLength) 89 | isCompressed := dataLength != 0 90 | if isCompressed { 91 | var err error 92 | if err = zlib.Decode(reader.Bytes(), decompressedData); err != nil { 93 | return Packet{}, err 94 | } 95 | } else { 96 | decompressedData = data[1:] 97 | } 98 | 99 | return Packet{ 100 | ID: decompressedData[0], 101 | Data: decompressedData[1:], 102 | }, nil 103 | } 104 | 105 | // ReadPacketBytes decodes a byte stream and cuts the first Packet as a byte array out 106 | func ReadPacketBytes(r DecodeReader) ([]byte, error) { 107 | var packetLength VarInt 108 | if err := packetLength.Decode(r); err != nil { 109 | return nil, err 110 | } 111 | 112 | if packetLength < 1 { 113 | return nil, fmt.Errorf("packet length too short") 114 | } 115 | 116 | data := make([]byte, packetLength) 117 | if _, err := io.ReadFull(r, data); err != nil { 118 | return nil, fmt.Errorf("reading the content of the packet failed: %v", err) 119 | } 120 | 121 | return data, nil 122 | } 123 | 124 | // ReadPacket decodes and decompresses a byte stream and cuts the first Packet out 125 | func ReadPacket(r DecodeReader, isZlib bool) (Packet, error) { 126 | data, err := ReadPacketBytes(r) 127 | if err != nil { 128 | return Packet{}, err 129 | } 130 | 131 | if isZlib { 132 | return ParsePacket(data) 133 | } 134 | 135 | return Packet{ 136 | ID: data[0], 137 | Data: data[1:], 138 | }, nil 139 | } 140 | 141 | // PeekPacket decodes and decompresses a byte stream and peeks the first Packet 142 | func PeekPacket(p PeekReader, isZlib bool) (Packet, error) { 143 | r := bytePeeker{ 144 | PeekReader: p, 145 | cursor: 0, 146 | } 147 | 148 | return ReadPacket(&r, isZlib) 149 | } 150 | -------------------------------------------------------------------------------- /protocol/packets/handshaking/serverbound_handshake.go: -------------------------------------------------------------------------------- 1 | package handshaking 2 | 3 | import ( 4 | "github.com/specspace/plasma/protocol" 5 | "strings" 6 | ) 7 | 8 | const ( 9 | ServerBoundHandshakePacketID byte = 0x00 10 | 11 | ServerBoundHandshakeStatusState = protocol.Byte(1) 12 | ServerBoundHandshakeLoginState = protocol.Byte(2) 13 | 14 | ForgeAddressSuffix = "\x00FML\x00" 15 | Forge2AddressSuffix = "\x00FML2\x00" 16 | ) 17 | 18 | type ServerBoundHandshake struct { 19 | ProtocolVersion protocol.VarInt 20 | ServerAddress protocol.String 21 | ServerPort protocol.UnsignedShort 22 | NextState protocol.Byte 23 | } 24 | 25 | func (pk ServerBoundHandshake) Marshal() protocol.Packet { 26 | return protocol.MarshalPacket( 27 | ServerBoundHandshakePacketID, 28 | pk.ProtocolVersion, 29 | pk.ServerAddress, 30 | pk.ServerPort, 31 | pk.NextState, 32 | ) 33 | } 34 | 35 | func UnmarshalServerBoundHandshake(packet protocol.Packet) (ServerBoundHandshake, error) { 36 | var pk ServerBoundHandshake 37 | 38 | if packet.ID != ServerBoundHandshakePacketID { 39 | return pk, protocol.ErrInvalidPacketID 40 | } 41 | 42 | if err := packet.Scan( 43 | &pk.ProtocolVersion, 44 | &pk.ServerAddress, 45 | &pk.ServerPort, 46 | &pk.NextState, 47 | ); err != nil { 48 | return pk, err 49 | } 50 | 51 | return pk, nil 52 | } 53 | 54 | func (pk ServerBoundHandshake) IsStatusRequest() bool { 55 | return pk.NextState == ServerBoundHandshakeStatusState 56 | } 57 | 58 | func (pk ServerBoundHandshake) IsLoginRequest() bool { 59 | return pk.NextState == ServerBoundHandshakeLoginState 60 | } 61 | 62 | func (pk ServerBoundHandshake) IsForgeAddress() bool { 63 | addr := string(pk.ServerAddress) 64 | 65 | if strings.HasSuffix(addr, ForgeAddressSuffix) { 66 | return true 67 | } 68 | 69 | if strings.HasSuffix(addr, Forge2AddressSuffix) { 70 | return true 71 | } 72 | 73 | return false 74 | } 75 | 76 | func (pk ServerBoundHandshake) ParseServerAddress() string { 77 | addr := string(pk.ServerAddress) 78 | addr = strings.TrimSuffix(addr, ForgeAddressSuffix) 79 | addr = strings.TrimSuffix(addr, Forge2AddressSuffix) 80 | addr = strings.Trim(addr, ".") 81 | return addr 82 | } 83 | -------------------------------------------------------------------------------- /protocol/packets/handshaking/serverbound_handshake_test.go: -------------------------------------------------------------------------------- 1 | package handshaking 2 | 3 | import "testing" 4 | 5 | func BenchmarkHandshakingServerBoundHandshake_Marshal(b *testing.B) { 6 | isHandshakePk := ServerBoundHandshake{ 7 | ProtocolVersion: 578, 8 | ServerAddress: "spook.space", 9 | ServerPort: 25565, 10 | NextState: 1, 11 | } 12 | 13 | pk := isHandshakePk.Marshal() 14 | 15 | for n := 0; n < b.N; n++ { 16 | if _, err := UnmarshalServerBoundHandshake(pk); err != nil { 17 | b.Error(err) 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /protocol/packets/login/clientbound_disconnect.go: -------------------------------------------------------------------------------- 1 | package login 2 | 3 | import ( 4 | "github.com/specspace/plasma/protocol" 5 | ) 6 | 7 | const ClientBoundDisconnectPacketID byte = 0x00 8 | 9 | type ClientBoundDisconnect struct { 10 | Reason protocol.Chat 11 | } 12 | 13 | func (pk ClientBoundDisconnect) Marshal() protocol.Packet { 14 | return protocol.MarshalPacket( 15 | ClientBoundDisconnectPacketID, 16 | pk.Reason, 17 | ) 18 | } 19 | 20 | func UnmarshalClientBoundDisconnect(packet protocol.Packet) (ClientBoundDisconnect, error) { 21 | var pk ClientBoundDisconnect 22 | 23 | if packet.ID != ClientBoundDisconnectPacketID { 24 | return pk, protocol.ErrInvalidPacketID 25 | } 26 | 27 | if err := packet.Scan( 28 | &pk.Reason, 29 | ); err != nil { 30 | return pk, err 31 | } 32 | 33 | return pk, nil 34 | } 35 | -------------------------------------------------------------------------------- /protocol/packets/login/clientbound_encryptionrequest.go: -------------------------------------------------------------------------------- 1 | package login 2 | 3 | import ( 4 | "github.com/specspace/plasma/protocol" 5 | ) 6 | 7 | const ClientBoundEncryptionRequestPacketID byte = 0x01 8 | 9 | type ClientBoundEncryptionRequest struct { 10 | ServerID protocol.String 11 | PublicKey protocol.ByteArray 12 | VerifyToken protocol.ByteArray 13 | } 14 | 15 | func (pk ClientBoundEncryptionRequest) Marshal() protocol.Packet { 16 | return protocol.MarshalPacket( 17 | ClientBoundEncryptionRequestPacketID, 18 | pk.ServerID, 19 | pk.PublicKey, 20 | pk.VerifyToken, 21 | ) 22 | } 23 | 24 | func UnmarshalClientBoundEncryptionRequest(packet protocol.Packet) (ClientBoundEncryptionRequest, error) { 25 | var pk ClientBoundEncryptionRequest 26 | 27 | if packet.ID != ClientBoundEncryptionRequestPacketID { 28 | return pk, protocol.ErrInvalidPacketID 29 | } 30 | 31 | if err := packet.Scan( 32 | &pk.ServerID, 33 | &pk.PublicKey, 34 | &pk.VerifyToken, 35 | ); err != nil { 36 | return pk, err 37 | } 38 | 39 | return pk, nil 40 | } 41 | -------------------------------------------------------------------------------- /protocol/packets/login/clientbound_loginpluginrequest.go: -------------------------------------------------------------------------------- 1 | package login 2 | 3 | import ( 4 | "github.com/specspace/plasma/protocol" 5 | ) 6 | 7 | const ClientBoundLoginPluginRequestPacketID byte = 0x04 8 | 9 | type ClientBoundLoginPluginRequest struct { 10 | MessageID protocol.VarInt 11 | Channel protocol.Identifier 12 | Data protocol.OptionalByteArray 13 | } 14 | 15 | func (pk ClientBoundLoginPluginRequest) Marshal() protocol.Packet { 16 | return protocol.MarshalPacket( 17 | ClientBoundLoginPluginRequestPacketID, 18 | pk.MessageID, 19 | pk.Channel, 20 | pk.Data, 21 | ) 22 | } 23 | 24 | func UnmarshalClientBoundLoginPluginRequest(packet protocol.Packet) (ClientBoundLoginPluginRequest, error) { 25 | var pk ClientBoundLoginPluginRequest 26 | 27 | if packet.ID != ClientBoundLoginPluginRequestPacketID { 28 | return pk, protocol.ErrInvalidPacketID 29 | } 30 | 31 | if err := packet.Scan( 32 | &pk.MessageID, 33 | &pk.Channel, 34 | &pk.Data, 35 | ); err != nil { 36 | return pk, err 37 | } 38 | 39 | return pk, nil 40 | } 41 | -------------------------------------------------------------------------------- /protocol/packets/login/clientbound_loginsuccess.go: -------------------------------------------------------------------------------- 1 | package login 2 | 3 | import ( 4 | "github.com/specspace/plasma/protocol" 5 | ) 6 | 7 | const ClientBoundLoginSuccessPacketID byte = 0x02 8 | 9 | type ClientBoundLoginSuccess struct { 10 | UUID protocol.UUID 11 | Username protocol.String 12 | } 13 | 14 | func (pk ClientBoundLoginSuccess) Marshal() protocol.Packet { 15 | return protocol.MarshalPacket( 16 | ClientBoundLoginSuccessPacketID, 17 | pk.UUID, 18 | pk.Username, 19 | ) 20 | } 21 | 22 | func ParseClientBoundLoginSuccess(packet protocol.Packet) (ClientBoundLoginSuccess, error) { 23 | var pk ClientBoundLoginSuccess 24 | 25 | if packet.ID != ClientBoundLoginSuccessPacketID { 26 | return pk, protocol.ErrInvalidPacketID 27 | } 28 | 29 | if err := packet.Scan( 30 | &pk.UUID, 31 | &pk.Username, 32 | ); err != nil { 33 | return pk, err 34 | } 35 | 36 | return pk, nil 37 | } 38 | -------------------------------------------------------------------------------- /protocol/packets/login/clientbound_setcompression.go: -------------------------------------------------------------------------------- 1 | package login 2 | 3 | import ( 4 | "github.com/specspace/plasma/protocol" 5 | ) 6 | 7 | const ClientBoundSetCompressionPacketID byte = 0x03 8 | 9 | type ClientBoundSetCompression struct { 10 | Threshold protocol.VarInt 11 | } 12 | 13 | func (pk ClientBoundSetCompression) Marshal() protocol.Packet { 14 | return protocol.MarshalPacket( 15 | ClientBoundSetCompressionPacketID, 16 | pk.Threshold, 17 | ) 18 | } 19 | 20 | func ParseClientBoundSetCompression(packet protocol.Packet) (ClientBoundSetCompression, error) { 21 | var pk ClientBoundSetCompression 22 | 23 | if packet.ID != ClientBoundSetCompressionPacketID { 24 | return pk, protocol.ErrInvalidPacketID 25 | } 26 | 27 | if err := packet.Scan(&pk.Threshold); err != nil { 28 | return pk, err 29 | } 30 | 31 | return pk, nil 32 | } 33 | -------------------------------------------------------------------------------- /protocol/packets/login/serverbound_encryptionresponse.go: -------------------------------------------------------------------------------- 1 | package login 2 | 3 | import ( 4 | "github.com/specspace/plasma/protocol" 5 | ) 6 | 7 | const ServerBoundEncryptionResponsePacketID = 0x01 8 | 9 | type ServerBoundEncryptionResponse struct { 10 | SharedSecret protocol.ByteArray 11 | VerifyToken protocol.ByteArray 12 | } 13 | 14 | func (pk ServerBoundEncryptionResponse) Marshal() protocol.Packet { 15 | return protocol.MarshalPacket( 16 | ServerBoundEncryptionResponsePacketID, 17 | pk.SharedSecret, 18 | pk.VerifyToken, 19 | ) 20 | } 21 | 22 | func UnmarshalServerBoundEncryptionResponse(packet protocol.Packet) (ServerBoundEncryptionResponse, error) { 23 | var pk ServerBoundEncryptionResponse 24 | 25 | if packet.ID != ServerBoundEncryptionResponsePacketID { 26 | return pk, protocol.ErrInvalidPacketID 27 | } 28 | 29 | if err := packet.Scan( 30 | &pk.SharedSecret, 31 | &pk.VerifyToken, 32 | ); err != nil { 33 | return pk, err 34 | } 35 | 36 | return pk, nil 37 | } 38 | -------------------------------------------------------------------------------- /protocol/packets/login/serverbound_loginpluginresponse.go: -------------------------------------------------------------------------------- 1 | package login 2 | 3 | import ( 4 | "github.com/specspace/plasma/protocol" 5 | ) 6 | 7 | const ServerBoundLoginPluginResponsePacketID byte = 0x04 8 | 9 | type ServerBoundLoginPluginResponse struct { 10 | MessageID protocol.VarInt 11 | Successful protocol.Boolean 12 | Data protocol.OptionalByteArray 13 | } 14 | 15 | func (pk ServerBoundLoginPluginResponse) Marshal() protocol.Packet { 16 | return protocol.MarshalPacket( 17 | ServerBoundLoginPluginResponsePacketID, 18 | pk.MessageID, 19 | pk.Successful, 20 | pk.Data, 21 | ) 22 | } 23 | 24 | func UnmarshalServerBoundLoginPluginResponse(packet protocol.Packet) (ServerBoundLoginPluginResponse, error) { 25 | var pk ServerBoundLoginPluginResponse 26 | 27 | if packet.ID != ServerBoundLoginPluginResponsePacketID { 28 | return pk, protocol.ErrInvalidPacketID 29 | } 30 | 31 | if err := packet.Scan( 32 | &pk.MessageID, 33 | &pk.Successful, 34 | &pk.Data, 35 | ); err != nil { 36 | return pk, err 37 | } 38 | 39 | return pk, nil 40 | } 41 | -------------------------------------------------------------------------------- /protocol/packets/login/serverbound_loginstart.go: -------------------------------------------------------------------------------- 1 | package login 2 | 3 | import ( 4 | "github.com/specspace/plasma/protocol" 5 | ) 6 | 7 | const ServerBoundLoginStartPacketID byte = 0x00 8 | 9 | type ServerLoginStart struct { 10 | Name protocol.String 11 | } 12 | 13 | func (pk ServerLoginStart) Marshal() protocol.Packet { 14 | return protocol.MarshalPacket( 15 | ClientBoundSetCompressionPacketID, 16 | pk.Name, 17 | ) 18 | } 19 | 20 | func UnmarshalServerBoundLoginStart(packet protocol.Packet) (ServerLoginStart, error) { 21 | var pk ServerLoginStart 22 | 23 | if packet.ID != ServerBoundLoginStartPacketID { 24 | return pk, protocol.ErrInvalidPacketID 25 | } 26 | 27 | if err := packet.Scan(&pk.Name); err != nil { 28 | return pk, err 29 | } 30 | 31 | return pk, nil 32 | } 33 | -------------------------------------------------------------------------------- /protocol/packets/play/clientbound_declarecommands.go: -------------------------------------------------------------------------------- 1 | package play 2 | 3 | import ( 4 | "github.com/specspace/plasma/protocol" 5 | ) 6 | 7 | const ClientBoundDeclareCommandsPacketID byte = 0x17 8 | 9 | type ClientBoundDeclareCommands struct { 10 | Nodes Nodes 11 | RootIndex protocol.VarInt 12 | } 13 | 14 | type Node struct{} 15 | 16 | type Nodes []Node 17 | 18 | func (nodes Nodes) Encode() []byte { 19 | b := protocol.VarInt(len(nodes)).Encode() 20 | return b 21 | } 22 | 23 | func (nodes *Nodes) Decode(r protocol.DecodeReader) error { 24 | var length protocol.VarInt 25 | if err := length.Decode(r); err != nil { 26 | return err 27 | } 28 | *nodes = make([]Node, length) 29 | return nil 30 | } 31 | 32 | func (pk ClientBoundDeclareCommands) Marshal() protocol.Packet { 33 | return protocol.MarshalPacket( 34 | ClientBoundDeclareCommandsPacketID, 35 | pk.Nodes, 36 | pk.RootIndex, 37 | ) 38 | } 39 | 40 | func UnmarshalClientBoundDeclareCommands(packet protocol.Packet) (ClientBoundDeclareCommands, error) { 41 | var pk ClientBoundDeclareCommands 42 | 43 | if packet.ID != ClientBoundDeclareCommandsPacketID { 44 | return pk, protocol.ErrInvalidPacketID 45 | } 46 | 47 | if err := packet.Scan( 48 | &pk.Nodes, 49 | &pk.RootIndex, 50 | ); err != nil { 51 | return pk, err 52 | } 53 | 54 | return pk, nil 55 | } 56 | -------------------------------------------------------------------------------- /protocol/packets/play/clientbound_declarerecipes.go: -------------------------------------------------------------------------------- 1 | package play 2 | 3 | import ( 4 | "github.com/specspace/plasma/protocol" 5 | ) 6 | 7 | const ClientBoundDeclareRecipesPacketID byte = 0x5b 8 | 9 | type ClientBoundDeclareRecipes struct { 10 | Recipes Recipes 11 | } 12 | 13 | func (pk ClientBoundDeclareRecipes) Marshal() protocol.Packet { 14 | return protocol.MarshalPacket( 15 | ClientBoundDeclareRecipesPacketID, 16 | pk.Recipes, 17 | ) 18 | } 19 | 20 | func UnmarshalClientBoundDeclareRecipes(packet protocol.Packet) (ClientBoundDeclareRecipes, error) { 21 | var pk ClientBoundDeclareRecipes 22 | 23 | if packet.ID != ClientBoundDeclareRecipesPacketID { 24 | return pk, protocol.ErrInvalidPacketID 25 | } 26 | 27 | if err := packet.Scan( 28 | &pk.Recipes, 29 | ); err != nil { 30 | return pk, err 31 | } 32 | 33 | return pk, nil 34 | } 35 | -------------------------------------------------------------------------------- /protocol/packets/play/clientbound_disconnect.go: -------------------------------------------------------------------------------- 1 | package play 2 | 3 | import "github.com/specspace/plasma/protocol" 4 | 5 | const ClientBoundDisconnectPacketID byte = 0x19 6 | 7 | type ClientBoundDisconnect struct { 8 | Reason protocol.Chat 9 | } 10 | 11 | func (pk ClientBoundDisconnect) Marshal() protocol.Packet { 12 | return protocol.MarshalPacket( 13 | ClientBoundDisconnectPacketID, 14 | pk.Reason, 15 | ) 16 | } 17 | 18 | func UnmarshalClientBoundDisconnect(packet protocol.Packet) (ClientBoundDisconnect, error) { 19 | var pk ClientBoundDisconnect 20 | 21 | if packet.ID != ClientBoundDisconnectPacketID { 22 | return pk, protocol.ErrInvalidPacketID 23 | } 24 | 25 | if err := packet.Scan( 26 | &pk.Reason, 27 | ); err != nil { 28 | return pk, err 29 | } 30 | 31 | return pk, nil 32 | } 33 | -------------------------------------------------------------------------------- /protocol/packets/play/clientbound_entitystatus.go: -------------------------------------------------------------------------------- 1 | package play 2 | 3 | import ( 4 | "github.com/specspace/plasma/protocol" 5 | ) 6 | 7 | const ClientBoundEntityStatusPacketID byte = 0x17 8 | 9 | type ClientBoundEntityStatus struct { 10 | EntityID protocol.Int 11 | EntityStatus protocol.Byte 12 | } 13 | 14 | func (pk ClientBoundEntityStatus) Marshal() protocol.Packet { 15 | return protocol.MarshalPacket( 16 | ClientBoundEntityStatusPacketID, 17 | pk.EntityID, 18 | pk.EntityStatus, 19 | ) 20 | } 21 | 22 | func UnmarshalClientBoundEntityStatus(packet protocol.Packet) (ClientBoundEntityStatus, error) { 23 | var pk ClientBoundEntityStatus 24 | 25 | if packet.ID != ClientBoundEntityStatusPacketID { 26 | return pk, protocol.ErrInvalidPacketID 27 | } 28 | 29 | if err := packet.Scan( 30 | &pk.EntityID, 31 | &pk.EntityStatus, 32 | ); err != nil { 33 | return pk, err 34 | } 35 | 36 | return pk, nil 37 | } 38 | -------------------------------------------------------------------------------- /protocol/packets/play/clientbound_helditemchange.go: -------------------------------------------------------------------------------- 1 | package play 2 | 3 | import ( 4 | "github.com/specspace/plasma/protocol" 5 | ) 6 | 7 | const ClientBoundHeldItemChangePacketID byte = 0x40 8 | 9 | type ClientBoundHeldItemChange struct { 10 | Slot protocol.Byte 11 | } 12 | 13 | func (pk ClientBoundHeldItemChange) Marshal() protocol.Packet { 14 | return protocol.MarshalPacket( 15 | ClientBoundHeldItemChangePacketID, 16 | pk.Slot, 17 | ) 18 | } 19 | 20 | func UnmarshalClientBoundHeldItemChange(packet protocol.Packet) (ClientBoundHeldItemChange, error) { 21 | var pk ClientBoundHeldItemChange 22 | 23 | if packet.ID != ClientBoundHeldItemChangePacketID { 24 | return pk, protocol.ErrInvalidPacketID 25 | } 26 | 27 | if err := packet.Scan( 28 | &pk.Slot, 29 | ); err != nil { 30 | return pk, err 31 | } 32 | 33 | return pk, nil 34 | } 35 | -------------------------------------------------------------------------------- /protocol/packets/play/clientbound_joingame.go: -------------------------------------------------------------------------------- 1 | package play 2 | 3 | import ( 4 | "github.com/specspace/plasma/protocol" 5 | ) 6 | 7 | const ( 8 | ClientBoundJoinGamePacketID byte = 0x24 9 | ) 10 | 11 | type ClientBoundJoinGame struct { 12 | EntityID protocol.Int 13 | IsHardcore protocol.Boolean 14 | Gamemode protocol.UnsignedByte 15 | PreviousGamemode protocol.Byte 16 | WorldNames protocol.IdentifierArray 17 | DimensionCodec protocol.NBT 18 | Dimension protocol.NBT 19 | WorldName protocol.Identifier 20 | HashedSeed protocol.Long 21 | MaxPlayers protocol.VarInt 22 | ViewDistance protocol.VarInt 23 | ReducedDebugInfo protocol.Boolean 24 | EnableRespawnScreen protocol.Boolean 25 | IsDebug protocol.Boolean 26 | IsFlat protocol.Boolean 27 | } 28 | 29 | type DimensionCodecVanilla struct { 30 | Name string `nbt:"name"` 31 | PiglinSafe byte `nbt:"piglin_safe"` 32 | Natural byte `nbt:"natural"` 33 | AmbientLight float32 `nbt:"ambient_light"` 34 | FixedTime int `nbt:"fixed_time"` 35 | Infiniburn string `nbt:"infiniburn"` 36 | RespawnAnchorWorks byte `nbt:"respawn_anchor_works"` 37 | HasSkylight byte `nbt:"has_skylight"` 38 | BedWorks byte `nbt:"bed_works"` 39 | Effects string `nbt:"effects"` 40 | HasRaids byte `nbt:"has_raids"` 41 | LogicalHeight int `nbt:"logical_height"` 42 | CoordinateScale float32 `nbt:"coordinate_scale"` 43 | Ultrawarm byte `nbt:"ultrawarm"` 44 | HasCeiling byte `nbt:"has_ceiling"` 45 | } 46 | 47 | func (pk ClientBoundJoinGame) Marshal() protocol.Packet { 48 | return protocol.MarshalPacket( 49 | ClientBoundJoinGamePacketID, 50 | pk.EntityID, 51 | pk.IsHardcore, 52 | pk.Gamemode, 53 | pk.PreviousGamemode, 54 | pk.WorldNames, 55 | pk.DimensionCodec, 56 | pk.Dimension, 57 | pk.WorldName, 58 | pk.HashedSeed, 59 | pk.MaxPlayers, 60 | pk.ViewDistance, 61 | pk.ReducedDebugInfo, 62 | pk.EnableRespawnScreen, 63 | pk.IsDebug, 64 | pk.IsFlat, 65 | ) 66 | } 67 | 68 | func UnmarshalClientBoundJoinGame(packet protocol.Packet) (ClientBoundJoinGame, error) { 69 | var pk ClientBoundJoinGame 70 | 71 | if packet.ID != ClientBoundJoinGamePacketID { 72 | return pk, protocol.ErrInvalidPacketID 73 | } 74 | 75 | if err := packet.Scan( 76 | &pk.EntityID, 77 | &pk.IsHardcore, 78 | &pk.Gamemode, 79 | &pk.PreviousGamemode, 80 | &pk.WorldNames, 81 | &pk.DimensionCodec, 82 | &pk.Dimension, 83 | &pk.WorldName, 84 | &pk.HashedSeed, 85 | &pk.MaxPlayers, 86 | &pk.ViewDistance, 87 | &pk.ReducedDebugInfo, 88 | &pk.EnableRespawnScreen, 89 | &pk.IsDebug, 90 | &pk.IsFlat, 91 | ); err != nil { 92 | return pk, err 93 | } 94 | 95 | return pk, nil 96 | } 97 | -------------------------------------------------------------------------------- /protocol/packets/play/clientbound_playerabilities.go: -------------------------------------------------------------------------------- 1 | package play 2 | 3 | import ( 4 | "github.com/specspace/plasma/protocol" 5 | ) 6 | 7 | const ClientBoundPlayerAbilitiesPacketID byte = 0x30 8 | 9 | type ClientBoundPlayerAbilities struct { 10 | Flags protocol.Byte 11 | FlyingSpeed protocol.Float 12 | FieldOfViewModifier protocol.Float 13 | } 14 | 15 | func (pk ClientBoundPlayerAbilities) Marshal() protocol.Packet { 16 | return protocol.MarshalPacket( 17 | ClientBoundPlayerAbilitiesPacketID, 18 | pk.Flags, 19 | pk.FlyingSpeed, 20 | pk.FieldOfViewModifier, 21 | ) 22 | } 23 | 24 | func UnmarshalClientBoundPlayerAbilities(packet protocol.Packet) (ClientBoundPlayerAbilities, error) { 25 | var pk ClientBoundPlayerAbilities 26 | 27 | if packet.ID != ClientBoundPlayerAbilitiesPacketID { 28 | return pk, protocol.ErrInvalidPacketID 29 | } 30 | 31 | if err := packet.Scan( 32 | &pk.Flags, 33 | &pk.FlyingSpeed, 34 | &pk.FieldOfViewModifier, 35 | ); err != nil { 36 | return pk, err 37 | } 38 | 39 | return pk, nil 40 | } 41 | -------------------------------------------------------------------------------- /protocol/packets/play/clientbound_playerpositionandlook.go: -------------------------------------------------------------------------------- 1 | package play 2 | 3 | import ( 4 | "github.com/specspace/plasma/protocol" 5 | ) 6 | 7 | const ClientBoundPlayerPositionAndLookPacketID byte = 0x34 8 | 9 | type ClientBoundPlayerPositionAndLook struct { 10 | X protocol.Double 11 | Y protocol.Double 12 | Z protocol.Double 13 | Yaw protocol.Float 14 | Pitch protocol.Float 15 | Flags protocol.Byte 16 | TeleportID protocol.VarInt 17 | } 18 | 19 | func (pk ClientBoundPlayerPositionAndLook) Marshal() protocol.Packet { 20 | return protocol.MarshalPacket( 21 | ClientBoundPlayerPositionAndLookPacketID, 22 | pk.X, 23 | pk.Y, 24 | pk.Z, 25 | pk.Yaw, 26 | pk.Pitch, 27 | pk.Flags, 28 | pk.TeleportID, 29 | ) 30 | } 31 | 32 | func UnmarshalClientBoundPlayerPositionAndLook(packet protocol.Packet) (ClientBoundPlayerPositionAndLook, error) { 33 | var pk ClientBoundPlayerPositionAndLook 34 | 35 | if packet.ID != ClientBoundPlayerPositionAndLookPacketID { 36 | return pk, protocol.ErrInvalidPacketID 37 | } 38 | 39 | if err := packet.Scan( 40 | &pk.X, 41 | &pk.Y, 42 | &pk.Z, 43 | &pk.Yaw, 44 | &pk.Pitch, 45 | &pk.Flags, 46 | &pk.TeleportID, 47 | ); err != nil { 48 | return pk, err 49 | } 50 | 51 | return pk, nil 52 | } 53 | -------------------------------------------------------------------------------- /protocol/packets/play/clientbound_pluginmessage.go: -------------------------------------------------------------------------------- 1 | package play 2 | 3 | import ( 4 | "github.com/specspace/plasma/protocol" 5 | ) 6 | 7 | const ClientBoundPluginMessagePacketID byte = 0x17 8 | 9 | type ClientBoundPluginMessage struct { 10 | Channel protocol.Identifier 11 | Data protocol.OptionalByteArray 12 | } 13 | 14 | func (pk ClientBoundPluginMessage) Marshal() protocol.Packet { 15 | return protocol.MarshalPacket( 16 | ClientBoundPluginMessagePacketID, 17 | pk.Channel, 18 | pk.Data, 19 | ) 20 | } 21 | 22 | func UnmarshalClientBoundPluginMessage(packet protocol.Packet) (ClientBoundPluginMessage, error) { 23 | var pk ClientBoundPluginMessage 24 | 25 | if packet.ID != ClientBoundPluginMessagePacketID { 26 | return pk, protocol.ErrInvalidPacketID 27 | } 28 | 29 | if err := packet.Scan( 30 | &pk.Channel, 31 | &pk.Data, 32 | ); err != nil { 33 | return pk, err 34 | } 35 | 36 | return pk, nil 37 | } 38 | -------------------------------------------------------------------------------- /protocol/packets/play/clientbound_serverdifficulty.go: -------------------------------------------------------------------------------- 1 | package play 2 | 3 | import ( 4 | "github.com/specspace/plasma/protocol" 5 | ) 6 | 7 | const ClientBoundServerDifficultyPacketID byte = 0x0d 8 | 9 | type ClientBoundServerDifficulty struct { 10 | Difficulty protocol.UnsignedByte 11 | IsDifficultyLocked protocol.Boolean 12 | } 13 | 14 | func (pk ClientBoundServerDifficulty) Marshal() protocol.Packet { 15 | return protocol.MarshalPacket( 16 | ClientBoundServerDifficultyPacketID, 17 | pk.Difficulty, 18 | pk.IsDifficultyLocked, 19 | ) 20 | } 21 | 22 | func UnmarshalClientBoundServerDifficulty(packet protocol.Packet) (ClientBoundServerDifficulty, error) { 23 | var pk ClientBoundServerDifficulty 24 | 25 | if packet.ID != ClientBoundServerDifficultyPacketID { 26 | return pk, protocol.ErrInvalidPacketID 27 | } 28 | 29 | if err := packet.Scan( 30 | &pk.Difficulty, 31 | &pk.IsDifficultyLocked, 32 | ); err != nil { 33 | return pk, err 34 | } 35 | 36 | return pk, nil 37 | } 38 | -------------------------------------------------------------------------------- /protocol/packets/play/clientbound_spawnentity.go: -------------------------------------------------------------------------------- 1 | package play 2 | 3 | import ( 4 | "github.com/specspace/plasma/protocol" 5 | ) 6 | 7 | const ClientBoundSpawnEntityPacketID byte = 0x00 8 | 9 | type ClientBoundSpawnEntity struct { 10 | EntityID protocol.VarInt 11 | ObjectUUID protocol.UUID 12 | Type protocol.VarInt 13 | X protocol.Double 14 | Y protocol.Double 15 | Z protocol.Double 16 | Pitch protocol.Angle 17 | Yaw protocol.Angle 18 | VelocityX protocol.Short 19 | VelocityY protocol.Short 20 | VelocityZ protocol.Short 21 | } 22 | 23 | func (pk ClientBoundSpawnEntity) Marshal() protocol.Packet { 24 | return protocol.MarshalPacket( 25 | ClientBoundSpawnEntityPacketID, 26 | pk.EntityID, 27 | pk.ObjectUUID, 28 | pk.Type, 29 | pk.X, 30 | pk.Y, 31 | pk.Z, 32 | pk.Pitch, 33 | pk.Yaw, 34 | pk.VelocityX, 35 | pk.VelocityY, 36 | pk.VelocityZ, 37 | ) 38 | } 39 | 40 | func UnmarshalClientBoundSpawnEntity(packet protocol.Packet) (ClientBoundSpawnEntity, error) { 41 | var pk ClientBoundSpawnEntity 42 | 43 | if packet.ID != ClientBoundSpawnEntityPacketID { 44 | return pk, protocol.ErrInvalidPacketID 45 | } 46 | 47 | if err := packet.Scan( 48 | &pk.EntityID, 49 | &pk.ObjectUUID, 50 | &pk.Type, 51 | &pk.X, 52 | &pk.Y, 53 | &pk.Z, 54 | &pk.Pitch, 55 | &pk.Yaw, 56 | &pk.VelocityX, 57 | &pk.VelocityY, 58 | &pk.VelocityZ, 59 | ); err != nil { 60 | return pk, err 61 | } 62 | 63 | return pk, nil 64 | } 65 | -------------------------------------------------------------------------------- /protocol/packets/play/clientbound_spawnexperienceorb.go: -------------------------------------------------------------------------------- 1 | package play 2 | 3 | import ( 4 | "github.com/specspace/plasma/protocol" 5 | ) 6 | 7 | const ClientBoundSpawnExperienceOrbPacketID byte = 0x01 8 | 9 | type ClientBoundSpawnExperienceOrb struct { 10 | EntityID protocol.VarInt 11 | X protocol.Double 12 | Y protocol.Double 13 | Z protocol.Double 14 | Count protocol.Short 15 | } 16 | 17 | func (pk ClientBoundSpawnExperienceOrb) Marshal() protocol.Packet { 18 | return protocol.MarshalPacket( 19 | ClientBoundSpawnExperienceOrbPacketID, 20 | pk.EntityID, 21 | pk.X, 22 | pk.Y, 23 | pk.Z, 24 | pk.Count, 25 | ) 26 | } 27 | 28 | func UnmarshalClientBoundSpawnExperienceOrb(packet protocol.Packet) (ClientBoundSpawnExperienceOrb, error) { 29 | var pk ClientBoundSpawnExperienceOrb 30 | 31 | if packet.ID != ClientBoundSpawnExperienceOrbPacketID { 32 | return pk, protocol.ErrInvalidPacketID 33 | } 34 | 35 | if err := packet.Scan( 36 | &pk.EntityID, 37 | &pk.X, 38 | &pk.Y, 39 | &pk.Z, 40 | &pk.Count, 41 | ); err != nil { 42 | return pk, err 43 | } 44 | 45 | return pk, nil 46 | } 47 | -------------------------------------------------------------------------------- /protocol/packets/play/clientbound_tags.go: -------------------------------------------------------------------------------- 1 | package play 2 | 3 | import ( 4 | "github.com/specspace/plasma/protocol" 5 | ) 6 | 7 | const ClientBoundTagsPacketID byte = 0x5c 8 | 9 | type ClientBoundTags struct { 10 | BlockTags Tags 11 | ItemTags Tags 12 | FluidTags Tags 13 | EntityTags Tags 14 | } 15 | 16 | type Tag struct { 17 | Type protocol.Identifier 18 | Entries protocol.VarIntArray 19 | } 20 | 21 | type Tags []Tag 22 | 23 | func (tags Tags) Encode() []byte { 24 | b := protocol.VarInt(len(tags)).Encode() 25 | for _, tag := range tags { 26 | b = append(b, tag.Type.Encode()...) 27 | b = append(b, tag.Entries.Encode()...) 28 | } 29 | return b 30 | } 31 | 32 | func (tags *Tags) Decode(r protocol.DecodeReader) error { 33 | var length protocol.VarInt 34 | if err := length.Decode(r); err != nil { 35 | return err 36 | } 37 | *tags = make([]Tag, length) 38 | for _, tag := range *tags { 39 | if err := tag.Type.Decode(r); err != nil { 40 | return err 41 | } 42 | if err := tag.Entries.Decode(r); err != nil { 43 | return err 44 | } 45 | } 46 | return nil 47 | } 48 | 49 | func (pk ClientBoundTags) Marshal() protocol.Packet { 50 | return protocol.MarshalPacket( 51 | ClientBoundTagsPacketID, 52 | pk.BlockTags, 53 | pk.ItemTags, 54 | pk.FluidTags, 55 | pk.EntityTags, 56 | ) 57 | } 58 | 59 | func UnmarshalClientBoundTags(packet protocol.Packet) (ClientBoundTags, error) { 60 | var pk ClientBoundTags 61 | 62 | if packet.ID != ClientBoundTagsPacketID { 63 | return pk, protocol.ErrInvalidPacketID 64 | } 65 | 66 | if err := packet.Scan( 67 | &pk.BlockTags, 68 | &pk.ItemTags, 69 | &pk.FluidTags, 70 | &pk.EntityTags, 71 | ); err != nil { 72 | return pk, err 73 | } 74 | 75 | return pk, nil 76 | } 77 | -------------------------------------------------------------------------------- /protocol/packets/play/clientbound_unlockrecipes.go: -------------------------------------------------------------------------------- 1 | package play 2 | 3 | import "github.com/specspace/plasma/protocol" 4 | 5 | const ClientBoundUnlockRecipesPacketID byte = 0x35 6 | 7 | type ClientBoundUnlockRecipes struct { 8 | Action protocol.VarInt 9 | CraftingRecipeBookFilterActive protocol.Boolean 10 | CraftingRecipeBookOpen protocol.Boolean 11 | SmeltingRecipeBookFilterActive protocol.Boolean 12 | SmeltingRecipeBookOpen protocol.Boolean 13 | BlastFurnaceRecipeBookFilterActive protocol.Boolean 14 | BlastFurnaceRecipeBookOpen protocol.Boolean 15 | SmokerRecipeBookFilterActive protocol.Boolean 16 | SmokerRecipeBookOpen protocol.Boolean 17 | RecipeIDs1 protocol.IdentifierArray 18 | RecipeIDs2 protocol.IdentifierArray 19 | } 20 | 21 | func (pk ClientBoundUnlockRecipes) Marshal() protocol.Packet { 22 | return protocol.MarshalPacket( 23 | ClientBoundUnlockRecipesPacketID, 24 | pk.Action, 25 | pk.CraftingRecipeBookFilterActive, 26 | pk.CraftingRecipeBookOpen, 27 | pk.SmeltingRecipeBookFilterActive, 28 | pk.SmeltingRecipeBookOpen, 29 | pk.BlastFurnaceRecipeBookFilterActive, 30 | pk.BlastFurnaceRecipeBookOpen, 31 | pk.SmokerRecipeBookFilterActive, 32 | pk.SmokerRecipeBookOpen, 33 | pk.RecipeIDs1, 34 | pk.RecipeIDs2, 35 | ) 36 | } 37 | 38 | func UnmarshalClientBoundUnlockRecipes(packet protocol.Packet) (ClientBoundUnlockRecipes, error) { 39 | var pk ClientBoundUnlockRecipes 40 | 41 | if packet.ID != ClientBoundUnlockRecipesPacketID { 42 | return pk, protocol.ErrInvalidPacketID 43 | } 44 | 45 | if err := packet.Scan( 46 | &pk.Action, 47 | &pk.CraftingRecipeBookFilterActive, 48 | &pk.CraftingRecipeBookOpen, 49 | &pk.SmeltingRecipeBookFilterActive, 50 | &pk.SmeltingRecipeBookOpen, 51 | &pk.BlastFurnaceRecipeBookFilterActive, 52 | &pk.BlastFurnaceRecipeBookOpen, 53 | &pk.SmokerRecipeBookFilterActive, 54 | &pk.SmokerRecipeBookOpen, 55 | &pk.RecipeIDs1, 56 | &pk.RecipeIDs2, 57 | ); err != nil { 58 | return pk, err 59 | } 60 | 61 | return pk, nil 62 | } 63 | -------------------------------------------------------------------------------- /protocol/packets/play/difficulty.go: -------------------------------------------------------------------------------- 1 | package play 2 | 3 | import ( 4 | "github.com/specspace/plasma/protocol" 5 | ) 6 | 7 | const ( 8 | DifficultyPeaceful = protocol.UnsignedByte(0x0) 9 | DifficultyEasy = protocol.UnsignedByte(0x1) 10 | DifficultyNormal = protocol.UnsignedByte(0x2) 11 | DifficultyHard = protocol.UnsignedByte(0x3) 12 | ) 13 | -------------------------------------------------------------------------------- /protocol/packets/play/dimension.go: -------------------------------------------------------------------------------- 1 | package play 2 | 3 | import ( 4 | "github.com/specspace/plasma/protocol" 5 | ) 6 | 7 | const ( 8 | DimensionNether = protocol.Int(-1) 9 | DimensionOverworld = protocol.Int(0) 10 | DimensionEnd = protocol.Int(1) 11 | ) 12 | -------------------------------------------------------------------------------- /protocol/packets/play/gamemode.go: -------------------------------------------------------------------------------- 1 | package play 2 | 3 | import ( 4 | "github.com/specspace/plasma/protocol" 5 | ) 6 | 7 | const ( 8 | GamemodeSurvival = protocol.UnsignedByte(0x0) 9 | GamemodeCreative = protocol.UnsignedByte(0x1) 10 | GamemodeAdventure = protocol.UnsignedByte(0x2) 11 | GamemodeSpectator = protocol.UnsignedByte(0x3) 12 | ) 13 | -------------------------------------------------------------------------------- /protocol/packets/play/ingredient.go: -------------------------------------------------------------------------------- 1 | package play 2 | 3 | import ( 4 | "errors" 5 | "github.com/specspace/plasma/protocol" 6 | ) 7 | 8 | type Ingredient struct { 9 | Items Slots 10 | } 11 | 12 | func (ingredient Ingredient) Encode() []byte { 13 | return ingredient.Items.Encode() 14 | } 15 | 16 | func (ingredient *Ingredient) Decode(r protocol.DecodeReader) error { 17 | return ingredient.Items.Decode(r) 18 | } 19 | 20 | type Ingredients []Ingredient 21 | 22 | func (ingredients Ingredients) Encode() []byte { 23 | b := protocol.VarInt(len(ingredients)).Encode() 24 | for _, ingredient := range ingredients { 25 | b = append(b, ingredient.Encode()...) 26 | } 27 | return b 28 | } 29 | 30 | func (ingredients *Ingredients) Decode(r protocol.DecodeReader) error { 31 | var length protocol.VarInt 32 | if err := length.Decode(r); err != nil { 33 | return err 34 | } 35 | *ingredients = make([]Ingredient, length) 36 | for _, ingredient := range *ingredients { 37 | if err := ingredient.Decode(r); err != nil { 38 | return err 39 | } 40 | } 41 | return nil 42 | } 43 | 44 | type IngredientsShaped Ingredients 45 | 46 | func (ingredientsShaped IngredientsShaped) Encode() []byte { 47 | var b []byte 48 | for _, ingredient := range ingredientsShaped { 49 | b = append(b, ingredient.Encode()...) 50 | } 51 | return b 52 | } 53 | 54 | func (ingredientsShaped *IngredientsShaped) Decode(r protocol.DecodeReader) error { 55 | if ingredientsShaped == nil { 56 | return errors.New("shaped ingredient cannot be null") 57 | } 58 | 59 | for _, ingredient := range *ingredientsShaped { 60 | if err := ingredient.Decode(r); err != nil { 61 | return err 62 | } 63 | } 64 | return nil 65 | } 66 | -------------------------------------------------------------------------------- /protocol/packets/play/recipe.go: -------------------------------------------------------------------------------- 1 | package play 2 | 3 | import ( 4 | "github.com/specspace/plasma/protocol" 5 | ) 6 | 7 | const ( 8 | RecipeTypeCraftingShapeless RecipeType = "crafting_shapeless" 9 | RecipeTypeCraftingShaped RecipeType = "crafting_shaped" 10 | RecipeTypeCraftingSpecialArmorDye RecipeType = "crafting_special_armordye" 11 | RecipeTypeCraftingSpecialBookCloning RecipeType = "crafting_special_bookcloning" 12 | RecipeTypeCraftingSpecialMapCloning RecipeType = "crafting_special_mapcloning" 13 | RecipeTypeCraftingSpecialMapExtending RecipeType = "crafting_special_mapextending" 14 | RecipeTypeCraftingSpecialFireworkRocket RecipeType = "crafting_special_firework_rocket" 15 | RecipeTypeCraftingSpecialFireworkStar RecipeType = "crafting_special_firework_star" 16 | RecipeTypeCraftingSpecialFireworkStarFade RecipeType = "crafting_special_firework_star_fade" 17 | RecipeTypeCraftingSpecialRepairItem RecipeType = "crafting_special_repairitem" 18 | RecipeTypeCraftingSpecialTippedArrow RecipeType = "crafting_special_tippedarrow" 19 | RecipeTypeCraftingSpecialBannerDuplicate RecipeType = "crafting_special_bannerduplicate" 20 | RecipeTypeCraftingSpecialBannerAddPattern RecipeType = "crafting_special_banneraddpattern" 21 | RecipeTypeCraftingSpecialShieldDecoration RecipeType = "crafting_special_shielddecoration" 22 | RecipeTypeCraftingSpecialShulkerBoxColor RecipeType = "crafting_special_shulkerboxcoloring" 23 | RecipeTypeCraftingSpecialSuspiciousStew RecipeType = "crafting_special_suspiciousstew" 24 | RecipeTypeSmelting RecipeType = "smelting" 25 | RecipeTypeBlasting RecipeType = "blasting" 26 | RecipeTypeSmoking RecipeType = "smoking" 27 | RecipeTypeCampfireCooking RecipeType = "campfire_cooking" 28 | RecipeTypeStoneCutting RecipeType = "stonecutting" 29 | ) 30 | 31 | type RecipeCraftingShaped struct { 32 | Width protocol.VarInt 33 | Height protocol.VarInt 34 | Group protocol.String 35 | Ingredients IngredientsShaped 36 | Result Slot 37 | } 38 | 39 | func (recipe RecipeCraftingShaped) Encode() []byte { 40 | recipe.Ingredients = make([]Ingredient, recipe.Width*recipe.Height) 41 | return protocol.MarshalFields( 42 | recipe.Width, 43 | recipe.Height, 44 | recipe.Group, 45 | recipe.Ingredients, 46 | recipe.Result, 47 | ) 48 | } 49 | 50 | func (recipe *RecipeCraftingShaped) Decode(r protocol.DecodeReader) error { 51 | return protocol.ScanFields(r, 52 | &recipe.Width, 53 | &recipe.Height, 54 | &recipe.Group, 55 | &recipe.Ingredients, 56 | &recipe.Result, 57 | ) 58 | } 59 | 60 | type RecipeCraftingShapeless struct { 61 | Group protocol.String 62 | Ingredients Ingredients 63 | Result Slot 64 | } 65 | 66 | func (recipe RecipeCraftingShapeless) Encode() []byte { 67 | return protocol.MarshalFields( 68 | recipe.Group, 69 | recipe.Ingredients, 70 | recipe.Result, 71 | ) 72 | } 73 | 74 | func (recipe *RecipeCraftingShapeless) Decode(r protocol.DecodeReader) error { 75 | return protocol.ScanFields(r, 76 | &recipe.Group, 77 | &recipe.Ingredients, 78 | &recipe.Result, 79 | ) 80 | } 81 | 82 | type RecipeCooking struct { 83 | Group protocol.String 84 | Ingredient Ingredient 85 | Result Slot 86 | Experience protocol.Float 87 | CookingTime protocol.VarInt 88 | } 89 | 90 | func (recipe RecipeCooking) Encode() []byte { 91 | return protocol.MarshalFields( 92 | recipe.Group, 93 | recipe.Ingredient, 94 | recipe.Result, 95 | recipe.Experience, 96 | recipe.CookingTime, 97 | ) 98 | } 99 | 100 | func (recipe *RecipeCooking) Decode(r protocol.DecodeReader) error { 101 | return protocol.ScanFields(r, 102 | &recipe.Group, 103 | &recipe.Ingredient, 104 | &recipe.Result, 105 | &recipe.Experience, 106 | &recipe.CookingTime, 107 | ) 108 | } 109 | 110 | type RecipeStoneCutting struct { 111 | Group protocol.String 112 | Ingredient Ingredient 113 | Result Slot 114 | } 115 | 116 | func (recipe RecipeStoneCutting) Encode() []byte { 117 | return protocol.MarshalFields( 118 | recipe.Group, 119 | recipe.Ingredient, 120 | recipe.Result, 121 | ) 122 | } 123 | 124 | func (recipe *RecipeStoneCutting) Decode(r protocol.DecodeReader) error { 125 | return protocol.ScanFields(r, 126 | &recipe.Group, 127 | &recipe.Ingredient, 128 | &recipe.Result, 129 | ) 130 | } 131 | 132 | type RecipeType protocol.Identifier 133 | 134 | func (recipeType RecipeType) Encode() []byte { 135 | return recipeType.Encode() 136 | } 137 | 138 | func (recipeType *RecipeType) Decode(r protocol.DecodeReader) error { 139 | return recipeType.Decode(r) 140 | } 141 | 142 | type Recipe struct { 143 | Type RecipeType 144 | RecipeID protocol.String 145 | Data protocol.Field 146 | } 147 | 148 | func (recipe Recipe) Encode() []byte { 149 | return protocol.MarshalFields( 150 | recipe.Type, 151 | recipe.RecipeID, 152 | recipe.Data, 153 | ) 154 | } 155 | 156 | func (recipe *Recipe) Decode(r protocol.DecodeReader) error { 157 | switch recipe.Type { 158 | /*case RecipeTypeCraftingShaped: 159 | data, ok := recipe.Data.(RecipeCraftingShaped) 160 | if !ok 161 | b = append(b, data.Encode()...)*/ 162 | } 163 | 164 | return protocol.ScanFields(r, 165 | &recipe.Type, 166 | &recipe.RecipeID, 167 | //&recipe.Data, 168 | ) 169 | } 170 | 171 | /*func (recipe Recipe) DataAsCraftingShapeless() (RecipeCraftingShapeless, error) { 172 | ok, craftingShapeless := recipe.Data.(RecipeCraftingShapeless) 173 | if !ok { 174 | return RecipeCraftingShapeless{}, errors.New("") 175 | } 176 | 177 | return craftingShapeless, nil 178 | }*/ 179 | 180 | type Recipes []Recipe 181 | 182 | func (recipes Recipes) Encode() []byte { 183 | b := protocol.VarInt(len(recipes)).Encode() 184 | for _, recipe := range recipes { 185 | b = append(b, recipe.Encode()...) 186 | } 187 | return b 188 | } 189 | 190 | func (recipes *Recipes) Decode(r protocol.DecodeReader) error { 191 | var length protocol.VarInt 192 | if err := length.Decode(r); err != nil { 193 | return err 194 | } 195 | *recipes = make([]Recipe, length) 196 | for _, recipe := range *recipes { 197 | if err := recipe.Decode(r); err != nil { 198 | return err 199 | } 200 | } 201 | return nil 202 | } 203 | -------------------------------------------------------------------------------- /protocol/packets/play/serverbound_clientsettings.go: -------------------------------------------------------------------------------- 1 | package play 2 | 3 | import ( 4 | "github.com/specspace/plasma/protocol" 5 | ) 6 | 7 | const ServerBoundClientSettingsPacketID byte = 0x05 8 | 9 | type ServerBoundClientSettings struct { 10 | Locale protocol.String 11 | ViewDistance protocol.Byte 12 | ChatMode protocol.VarInt 13 | ChatColors protocol.Boolean 14 | DisplayedSkinParts protocol.UnsignedByte 15 | MainHand protocol.VarInt 16 | } 17 | 18 | func (pk ServerBoundClientSettings) Marshal() protocol.Packet { 19 | return protocol.MarshalPacket( 20 | ServerBoundClientSettingsPacketID, 21 | pk.Locale, 22 | pk.ViewDistance, 23 | pk.ChatMode, 24 | pk.ChatColors, 25 | pk.DisplayedSkinParts, 26 | pk.MainHand, 27 | ) 28 | } 29 | 30 | func UnmarshalServerBoundClientSettings(packet protocol.Packet) (ServerBoundClientSettings, error) { 31 | var pk ServerBoundClientSettings 32 | 33 | if packet.ID != ServerBoundClientSettingsPacketID { 34 | return pk, protocol.ErrInvalidPacketID 35 | } 36 | 37 | if err := packet.Scan( 38 | &pk.Locale, 39 | &pk.ViewDistance, 40 | &pk.ChatMode, 41 | &pk.ChatColors, 42 | &pk.DisplayedSkinParts, 43 | &pk.MainHand, 44 | ); err != nil { 45 | return pk, err 46 | } 47 | 48 | return pk, nil 49 | } 50 | -------------------------------------------------------------------------------- /protocol/packets/play/serverbound_pluginmessage.go: -------------------------------------------------------------------------------- 1 | package play 2 | 3 | import ( 4 | "github.com/specspace/plasma/protocol" 5 | ) 6 | 7 | const ServerBoundPluginMessagePacketID byte = 0x17 8 | 9 | type ServerBoundPluginMessage struct { 10 | Channel protocol.Identifier 11 | Data protocol.OptionalByteArray 12 | } 13 | 14 | func (pk ServerBoundPluginMessage) Marshal() protocol.Packet { 15 | return protocol.MarshalPacket( 16 | ServerBoundPluginMessagePacketID, 17 | pk.Channel, 18 | pk.Data, 19 | ) 20 | } 21 | 22 | func UnmarshalServerBoundPluginMessage(packet protocol.Packet) (ServerBoundPluginMessage, error) { 23 | var pk ServerBoundPluginMessage 24 | 25 | if packet.ID != ServerBoundPluginMessagePacketID { 26 | return pk, protocol.ErrInvalidPacketID 27 | } 28 | 29 | if err := packet.Scan( 30 | &pk.Channel, 31 | &pk.Data, 32 | ); err != nil { 33 | return pk, err 34 | } 35 | 36 | return pk, nil 37 | } 38 | -------------------------------------------------------------------------------- /protocol/packets/play/slot.go: -------------------------------------------------------------------------------- 1 | package play 2 | 3 | import ( 4 | "github.com/specspace/plasma/protocol" 5 | ) 6 | 7 | type Slot struct { 8 | Present protocol.Boolean 9 | ItemID protocol.VarInt 10 | ItemCount protocol.Byte 11 | NBT protocol.NBT 12 | } 13 | 14 | func (slot Slot) Encode() []byte { 15 | var b []byte 16 | b = append(b, slot.Present.Encode()...) 17 | if !slot.Present { 18 | return b 19 | } 20 | b = append(b, slot.ItemID.Encode()...) 21 | b = append(b, slot.ItemCount.Encode()...) 22 | b = append(b, slot.NBT.Encode()...) 23 | return b 24 | } 25 | 26 | func (slot *Slot) Decode(r protocol.DecodeReader) error { 27 | if err := slot.Present.Decode(r); err != nil { 28 | return err 29 | } 30 | if !slot.Present { 31 | return nil 32 | } 33 | if err := slot.ItemID.Decode(r); err != nil { 34 | return err 35 | } 36 | if err := slot.ItemCount.Decode(r); err != nil { 37 | return err 38 | } 39 | if err := slot.NBT.Decode(r); err != nil { 40 | return err 41 | } 42 | return nil 43 | } 44 | 45 | type Slots []Slot 46 | 47 | func (slots Slots) Encode() []byte { 48 | b := protocol.VarInt(len(slots)).Encode() 49 | for _, slot := range slots { 50 | b = append(b, slot.Encode()...) 51 | } 52 | return b 53 | } 54 | 55 | func (slots *Slots) Decode(r protocol.DecodeReader) error { 56 | var length protocol.VarInt 57 | if err := length.Decode(r); err != nil { 58 | return err 59 | } 60 | *slots = make([]Slot, length) 61 | for _, slot := range *slots { 62 | if err := slot.Decode(r); err != nil { 63 | return err 64 | } 65 | } 66 | return nil 67 | } 68 | -------------------------------------------------------------------------------- /protocol/packets/status/clientbound_pong.go: -------------------------------------------------------------------------------- 1 | package status 2 | 3 | import ( 4 | "github.com/specspace/plasma/protocol" 5 | ) 6 | 7 | const ClientBoundPongPacketID byte = 0x01 8 | 9 | type ClientBoundPong struct { 10 | Payload protocol.Long 11 | } 12 | 13 | func (pk ClientBoundPong) Marshal() protocol.Packet { 14 | return protocol.MarshalPacket( 15 | ClientBoundPongPacketID, 16 | pk.Payload, 17 | ) 18 | } 19 | 20 | func UnmarshalClientBoundPong(packet protocol.Packet) (ClientBoundPong, error) { 21 | var pk ClientBoundPong 22 | 23 | if packet.ID != ClientBoundPongPacketID { 24 | return pk, protocol.ErrInvalidPacketID 25 | } 26 | 27 | if err := packet.Scan( 28 | &pk.Payload, 29 | ); err != nil { 30 | return pk, err 31 | } 32 | 33 | return pk, nil 34 | } 35 | -------------------------------------------------------------------------------- /protocol/packets/status/clientbound_response.go: -------------------------------------------------------------------------------- 1 | package status 2 | 3 | import ( 4 | "github.com/specspace/plasma/protocol" 5 | ) 6 | 7 | const ClientBoundResponsePacketID byte = 0x00 8 | 9 | type ClientBoundResponse struct { 10 | JSONResponse protocol.String 11 | } 12 | 13 | func (pk ClientBoundResponse) Marshal() protocol.Packet { 14 | return protocol.MarshalPacket( 15 | ClientBoundResponsePacketID, 16 | pk.JSONResponse, 17 | ) 18 | } 19 | 20 | func UnmarshalClientBoundResponse(packet protocol.Packet) (ClientBoundResponse, error) { 21 | var pk ClientBoundResponse 22 | 23 | if packet.ID != ClientBoundResponsePacketID { 24 | return pk, protocol.ErrInvalidPacketID 25 | } 26 | 27 | if err := packet.Scan( 28 | &pk.JSONResponse, 29 | ); err != nil { 30 | return pk, err 31 | } 32 | 33 | return pk, nil 34 | } 35 | -------------------------------------------------------------------------------- /protocol/packets/status/serverbound_ping.go: -------------------------------------------------------------------------------- 1 | package status 2 | 3 | import ( 4 | "github.com/specspace/plasma/protocol" 5 | ) 6 | 7 | const ServerBoundPingPacketID byte = 0x01 8 | 9 | type ServerBoundPing struct { 10 | Payload protocol.Long 11 | } 12 | 13 | func (pk ServerBoundPing) Marshal() protocol.Packet { 14 | return protocol.MarshalPacket( 15 | ServerBoundPingPacketID, 16 | pk.Payload, 17 | ) 18 | } 19 | 20 | func UnmarshalServerBoundPing(packet protocol.Packet) (ServerBoundPing, error) { 21 | var pk ServerBoundPing 22 | 23 | if packet.ID != ServerBoundPingPacketID { 24 | return pk, protocol.ErrInvalidPacketID 25 | } 26 | 27 | if err := packet.Scan( 28 | &pk.Payload, 29 | ); err != nil { 30 | return pk, err 31 | } 32 | 33 | return pk, nil 34 | } 35 | -------------------------------------------------------------------------------- /protocol/packets/status/serverbound_request.go: -------------------------------------------------------------------------------- 1 | package status 2 | 3 | import ( 4 | "github.com/specspace/plasma/protocol" 5 | ) 6 | 7 | const ServerBoundRequestPacketID byte = 0x00 8 | 9 | type ServerBoundRequest struct{} 10 | 11 | func (pk ServerBoundRequest) Marshal() protocol.Packet { 12 | return protocol.MarshalPacket( 13 | ServerBoundRequestPacketID, 14 | ) 15 | } 16 | 17 | func UnmarshalServerBoundRequest(packet protocol.Packet) (ServerBoundRequest, error) { 18 | var pk ServerBoundRequest 19 | 20 | if packet.ID != ServerBoundRequestPacketID { 21 | return pk, protocol.ErrInvalidPacketID 22 | } 23 | 24 | return pk, nil 25 | } 26 | -------------------------------------------------------------------------------- /protocol/peeker.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import "io" 4 | 5 | type PeekReader interface { 6 | Peek(n int) ([]byte, error) 7 | io.Reader 8 | } 9 | 10 | type bytePeeker struct { 11 | PeekReader 12 | cursor int 13 | } 14 | 15 | func (peeker *bytePeeker) Read(b []byte) (int, error) { 16 | buf, err := peeker.Peek(len(b) + peeker.cursor) 17 | if err != nil { 18 | return 0, err 19 | } 20 | 21 | for i := 0; i < len(b); i++ { 22 | b[i] = buf[i+peeker.cursor] 23 | } 24 | 25 | peeker.cursor += len(b) 26 | 27 | return len(buf), nil 28 | } 29 | 30 | func (peeker *bytePeeker) ReadByte() (byte, error) { 31 | buf, err := peeker.Peek(1 + peeker.cursor) 32 | if err != nil { 33 | return 0x00, err 34 | } 35 | 36 | b := buf[peeker.cursor] 37 | peeker.cursor++ 38 | 39 | return b, nil 40 | } 41 | -------------------------------------------------------------------------------- /protocol/state.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | type State byte 4 | 5 | const ( 6 | StateHandshaking State = iota 7 | StateStatus 8 | StateLogin 9 | StatePlay 10 | ) 11 | 12 | func (state State) String() string { 13 | names := map[State]string{ 14 | StateHandshaking: "Handshaking", 15 | StateStatus: "Status", 16 | StateLogin: "Login", 17 | StatePlay: "Play", 18 | } 19 | 20 | return names[state] 21 | } 22 | 23 | func (state State) IsHandshaking() bool { 24 | return state == StateHandshaking 25 | } 26 | 27 | func (state State) IsStatus() bool { 28 | return state == StateStatus 29 | } 30 | 31 | func (state State) IsLogin() bool { 32 | return state == StateLogin 33 | } 34 | 35 | func (state State) IsPlay() bool { 36 | return state == StatePlay 37 | } 38 | -------------------------------------------------------------------------------- /protocol/types.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "github.com/gofrs/uuid" 7 | "io" 8 | "math" 9 | 10 | "github.com/Tnze/go-mc/nbt" 11 | ) 12 | 13 | // A Field is both FieldEncoder and FieldDecoder 14 | type Field interface { 15 | FieldEncoder 16 | FieldDecoder 17 | } 18 | 19 | // A FieldEncoder can be encode as minecraft protocol used. 20 | type FieldEncoder interface { 21 | Encode() []byte 22 | } 23 | 24 | // A FieldDecoder can Decode from minecraft protocol 25 | type FieldDecoder interface { 26 | Decode(r DecodeReader) error 27 | } 28 | 29 | //DecodeReader is both io.Reader and io.ByteReader 30 | type DecodeReader interface { 31 | io.ByteReader 32 | io.Reader 33 | } 34 | 35 | type ( 36 | // Boolean of True is encoded as 0x01, false as 0x00. 37 | Boolean bool 38 | // Byte is signed 8-bit integer, two's complement 39 | Byte int8 40 | // UnsignedByte is unsigned 8-bit integer 41 | UnsignedByte uint8 42 | // Short is signed 16-bit integer, two's complement 43 | Short int16 44 | // UnsignedShort is unsigned 16-bit integer 45 | UnsignedShort uint16 46 | // Int is signed 32-bit integer, two's complement 47 | Int int32 48 | // Long is signed 64-bit integer, two's complement 49 | Long int64 50 | // A Float is a single-precision 32-bit IEEE 754 floating point number 51 | Float float32 52 | // A Double is a double-precision 64-bit IEEE 754 floating point number 53 | Double float64 54 | // String is sequence of Unicode scalar values 55 | String string 56 | 57 | // Chat is encoded as a String with max length of 32767. 58 | Chat = String 59 | // Identifier is encoded as a String with max length of 32767. 60 | Identifier = String 61 | 62 | // VarInt is variable-length data encoding a two's complement signed 32-bit integer 63 | VarInt int32 64 | // VarLong is variable-length data encoding a two's complement signed 64-bit integer 65 | VarLong int64 66 | 67 | // Position x as a 26-bit integer, followed by y as a 12-bit integer, 68 | // followed by z as a 26-bit integer (all signed, two's complement) 69 | Position struct { 70 | X, Y, Z int 71 | } 72 | 73 | // Angle is rotation angle in steps of 1/256 of a full turn 74 | Angle = UnsignedByte 75 | 76 | // UUID encoded as an unsigned 128-bit integer 77 | UUID uuid.UUID 78 | 79 | // NBT encode a value as Named Binary Tag 80 | NBT struct { 81 | V interface{} 82 | } 83 | 84 | // ByteArray is []byte with prefix VarInt as length 85 | ByteArray []byte 86 | 87 | // OptionalByteArray is []byte without prefix VarInt as length 88 | OptionalByteArray []byte 89 | 90 | // IdentifierArray is a slice of Identifier 91 | IdentifierArray []Identifier 92 | 93 | // VarIntArray is a slice of VarInt 94 | VarIntArray []VarInt 95 | ) 96 | 97 | // ReadNBytes read N bytes from bytes.Reader 98 | func ReadNBytes(r DecodeReader, n int) (bs []byte, err error) { 99 | bs = make([]byte, n) 100 | for i := 0; i < n; i++ { 101 | bs[i], err = r.ReadByte() 102 | if err != nil { 103 | return 104 | } 105 | } 106 | return 107 | } 108 | 109 | // Encode a Boolean 110 | func (b Boolean) Encode() []byte { 111 | if b { 112 | return []byte{0x01} 113 | } 114 | return []byte{0x00} 115 | } 116 | 117 | // Decode a Boolean 118 | func (b *Boolean) Decode(r DecodeReader) error { 119 | v, err := r.ReadByte() 120 | if err != nil { 121 | return err 122 | } 123 | 124 | *b = v != 0 125 | return nil 126 | } 127 | 128 | // Encode a String 129 | func (s String) Encode() (p []byte) { 130 | byteString := []byte(s) 131 | p = append(p, VarInt(len(byteString)).Encode()...) // len 132 | p = append(p, byteString...) // data 133 | return 134 | } 135 | 136 | // Decode a String 137 | func (s *String) Decode(r DecodeReader) error { 138 | var l VarInt // String length 139 | if err := l.Decode(r); err != nil { 140 | return err 141 | } 142 | 143 | bs, err := ReadNBytes(r, int(l)) 144 | if err != nil { 145 | return err 146 | } 147 | 148 | *s = String(bs) 149 | return nil 150 | } 151 | 152 | // Encode a Byte 153 | func (b Byte) Encode() []byte { 154 | return []byte{byte(b)} 155 | } 156 | 157 | // Decode a Byte 158 | func (b *Byte) Decode(r DecodeReader) error { 159 | v, err := r.ReadByte() 160 | if err != nil { 161 | return err 162 | } 163 | *b = Byte(v) 164 | return nil 165 | } 166 | 167 | // Encode a UnsignedByte 168 | func (ub UnsignedByte) Encode() []byte { 169 | return []byte{byte(ub)} 170 | } 171 | 172 | // Decode a UnsignedByte 173 | func (ub *UnsignedByte) Decode(r DecodeReader) error { 174 | v, err := r.ReadByte() 175 | if err != nil { 176 | return err 177 | } 178 | *ub = UnsignedByte(v) 179 | return nil 180 | } 181 | 182 | // Encode a Signed Short 183 | func (s Short) Encode() []byte { 184 | n := uint16(s) 185 | return []byte{ 186 | byte(n >> 8), 187 | byte(n), 188 | } 189 | } 190 | 191 | // Decode a Short 192 | func (s *Short) Decode(r DecodeReader) error { 193 | bs, err := ReadNBytes(r, 2) 194 | if err != nil { 195 | return err 196 | } 197 | 198 | *s = Short(int16(bs[0])<<8 | int16(bs[1])) 199 | return nil 200 | } 201 | 202 | // Encode a Unsigned Short 203 | func (us UnsignedShort) Encode() []byte { 204 | n := uint16(us) 205 | return []byte{ 206 | byte(n >> 8), 207 | byte(n), 208 | } 209 | } 210 | 211 | // Decode a UnsignedShort 212 | func (us *UnsignedShort) Decode(r DecodeReader) error { 213 | bs, err := ReadNBytes(r, 2) 214 | if err != nil { 215 | return err 216 | } 217 | 218 | *us = UnsignedShort(int16(bs[0])<<8 | int16(bs[1])) 219 | return nil 220 | } 221 | 222 | // Encode a Int 223 | func (i Int) Encode() []byte { 224 | n := uint32(i) 225 | return []byte{ 226 | byte(n >> 24), byte(n >> 16), 227 | byte(n >> 8), byte(n), 228 | } 229 | } 230 | 231 | // Decode a Int 232 | func (i *Int) Decode(r DecodeReader) error { 233 | bs, err := ReadNBytes(r, 4) 234 | if err != nil { 235 | return err 236 | } 237 | 238 | *i = Int(int32(bs[0])<<24 | int32(bs[1])<<16 | int32(bs[2])<<8 | int32(bs[3])) 239 | return nil 240 | } 241 | 242 | // Encode a Long 243 | func (l Long) Encode() []byte { 244 | n := uint64(l) 245 | return []byte{ 246 | byte(n >> 56), byte(n >> 48), byte(n >> 40), byte(n >> 32), 247 | byte(n >> 24), byte(n >> 16), byte(n >> 8), byte(n), 248 | } 249 | } 250 | 251 | // Decode a Long 252 | func (l *Long) Decode(r DecodeReader) error { 253 | bs, err := ReadNBytes(r, 8) 254 | if err != nil { 255 | return err 256 | } 257 | 258 | *l = Long(int64(bs[0])<<56 | int64(bs[1])<<48 | int64(bs[2])<<40 | int64(bs[3])<<32 | 259 | int64(bs[4])<<24 | int64(bs[5])<<16 | int64(bs[6])<<8 | int64(bs[7])) 260 | return nil 261 | } 262 | 263 | // Encode a VarInt 264 | func (v VarInt) Encode() (vi []byte) { 265 | num := uint32(v) 266 | for { 267 | b := num & 0x7F 268 | num >>= 7 269 | if num != 0 { 270 | b |= 0x80 271 | } 272 | vi = append(vi, byte(b)) 273 | if num == 0 { 274 | break 275 | } 276 | } 277 | return 278 | } 279 | 280 | // Decode a VarInt 281 | func (v *VarInt) Decode(r DecodeReader) error { 282 | var n uint32 283 | for i := 0; ; i++ { 284 | sec, err := r.ReadByte() 285 | if err != nil { 286 | return err 287 | } 288 | 289 | n |= uint32(sec&0x7F) << uint32(7*i) 290 | 291 | if i >= 5 { 292 | return errors.New("VarInt is too big") 293 | } else if sec&0x80 == 0 { 294 | break 295 | } 296 | } 297 | 298 | *v = VarInt(n) 299 | return nil 300 | } 301 | 302 | // Encode a VarLong 303 | func (v VarLong) Encode() (vi []byte) { 304 | num := uint64(v) 305 | for { 306 | b := num & 0x7F 307 | num >>= 7 308 | if num != 0 { 309 | b |= 0x80 310 | } 311 | vi = append(vi, byte(b)) 312 | if num == 0 { 313 | break 314 | } 315 | } 316 | return 317 | } 318 | 319 | // Decode a VarLong 320 | func (v *VarLong) Decode(r DecodeReader) error { 321 | var n uint64 322 | for i := 0; ; i++ { 323 | sec, err := r.ReadByte() 324 | if err != nil { 325 | return err 326 | } 327 | 328 | n |= uint64(sec&0x7F) << uint64(7*i) 329 | 330 | if i >= 10 { 331 | return errors.New("VarLong is too big") 332 | } else if sec&0x80 == 0 { 333 | break 334 | } 335 | } 336 | 337 | *v = VarLong(n) 338 | return nil 339 | } 340 | 341 | // Encode a Position 342 | func (p Position) Encode() []byte { 343 | b := make([]byte, 8) 344 | position := uint64(p.X&0x3FFFFFF)<<38 | uint64((p.Z&0x3FFFFFF)<<12) | uint64(p.Y&0xFFF) 345 | for i := 7; i >= 0; i-- { 346 | b[i] = byte(position) 347 | position >>= 8 348 | } 349 | return b 350 | } 351 | 352 | // Decode a Position 353 | func (p *Position) Decode(r DecodeReader) error { 354 | var v Long 355 | if err := v.Decode(r); err != nil { 356 | return err 357 | } 358 | 359 | x := int(v >> 38) 360 | y := int(v & 0xFFF) 361 | z := int(v << 26 >> 38) 362 | 363 | if x >= 1<<25 { 364 | x -= 1 << 26 365 | } 366 | if y >= 1<<11 { 367 | y -= 1 << 12 368 | } 369 | if z >= 1<<25 { 370 | z -= 1 << 26 371 | } 372 | 373 | p.X, p.Y, p.Z = x, y, z 374 | return nil 375 | } 376 | 377 | // Encode a Float 378 | func (f Float) Encode() []byte { 379 | return Int(math.Float32bits(float32(f))).Encode() 380 | } 381 | 382 | // Decode a Float 383 | func (f *Float) Decode(r DecodeReader) error { 384 | var v Int 385 | if err := v.Decode(r); err != nil { 386 | return err 387 | } 388 | 389 | *f = Float(math.Float32frombits(uint32(v))) 390 | return nil 391 | } 392 | 393 | // Encode a Double 394 | func (d Double) Encode() []byte { 395 | return Long(math.Float64bits(float64(d))).Encode() 396 | } 397 | 398 | // Decode a Double 399 | func (d *Double) Decode(r DecodeReader) error { 400 | var v Long 401 | if err := v.Decode(r); err != nil { 402 | return err 403 | } 404 | 405 | *d = Double(math.Float64frombits(uint64(v))) 406 | return nil 407 | } 408 | 409 | // Encode a NBT 410 | func (n NBT) Encode() []byte { 411 | w := bytes.NewBuffer([]byte{}) 412 | _ = nbt.NewEncoder(w).Encode(n.V) 413 | return w.Bytes() 414 | } 415 | 416 | // Decode a NBT 417 | func (n NBT) Decode(r DecodeReader) error { 418 | return nbt.NewDecoder(r).Decode(n.V) 419 | } 420 | 421 | // Encode a ByteArray 422 | func (b ByteArray) Encode() []byte { 423 | return append(VarInt(len(b)).Encode(), b...) 424 | } 425 | 426 | // Decode a ByteArray 427 | func (b *ByteArray) Decode(r DecodeReader) error { 428 | var length VarInt 429 | if err := length.Decode(r); err != nil { 430 | return err 431 | } 432 | *b = make([]byte, length) 433 | _, err := r.Read(*b) 434 | return err 435 | } 436 | 437 | // Encode a UUID 438 | func (u UUID) Encode() []byte { 439 | return u[:] 440 | } 441 | 442 | // Decode a UUID 443 | func (u *UUID) Decode(r DecodeReader) error { 444 | _, err := io.ReadFull(r, (*u)[:]) 445 | return err 446 | } 447 | 448 | // Encode a OptionalByteArray 449 | func (b OptionalByteArray) Encode() []byte { 450 | return b 451 | } 452 | 453 | // Decode a OptionalByteArray 454 | func (b *OptionalByteArray) Decode(r DecodeReader) error { 455 | br := bytes.NewBuffer([]byte{}) 456 | _, err := br.ReadFrom(r) 457 | if err != nil { 458 | return err 459 | } 460 | *b = br.Bytes() 461 | return nil 462 | } 463 | 464 | // Encode a IdentifierArray 465 | func (ia IdentifierArray) Encode() []byte { 466 | b := VarInt(len(ia)).Encode() 467 | for _, i := range ia { 468 | b = append(b, i.Encode()...) 469 | } 470 | return b 471 | } 472 | 473 | // Decode a IdentifierArray 474 | func (ia *IdentifierArray) Decode(r DecodeReader) error { 475 | var length VarInt 476 | if err := length.Decode(r); err != nil { 477 | return err 478 | } 479 | *ia = make([]Identifier, length) 480 | for _, i := range *ia { 481 | if err := i.Decode(r); err != nil { 482 | return err 483 | } 484 | } 485 | return nil 486 | } 487 | 488 | // Encode a VarIntArray 489 | func (ia VarIntArray) Encode() []byte { 490 | b := VarInt(len(ia)).Encode() 491 | for _, i := range ia { 492 | b = append(b, i.Encode()...) 493 | } 494 | return b 495 | } 496 | 497 | // Decode a VarIntArray 498 | func (ia *VarIntArray) Decode(r DecodeReader) error { 499 | var length VarInt 500 | if err := length.Decode(r); err != nil { 501 | return err 502 | } 503 | *ia = make([]VarInt, length) 504 | for _, i := range *ia { 505 | if err := i.Decode(r); err != nil { 506 | return err 507 | } 508 | } 509 | return nil 510 | } 511 | -------------------------------------------------------------------------------- /protocol/zlib/zlib.go: -------------------------------------------------------------------------------- 1 | package zlib 2 | 3 | import ( 4 | "github.com/4kills/go-libdeflate/v2" 5 | "sync" 6 | ) 7 | 8 | var ( 9 | decoder libdeflate.Decompressor 10 | decodeLock sync.Mutex 11 | 12 | encoder libdeflate.Compressor 13 | encodeLock sync.Mutex 14 | ) 15 | 16 | func init() { 17 | var err error 18 | decoder, err = libdeflate.NewDecompressor() 19 | if err != nil { 20 | panic(err) 21 | } 22 | 23 | encoder, err = libdeflate.NewCompressor() 24 | if err != nil { 25 | panic(err) 26 | } 27 | } 28 | 29 | func Decode(in, out []byte) error { 30 | decodeLock.Lock() 31 | defer decodeLock.Unlock() 32 | 33 | _, _, err := decoder.DecompressZlib(in, out) 34 | return err 35 | } 36 | 37 | func Encode(in []byte) ([]byte, error) { 38 | encodeLock.Lock() 39 | defer encodeLock.Unlock() 40 | 41 | _, bb, err := encoder.CompressZlib(in, nil) 42 | return bb, err 43 | } 44 | -------------------------------------------------------------------------------- /server.go: -------------------------------------------------------------------------------- 1 | package plasma 2 | 3 | import ( 4 | "bufio" 5 | "crypto/aes" 6 | "encoding/base64" 7 | "encoding/json" 8 | "errors" 9 | "fmt" 10 | "github.com/gofrs/uuid" 11 | "github.com/specspace/plasma/protocol" 12 | "github.com/specspace/plasma/protocol/cfb8" 13 | "net" 14 | "os" 15 | "sync" 16 | ) 17 | 18 | type HandlerFunc func(w ResponseWriter, r *Request) 19 | 20 | func (f HandlerFunc) ServeProtocol(w ResponseWriter, r *Request) { 21 | f(w, r) 22 | } 23 | 24 | type Handler interface { 25 | ServeProtocol(w ResponseWriter, r *Request) 26 | } 27 | 28 | type Version struct { 29 | // Name of the version eg. "1.16.5" 30 | Name string `json:"name"` 31 | // Protocol version number eg. 754 32 | ProtocolNumber int `json:"protocol"` 33 | } 34 | 35 | type PlayerInfo struct { 36 | Name string `json:"name"` 37 | UUID string `json:"id"` 38 | } 39 | 40 | type PlayersInfo struct { 41 | MaxPlayers int `json:"max"` 42 | PlayersOnline int `json:"online"` 43 | Players []PlayerInfo `json:"sample"` 44 | } 45 | 46 | type StatusResponse struct { 47 | Version Version 48 | PlayersInfo PlayersInfo 49 | IconPath string 50 | MOTD string 51 | } 52 | 53 | func (sr StatusResponse) JSON() ([]byte, error) { 54 | response := struct { 55 | Version Version `json:"version"` 56 | Players PlayersInfo `json:"players"` 57 | Description struct { 58 | Text string `json:"text"` 59 | } `json:"description"` 60 | Favicon string `json:"favicon"` 61 | }{} 62 | 63 | response.Version = sr.Version 64 | response.Players = sr.PlayersInfo 65 | response.Description.Text = sr.MOTD 66 | 67 | if sr.IconPath != "" { 68 | img64, err := loadImageAndEncodeToBase64String(sr.IconPath) 69 | if err != nil { 70 | return nil, err 71 | } 72 | response.Favicon = fmt.Sprintf("data:image/png;base64,%s", img64) 73 | } 74 | 75 | return json.Marshal(response) 76 | } 77 | 78 | func loadImageAndEncodeToBase64String(path string) (string, error) { 79 | if path == "" { 80 | return "", nil 81 | } 82 | 83 | imgFile, err := os.Open(path) 84 | if err != nil { 85 | return "", err 86 | } 87 | defer imgFile.Close() 88 | 89 | fileInfo, err := imgFile.Stat() 90 | if err != nil { 91 | return "", err 92 | } 93 | 94 | buffer := make([]byte, fileInfo.Size()) 95 | fileReader := bufio.NewReader(imgFile) 96 | _, err = fileReader.Read(buffer) 97 | if err != nil { 98 | return "", nil 99 | } 100 | 101 | return base64.StdEncoding.EncodeToString(buffer), nil 102 | } 103 | 104 | const DefaultAddr string = ":25565" 105 | 106 | // Server defines the struct of a running Minecraft server 107 | type Server struct { 108 | ID string 109 | Addr string 110 | Encryption bool 111 | MaxPlayers int 112 | 113 | SessionEncrypter SessionEncrypter 114 | SessionAuthenticator SessionAuthenticator 115 | Handler Handler 116 | 117 | listener net.Listener 118 | players map[*conn]player 119 | mu sync.RWMutex 120 | } 121 | 122 | func (srv *Server) getPlayer(conn *conn) player { 123 | srv.mu.RLock() 124 | defer srv.mu.RUnlock() 125 | return srv.players[conn] 126 | } 127 | 128 | func (srv *Server) putPlayer(c *conn, p player) { 129 | srv.mu.Lock() 130 | defer srv.mu.Unlock() 131 | srv.players[c] = p 132 | } 133 | 134 | func (srv *Server) removePlayer(c *conn) { 135 | srv.mu.Lock() 136 | defer srv.mu.Unlock() 137 | delete(srv.players, c) 138 | } 139 | 140 | func (srv *Server) Player(r *Request) Player { 141 | return srv.getPlayer(r.conn) 142 | } 143 | 144 | func (srv *Server) Players() []Player { 145 | srv.mu.RLock() 146 | defer srv.mu.RUnlock() 147 | 148 | var players []Player 149 | for _, player := range srv.players { 150 | players = append(players, player) 151 | } 152 | return players 153 | } 154 | 155 | func (srv *Server) AddPlayer(r *Request, username string) { 156 | srv.putPlayer(r.conn, player{ 157 | conn: r.conn, 158 | uuid: uuid.NewV3(uuid.NamespaceOID, "OfflinePlayer:"+username), 159 | username: username, 160 | }) 161 | } 162 | 163 | func (srv *Server) PlayersInfo() PlayersInfo { 164 | pp := srv.Players() 165 | var players []PlayerInfo 166 | 167 | for _, p := range pp { 168 | players = append(players, PlayerInfo{ 169 | Name: p.Username(), 170 | UUID: p.UUID().String(), 171 | }) 172 | } 173 | 174 | srv.mu.RLock() 175 | defer srv.mu.RUnlock() 176 | return PlayersInfo{ 177 | MaxPlayers: srv.MaxPlayers, 178 | PlayersOnline: len(pp), 179 | Players: players, 180 | } 181 | } 182 | 183 | func (srv *Server) IsRunning() bool { 184 | return srv.listener != nil 185 | } 186 | 187 | func (srv *Server) Close() error { 188 | if srv.listener != nil { 189 | return srv.listener.Close() 190 | } 191 | 192 | return nil 193 | } 194 | 195 | func (srv *Server) ListenAndServe() error { 196 | if srv.listener != nil { 197 | return errors.New("server is already running") 198 | } 199 | 200 | addr := srv.Addr 201 | if addr == "" { 202 | addr = DefaultAddr 203 | } 204 | 205 | srv.players = map[*conn]player{} 206 | srv.mu = sync.RWMutex{} 207 | 208 | l, err := net.Listen("tcp", addr) 209 | if err != nil { 210 | return err 211 | } 212 | defer l.Close() 213 | srv.listener = l 214 | 215 | for { 216 | c, err := l.Accept() 217 | if err != nil { 218 | return err 219 | } 220 | 221 | go srv.serve(c) 222 | } 223 | } 224 | 225 | func (srv *Server) serve(c net.Conn) { 226 | conn := wrapConn(c) 227 | defer func() { 228 | srv.removePlayer(conn) 229 | conn.Close() 230 | }() 231 | 232 | r := Request{ 233 | server: srv, 234 | conn: conn, 235 | } 236 | 237 | w := responseWriter{ 238 | conn: conn, 239 | } 240 | 241 | for { 242 | pk, err := conn.ReadPacket() 243 | if err != nil { 244 | return 245 | } 246 | 247 | r.Packet = pk 248 | 249 | srv.Handler.ServeProtocol(&w, &r) 250 | } 251 | } 252 | 253 | func ListenAndServe(addr string, handler Handler) error { 254 | encrypter, err := NewDefaultSessionEncrypter() 255 | if err != nil { 256 | return err 257 | } 258 | 259 | if handler == nil { 260 | handler = DefaultServeMux 261 | } 262 | 263 | srv := &Server{ 264 | Addr: addr, 265 | Encryption: true, 266 | SessionEncrypter: encrypter, 267 | SessionAuthenticator: &MojangSessionAuthenticator{}, 268 | Handler: handler, 269 | } 270 | 271 | return srv.ListenAndServe() 272 | } 273 | 274 | type ResponseWriter interface { 275 | PacketWriter 276 | SetState(state protocol.State) 277 | SetEncryption(sharedSecret []byte) error 278 | SetCompression(threshold int) 279 | } 280 | 281 | type responseWriter struct { 282 | nextState bool 283 | conn *conn 284 | } 285 | 286 | func (w *responseWriter) SetState(state protocol.State) { 287 | w.conn.state = state 288 | } 289 | 290 | func (w *responseWriter) WritePacket(p protocol.Packet) error { 291 | return w.conn.WritePacket(p) 292 | } 293 | 294 | func (w *responseWriter) SetEncryption(sharedSecret []byte) error { 295 | block, err := aes.NewCipher(sharedSecret) 296 | if err != nil { 297 | return err 298 | } 299 | 300 | w.conn.SetCipher( 301 | cfb8.NewEncrypter(block, sharedSecret), 302 | cfb8.NewDecrypter(block, sharedSecret), 303 | ) 304 | return nil 305 | } 306 | 307 | func (w *responseWriter) SetCompression(threshold int) { 308 | w.conn.threshold = threshold 309 | } 310 | 311 | type Request struct { 312 | Packet protocol.Packet 313 | 314 | server *Server 315 | conn *conn 316 | } 317 | 318 | func (r Request) Server() *Server { 319 | return r.server 320 | } 321 | 322 | // ClonePacket makes a deep copy of the packet and returns it. 323 | func (r Request) ClonePacket() protocol.Packet { 324 | data := make([]byte, len(r.Packet.Data)) 325 | copy(data, r.Packet.Data) 326 | return protocol.Packet{ 327 | ID: r.Packet.ID, 328 | Data: data, 329 | } 330 | } 331 | 332 | func (r Request) ProtocolState() protocol.State { 333 | return r.conn.state 334 | } 335 | 336 | func (r Request) Conn() Conn { 337 | return r.conn 338 | } 339 | 340 | func (r *Request) Player() Player { 341 | return r.server.Player(r) 342 | } 343 | 344 | func (r *Request) UpdatePlayerUsername(username string) { 345 | player := r.server.getPlayer(r.conn) 346 | player.username = username 347 | r.server.putPlayer(r.conn, player) 348 | } 349 | 350 | func (r *Request) UpdatePlayerUUID(uuid uuid.UUID) { 351 | player := r.server.getPlayer(r.conn) 352 | player.uuid = uuid 353 | r.server.putPlayer(r.conn, player) 354 | } 355 | 356 | func (r *Request) UpdatePlayerSkin(skin Skin) { 357 | player := r.server.getPlayer(r.conn) 358 | player.skin = skin 359 | r.server.putPlayer(r.conn, player) 360 | } 361 | -------------------------------------------------------------------------------- /server_test.go: -------------------------------------------------------------------------------- 1 | package plasma 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestStatusResponse_JSON(t *testing.T) { 8 | tt := []struct { 9 | statusResponse StatusResponse 10 | expectedJSON string 11 | }{ 12 | { 13 | statusResponse: StatusResponse{ 14 | Version: Version{ 15 | Name: "1.16.4", 16 | ProtocolNumber: 754, 17 | }, 18 | MOTD: "test", 19 | PlayersInfo: PlayersInfo{ 20 | MaxPlayers: 20, 21 | }, 22 | }, 23 | expectedJSON: "{\"version\":{\"name\":\"1.16.4\",\"protocol\":754},\"players\":{\"max\":20,\"online\":0," + 24 | "\"sample\":null},\"description\":{\"text\":\"test\"},\"favicon\":\"\"}", 25 | }, 26 | { 27 | statusResponse: StatusResponse{ 28 | Version: Version{ 29 | Name: "1.16.4", 30 | ProtocolNumber: 754, 31 | }, 32 | MOTD: "test", 33 | PlayersInfo: PlayersInfo{ 34 | MaxPlayers: 20, 35 | PlayersOnline: 20, 36 | Players: []PlayerInfo{ 37 | { 38 | Name: "Haveachin", 39 | UUID: "1234", 40 | }, 41 | }, 42 | }, 43 | }, 44 | expectedJSON: "{\"version\":{\"name\":\"1.16.4\",\"protocol\":754},\"players\":{\"max\":20,\"online\":20," + 45 | "\"sample\":[{\"name\":\"Haveachin\",\"id\":\"1234\"}]},\"description\":{\"text\":\"test\"}," + 46 | "\"favicon\":\"\"}", 47 | }, 48 | { 49 | statusResponse: StatusResponse{ 50 | Version: Version{ 51 | Name: "1.16.4", 52 | ProtocolNumber: 754, 53 | }, 54 | MOTD: "test", 55 | PlayersInfo: PlayersInfo{ 56 | MaxPlayers: 20, 57 | PlayersOnline: 20, 58 | Players: []PlayerInfo{ 59 | { 60 | Name: "mb175", 61 | UUID: "175", 62 | }, 63 | }, 64 | }, 65 | IconPath: "./.testfiles/test-icon-64x64.png", 66 | }, 67 | expectedJSON: "{\"version\":{\"name\":\"1.16.4\",\"protocol\":754},\"players\":{\"max\":20,\"online\":20," + 68 | "\"sample\":[{\"name\":\"mb175\",\"id\":\"175\"}]},\"description\":{\"text\":\"test\"},\"favicon\":" + 69 | "\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAIAAAAlC+aJAAAAAXNSR0IArs4c6QAAAARnQU1BAACx" + 70 | "jwv8YQUAAAAJcEhZcwAAFiUAABYlAUlSJPAAAABeSURBVGhD7c9BCQAwDACxCemz/p3Nw2Qcg0AM5NzZrwnUBGoCNYGaQE2gJlAT" + 71 | "qAnUBGoCNYGaQE2gJlATqAnUBGoCNYGaQE2gJlATqAnUBGoCNYGaQE2gJlD7PDD7AFpP0Q+6dA8hAAAAAElFTkSuQmCC\"}", 72 | }, 73 | } 74 | 75 | for _, tc := range tt { 76 | bb, err := tc.statusResponse.JSON() 77 | if err != nil { 78 | t.Error(err) 79 | } 80 | 81 | if tc.expectedJSON != string(bb) { 82 | t.Errorf("got %s; want %s", tc.expectedJSON, string(bb)) 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /sha1.go: -------------------------------------------------------------------------------- 1 | package plasma 2 | 3 | import ( 4 | "crypto/sha1" 5 | "fmt" 6 | "hash" 7 | "strings" 8 | ) 9 | 10 | type Sha1Hash struct { 11 | hash.Hash 12 | } 13 | 14 | func NewSha1Hash() Sha1Hash { 15 | return Sha1Hash{ 16 | Hash: sha1.New(), 17 | } 18 | } 19 | 20 | func (h Sha1Hash) Update(b []byte) { 21 | // This will never return an error like documented in hash.Hash 22 | // so ignoring this error is ok 23 | _, _ = h.Write(b) 24 | } 25 | 26 | func (h Sha1Hash) HexDigest() string { 27 | hashBytes := h.Sum(nil) 28 | 29 | negative := (hashBytes[0] & 0x80) == 0x80 30 | if negative { 31 | // two's compliment, big endian 32 | carry := true 33 | for i := len(hashBytes) - 1; i >= 0; i-- { 34 | hashBytes[i] = ^hashBytes[i] 35 | if carry { 36 | carry = hashBytes[i] == 0xff 37 | hashBytes[i]++ 38 | } 39 | } 40 | } 41 | 42 | hashString := strings.TrimLeft(fmt.Sprintf("%x", hashBytes), "0") 43 | if negative { 44 | hashString = "-" + hashString 45 | } 46 | 47 | return hashString 48 | } 49 | -------------------------------------------------------------------------------- /sha1_test.go: -------------------------------------------------------------------------------- 1 | package plasma 2 | 3 | import "testing" 4 | 5 | func TestHash_HexDigest(t *testing.T) { 6 | tt := []struct { 7 | username string 8 | hash string 9 | }{ 10 | { 11 | username: "Notch", 12 | hash: "4ed1f46bbe04bc756bcb17c0c7ce3e4632f06a48", 13 | }, 14 | { 15 | username: "jeb_", 16 | hash: "-7c9d5b0044c130109a5d7b5fb5c317c02b4e28c1", 17 | }, 18 | { 19 | username: "simon", 20 | hash: "88e16a1019277b15d58faf0541e11910eb756f6", 21 | }, 22 | } 23 | 24 | for _, test := range tt { 25 | notchSha1 := NewSha1Hash() 26 | notchSha1.Update([]byte(test.username)) 27 | hash := notchSha1.HexDigest() 28 | if test.hash != hash { 29 | t.Errorf("HexDigest of %s should be %s; got: %s", test.username, test.hash, hash) 30 | } 31 | } 32 | } 33 | --------------------------------------------------------------------------------