minestat 2.1.1 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. checksums.yaml +4 -4
  2. data/lib/minestat.rb +139 -35
  3. metadata +3 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b2824d765e8d40dd3a1087567b27a9a04c9df93427b344c8e47346a0a778f5be
4
- data.tar.gz: 5974801cb768bc9a67cd789a0296b569c1f9b14442d7124135c3ddbd8d41886b
3
+ metadata.gz: 289592d2813eb1246b9a3033ce10a219513bf0c9cb792951051bdf6f97b20598
4
+ data.tar.gz: beee739e9c19250fb6663e9561e065dfbcbae817442afe07ca3a21911e5f975a
5
5
  SHA512:
6
- metadata.gz: fc7bb79f048f5d280405638d416cb74ef8e0da6873074bd6e1b70c8176c913fc5fa59ffb92aaa3673f972225851b533964402cfc76160d1d5caec4bf207c43a2
7
- data.tar.gz: 885fb2073958d8a95f121ed7a45e230987520dd0878744650bfe6ec45082a75a1e5dc9d512e76aff6dad965b3a36e493a76e9e5e6bd085bde14d2dd562eb33e6
6
+ metadata.gz: ad094493fac2c5b788c7ed86bdcc1e5296715d410cfe435491101fa56aae2bda6cd81644a88ce8caf183e6ae13072e559a073a4a4b42f9a51b0dfe261a46b378
7
+ data.tar.gz: a757852cab9404fe9937a9a4f9fb7bac2effccd3317173d2abf0bc200a87dea5a6576e382c878be9d15f31b2489298197fafa6f5c1b4eaa1aff75d90f6ab6c16
data/lib/minestat.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # minestat.rb - A Minecraft server status checker
2
- # Copyright (C) 2014-2021 Lloyd Dilley
2
+ # Copyright (C) 2014-2022 Lloyd Dilley
3
3
  # http://www.dilley.me/
4
4
  #
5
5
  # This program is free software; you can redistribute it and/or modify
@@ -24,7 +24,7 @@ require 'timeout'
24
24
  # Provides a ruby interface for polling Minecraft server status.
25
25
  class MineStat
26
26
  # MineStat version
27
- VERSION = "2.1.1"
27
+ VERSION = "2.2.0"
28
28
  # Number of values expected from server
29
29
  NUM_FIELDS = 6
30
30
  # Number of values expected from a 1.8b/1.3 server
@@ -33,8 +33,17 @@ class MineStat
33
33
  MAX_VARINT_SIZE = 5
34
34
  # Default TCP port
35
35
  DEFAULT_PORT = 25565
36
- # Default TCP timeout in seconds
36
+ # Bedrock/Pocket Edition default UDP port
37
+ DEFAULT_BEDROCK_PORT = 19132
38
+ # Default TCP/UDP timeout in seconds
37
39
  DEFAULT_TIMEOUT = 5
40
+ # Bedrock/Pocket Edition packet offset in bytes (1 + 8 + 8 + 16 + 2)
41
+ # Unconnected pong (0x1C) = 1 byte
42
+ # Timestamp as a long = 8 bytes
43
+ # Server GUID as a long = 8 bytes
44
+ # Magic number = 16 bytes
45
+ # String ID length = 2 bytes
46
+ BEDROCK_PACKET_OFFSET = 35
38
47
 
39
48
  ##
40
49
  # Stores constants that represent the results of a server ping
@@ -64,25 +73,28 @@ class MineStat
64
73
  EXTENDED = 2
65
74
  # Server versions 1.7 to latest
66
75
  JSON = 3
76
+ # Bedrock/Pocket Edition
77
+ BEDROCK = 4
67
78
  end
68
79
 
69
80
  ##
70
81
  # Instantiate an instance of MineStat and poll the specified server for information
71
82
  def initialize(address, port = DEFAULT_PORT, timeout = DEFAULT_TIMEOUT, request_type = Request::NONE)
72
- @address = address # address of server
73
- @port = port # TCP port of server
74
- @online # online or offline?
75
- @version # server version
76
- @motd # message of the day
77
- @stripped_motd # message of the day without formatting
78
- @current_players # current number of players online
79
- @max_players # maximum player capacity
80
- @protocol # protocol level
81
- @json_data # JSON data for 1.7 queries
82
- @latency # ping time to server in milliseconds
83
- @timeout = timeout # TCP timeout
84
- @server # server socket
85
- @request_type # SLP protocol version
83
+ @address = address # address of server
84
+ @port = port # TCP/UDP port of server
85
+ @online # online or offline?
86
+ @version # server version
87
+ @mode = "Unspecified" # game mode (Bedrock/Pocket Edition only)
88
+ @motd # message of the day
89
+ @stripped_motd # message of the day without formatting
90
+ @current_players # current number of players online
91
+ @max_players # maximum player capacity
92
+ @protocol # protocol level
93
+ @json_data # JSON data for 1.7 queries
94
+ @latency # ping time to server in milliseconds
95
+ @timeout = timeout # TCP/UDP timeout
96
+ @server # server socket
97
+ @request_type # Protocol version
86
98
 
