├── .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 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
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 |
--------------------------------------------------------------------------------