This repository was archived by the owner on Mar 20, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathminecraftping.go
More file actions
130 lines (104 loc) · 3.49 KB
/
minecraftping.go
File metadata and controls
130 lines (104 loc) · 3.49 KB
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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
// Package minecraftping is a simple library to ping Minecraft Java Edition servers.
package minecraftping
import (
"bufio"
"bytes"
"encoding/binary"
"encoding/json"
"fmt"
"io"
"net"
"strconv"
"time"
)
// DefaultPort is the default Minecraft Java Edition network port.
const DefaultPort = 25565
// requestPacket is a cached copy of the Request packet.
// It contains only it's length (1) and the packet's ID (0).
var requestPacket = []byte{1, 0}
// Response is a representation of the Minecraft Java Edition server's ping response.
// More information: https://wiki.vg/Server_List_Ping#Response
type Response struct {
Version struct {
Name string `json:"name"`
Protocol int `json:"protocol"`
} `json:"version"`
Players struct {
Max int `json:"max"`
Online int `json:"online"`
Sample []struct {
Name string `json:"name"`
Id string `json:"id"`
} `json:"sample"`
} `json:"players"`
Description json.RawMessage `json:"description"`
Favicon string `json:"favicon"`
}
// Ping connects and pings the Minecraft Java Edition server at the specified address and port.
// protocolVersion dictates which protocol version to attempt the ping with as the response is protocol version dependent.
// More information: https://wiki.vg/Server_List_Ping
func Ping(address string, port uint16, protocolVersion int, timeout time.Duration) (Response, error) {
var resp Response
deadline := time.Now().Add(timeout)
conn, err := net.DialTimeout("tcp", address+":"+strconv.Itoa(int(port)), timeout)
if err != nil {
return resp, err
}
defer conn.Close()
if err := conn.SetDeadline(deadline); err != nil {
return resp, err
}
// Construct and write Handshake packet to open connection and then write Request packet.
// More information: https://wiki.vg/Server_List_Ping
handshake := makeHandshakePacket(address, port, protocolVersion)
conn.Write(handshake)
conn.Write(requestPacket)
reader := bufio.NewReader(conn)
// Read and discard the length of the incoming packet.
_, err = binary.ReadUvarint(reader)
if err != nil {
return resp, err
}
// Read the packet ID and validate it as 0.
packetId, err := binary.ReadUvarint(reader)
if err != nil {
return resp, err
}
if packetId != 0 {
return resp, fmt.Errorf("received invalid packetId (expected 0!) %d", packetId)
}
// Read the length of the incoming JSON payload (as a uvarint). Read the following bytes into a buffer and then
// unmarshal the []byte into its struct representation Response.
length, err := binary.ReadUvarint(reader)
if err != nil {
return resp, err
}
payload := make([]byte, length)
if _, err = io.ReadFull(reader, payload); err != nil {
return resp, err
}
if err = json.Unmarshal(payload, &resp); err != nil {
return resp, err
}
return resp, nil
}
func makeHandshakePacket(address string, port uint16, protocolVersion int) []byte {
var buf bytes.Buffer
buf.Write([]byte("\x00"))
putVarInt(&buf, int32(protocolVersion))
putVarInt(&buf, int32(len(address)))
buf.WriteString(address)
binary.Write(&buf, binary.BigEndian, port)
putVarInt(&buf, 1)
// Prepend the buffer with it's length as a uvarint
var out bytes.Buffer
putVarInt(&out, int32(buf.Len()))
out.Write(buf.Bytes())
return out.Bytes()
}
// Allocate a []byte buffer of binary.MaxVarintlen32 and write value as a uvarint32. Trim and write to buf.
func putVarInt(buf *bytes.Buffer, value int32) {
bytes := make([]byte, binary.MaxVarintLen32)
bytesWritten := binary.PutUvarint(bytes, uint64(value))
buf.Write(bytes[:bytesWritten])
}