87
99
  case request_type
88
100
  when Request::BETA
@@ -93,13 +105,14 @@ class MineStat
93
105
  retval = extended_legacy_request()
94
106
  when Request::JSON
95
107
  retval = json_request()
108
+ when Request::BEDROCK
109
+ retval = bedrock_request()
96
110
  else
97
- # Attempt various SLP ping requests in a particular order. If the request
98
- # succeeds or the connection fails, there is no reason to continue with
99
- # subsequent requests. Attempts should continue in the event of a timeout
111
+ # Attempt various SLP ping requests in a particular order. If the
112
+ # connection fails, there is no reason to continue with subsequent
113
+ # requests. Attempts should continue in the event of a timeout
100
114
  # however since it may be due to an issue during the handshake.
101
115
  # Note: Newer server versions may still respond to older SLP requests.
102
- # For example, 1.13.2 responds to 1.4/1.5 queries, but not 1.6 queries.
103
116
  # SLP 1.4/1.5
104
117
  retval = legacy_request()
105
118
  # SLP 1.8b/1.3
@@ -114,6 +127,10 @@ class MineStat
114
127
  unless retval == Retval::CONNFAIL
115
128
  retval = json_request()
116
129
  end
130
+ # Bedrock
131
+ unless retval == Retval::CONNFAIL
132
+ retval = bedrock_request()
133
+ end
117
134
  end
118
135
  @online = false unless retval == Retval::SUCCESS
119
136
  end
@@ -133,6 +150,7 @@ class MineStat
133
150
  end
134
151
  end
135
152
  end
153
+ @stripped_motd = @stripped_motd.force_encoding('UTF-8')
136
154
  @stripped_motd = @stripped_motd.gsub(/§./, "")
137
155
  end
138
156
 
@@ -140,8 +158,15 @@ class MineStat
140
158
  # Establishes a connection to the Minecraft server
141
159
  def connect()
142
160
  begin
143
- start_time = Time.now
144
- @server = TCPSocket.new(@address, @port)
161
+ if @request_type == Request::BEDROCK || @request_type == "Bedrock/Pocket Edition"
162
+ @port = DEFAULT_BEDROCK_PORT if @port == DEFAULT_PORT && @request_type == Request::NONE
163
+ start_time = Time.now
164
+ @server = UDPSocket.new
165
+ @server.connect(@address, @port)
166
+ else
167
+ start_time = Time.now
168
+ @server = TCPSocket.new(@address, @port)
169
+ end
145
170
  @latency = ((Time.now - start_time) * 1000).round
146
171
  rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH
147
172
  return Retval::CONNFAIL
@@ -156,15 +181,27 @@ class MineStat
156
181
  def parse_data(delimiter, is_beta = false)
157
182
  data = nil
158
183
  begin
159
- if @server.read(1).unpack('C').first == 0xFF # kick packet (255)
160
- len = @server.read(2).unpack('n').first
161
- data = @server.read(len * 2).force_encoding('UTF-16BE').encode('UTF-8')
162
- @server.close
163
- else
164
- @server.close
165
- return Retval::UNKNOWN
184
+ if @request_type == "Bedrock/Pocket Edition"
185
+ if @server.recv(1, Socket::MSG_PEEK).unpack('C').first == 0x1C # unconnected pong packet
186
+ server_id_len = @server.recv(BEDROCK_PACKET_OFFSET, Socket::MSG_PEEK)[-2..-1].unpack('n').first
187
+ data = @server.recv(BEDROCK_PACKET_OFFSET + server_id_len)[BEDROCK_PACKET_OFFSET..-1]
188
+ @server.close
189
+ else
190
+ @server.close
191
+ return Retval::UNKNOWN
192
+ end
193
+ else # SLP
194
+ if @server.read(1).unpack('C').first == 0xFF # kick packet (255)
195
+ len = @server.read(2).unpack('n').first
196
+ data = @server.read(len * 2).force_encoding('UTF-16BE').encode('UTF-8')
197
+ @server.close
198
+ else
199
+ @server.close
200
+ return Retval::UNKNOWN
201
+ end
166
202
  end
167
- rescue => exception
203
+ rescue
204
+ #rescue => exception
168
205
  #$stderr.puts exception
169
206
  return Retval::UNKNOWN
170
207
  end
@@ -185,7 +222,20 @@ class MineStat
185
222
  else
186
223
  return Retval::UNKNOWN
187
224
  end
188
- else
225
+ elsif @request_type == "Bedrock/Pocket Edition"
226
+ if server_info != nil
227
+ @protocol = server_info[2].to_i
228
+ @version = "#{server_info[3]} #{server_info[7]} (#{server_info[0]})"
229
+ @mode = server_info[8]
230
+ @motd = server_info[1]
231
+ strip_motd
232
+ @current_players = server_info[4].to_i
233
+ @max_players = server_info[5].to_i
234
+ @online = true
235
+ else
236
+ return Retval::UNKNOWN
237
+ end
238
+ else # SLP
189
239
  if server_info != nil && server_info.length >= NUM_FIELDS
190
240
  # server_info[0] contains the section symbol and 1
191
241
  @protocol = server_info[1].to_i # contains the protocol version (51 for 1.9 or 78 for 1.6.4 for example)
@@ -424,6 +474,57 @@ class MineStat
424
474
  return vint
425
475
  end
426
476
 
477
+ ##
478
+ # Bedrock/Pocket Edition servers communicate as follows for an unconnected ping request:
479
+ # 1. Client sends:
480
+ # 1a. \x01 (unconnected ping packet containing the fields specified below)
481
+ # 1b. current time as a long
482
+ # 1c. magic number
483
+ # 1d. client GUID as a long
484
+ # 2. Server responds with:
485
+ # 2a. \x1c (unconnected pong packet containing the follow fields)
486
+ # 2b. current time as a long
487
+ # 2c. server GUID as a long
488
+ # 2d. 16-bit magic number
489
+ # 2e. server ID as a string
490
+ # The fields from the pong response, in order, are:
491
+ # * edition
492
+ # * MotD line 1
493
+ # * protocol version
494
+ # * version name
495
+ # * current player count
496
+ # * maximum player count
497
+ # * unique server ID
498
+ # * MotD line 2
499
+ # * game mode as a string
500
+ # * game mode as a numeric
501
+ # * IPv4 port number
502
+ # * IPv6 port number
503
+ def bedrock_request()
504
+ retval = nil
505
+ begin
506
+ Timeout::timeout(@timeout) do
507
+ @request_type = "Bedrock/Pocket Edition"
508
+ retval = connect()
509
+ return retval unless retval == Retval::SUCCESS
510
+ # Perform handshake and acquire data
511
+ payload = "\x01".force_encoding('ASCII-8BIT') # unconnected ping
512
+ payload += [Time.now.to_i].pack('L!<').force_encoding('ASCII-8BIT') # current time as a long
513
+ payload += "\x00\xFF\xFF\x00\xFE\xFE\xFE\xFE\xFD\xFD\xFD\xFD\x12\x34\x56\x78".force_encoding('ASCII-8BIT') # magic number
514
+ payload += [2].pack('L!<').force_encoding('ASCII-8BIT') # client GUID as a long
515
+ @server.write(payload)
516
+ @server.flush
517
+ retval = parse_data("\x3B") # semicolon
518
+ end
519
+ rescue Timeout::Error
520
+ return Retval::TIMEOUT
521
+ rescue => exception
522
+ $stderr.puts exception
523
+ return Retval::UNKNOWN
524
+ end
525
+ return retval
526
+ end
527
+
427
528
  # Returns the Minecraft server IP
428
529
  attr_reader :address
429
530
 
@@ -436,12 +537,15 @@ class MineStat
436
537
  # Returns the Minecraft version that the server is running
437
538
  attr_reader :version
438
539
 
439
- # Returns the full version of the MOTD
540
+ # Returns the game mode (Bedrock/Pocket Edition only)
541
+ attr_reader :mode
542
+
543
+ # Returns the full version of the MotD
440
544
  #
441
- # If you just want the MOTD text, use stripped_motd
545
+ # If you just want the MotD text, use stripped_motd
442
546
  attr_reader :motd
443
547
 
444
- # Returns just the plain text contained within the MOTD
548
+ # Returns just the plain text contained within the MotD
445
549
  attr_reader :stripped_motd
446
550
 
447
551
  # Returns the current player count
metadata CHANGED
@@ -1,15 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: minestat
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.1
4
+ version: 2.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lloyd Dilley
8
- - Stepan Melnikov
9
8
  autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2021-09-27 00:00:00.000000000 Z
11
+ date: 2022-05-01 00:00:00.000000000 Z
13
12
  dependencies: []
14
13
  description: MineStat polls Minecraft server data such as version, motd, current players,
15
14
  and max players.
@@ -39,7 +38,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
39
38
  - !ruby/object:Gem::Version
40
39
  version: '0'
41
40
  requirements: []
42
- rubygems_version: 3.2.22
41
+ rubygems_version: 3.3.7
43
42
  signing_key:
44
43
  specification_version: 4
45
44
  summary: Minecraft server status checker