gamespy_query 0.0.5 → 0.1.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.
- data/.gitignore +0 -0
- data/Gemfile +0 -0
- data/README.md +0 -0
- data/Rakefile +0 -0
- data/gamespy_query.gemspec +0 -0
- data/lib/gamespy_query/base.rb +87 -55
- data/lib/gamespy_query/master.rb +90 -81
- data/lib/gamespy_query/parser.rb +234 -234
- data/lib/gamespy_query/socket.rb +325 -266
- data/lib/gamespy_query/socket_master.rb +78 -0
- data/lib/gamespy_query/version.rb +1 -1
- data/lib/gamespy_query.rb +1 -0
- metadata +3 -2
data/lib/gamespy_query/socket.rb
CHANGED
@@ -1,266 +1,325 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
# GameSpy query class by Sickboy [Patrick Roza] (sb_at_dev-heaven.net)
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
require 'yaml'
|
7
|
-
require_relative 'base'
|
8
|
-
require_relative 'parser'
|
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
|
-
|
131
|
-
|
132
|
-
end
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
end
|
256
|
-
|
257
|
-
end
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
1
|
+
# encoding: utf-8
|
2
|
+
# GameSpy query class by Sickboy [Patrick Roza] (sb_at_dev-heaven.net)
|
3
|
+
|
4
|
+
|
5
|
+
|
6
|
+
require 'yaml'
|
7
|
+
require_relative 'base'
|
8
|
+
require_relative 'parser'
|
9
|
+
require 'socket'
|
10
|
+
|
11
|
+
module GamespyQuery
|
12
|
+
module MultiSocket
|
13
|
+
def create_socket(*params)
|
14
|
+
puts "Creating socket #{params}"
|
15
|
+
_create_socket(*params)
|
16
|
+
end
|
17
|
+
|
18
|
+
def socket_send(*params)
|
19
|
+
puts "Sending socket #{params}"
|
20
|
+
_socket_send(*params)
|
21
|
+
end
|
22
|
+
|
23
|
+
def socket_receive(*params)
|
24
|
+
puts "Receiving socket #{params}"
|
25
|
+
_socket_receive(*params)
|
26
|
+
end
|
27
|
+
|
28
|
+
def socket_close(*params)
|
29
|
+
puts "Closing socket #{params}"
|
30
|
+
_socket_close(*params)
|
31
|
+
end
|
32
|
+
|
33
|
+
if RUBY_PLATFORM =~ /mswin32/
|
34
|
+
include System::Net
|
35
|
+
include System::Net::Sockets
|
36
|
+
|
37
|
+
def _create_socket(host, port)
|
38
|
+
@ip_end_point = IPEndPoint.new(IPAddress.Any, 0)
|
39
|
+
@s = UdpClient.new
|
40
|
+
@s.client.receive_timeout = DEFAULT_TIMEOUT * 1000
|
41
|
+
@s.connect(host, port.to_i)
|
42
|
+
end
|
43
|
+
|
44
|
+
def _socket_send(packet)
|
45
|
+
@s.Send(packet, packet.length)
|
46
|
+
end
|
47
|
+
|
48
|
+
def _socket_receive
|
49
|
+
@s.Receive(@ip_end_point)
|
50
|
+
end
|
51
|
+
|
52
|
+
def _socket_close
|
53
|
+
@s.close
|
54
|
+
end
|
55
|
+
else
|
56
|
+
def _create_socket(host, port)
|
57
|
+
@s = UDPSocket.new
|
58
|
+
@s.connect(host, port)
|
59
|
+
end
|
60
|
+
|
61
|
+
def _socket_send(packet)
|
62
|
+
@s.puts(packet)
|
63
|
+
end
|
64
|
+
|
65
|
+
def _socket_receive
|
66
|
+
begin
|
67
|
+
Timeout::timeout(DEFAULT_TIMEOUT) do
|
68
|
+
@s.recvfrom(RECEIVE_SIZE)
|
69
|
+
end
|
70
|
+
rescue Timeout::Error
|
71
|
+
#socket_close
|
72
|
+
raise TimeoutError
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def _socket_close
|
77
|
+
@s.close
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
class Socket < UDPSocket
|
83
|
+
DEFAULT_TIMEOUT = 3
|
84
|
+
MAX_PACKETS = 7
|
85
|
+
|
86
|
+
ID_PACKET = [0x04, 0x05, 0x06, 0x07].pack("c*") # TODO: Randomize
|
87
|
+
BASE_PACKET = [0xFE, 0xFD, 0x00].pack("c*")
|
88
|
+
CHALLENGE_PACKET = [0xFE, 0xFD, 0x09].pack("c*")
|
89
|
+
|
90
|
+
FULL_INFO_PACKET_MP = [0xFF, 0xFF, 0xFF, 0x01].pack("c*")
|
91
|
+
FULL_INFO_PACKET = [0xFF, 0xFF, 0xFF].pack("c*")
|
92
|
+
SERVER_INFO_PACKET = [0xFF, 0x00, 0x00].pack("c*")
|
93
|
+
PLAYER_INFO_PACKET = [0x00, 0xFF, 0x00].pack("c*")
|
94
|
+
|
95
|
+
RECEIVE_SIZE = 1500
|
96
|
+
|
97
|
+
STR_HOSTNAME = "hostname"
|
98
|
+
STR_PLAYERS = "players"
|
99
|
+
STR_DEATHS = "deaths_\x00\x00"
|
100
|
+
STR_PLAYER = "player_\x00\x00"
|
101
|
+
STR_TEAM = "team_\x00\x00"
|
102
|
+
STR_SCORE = "score_\x00\x00"
|
103
|
+
|
104
|
+
SPLIT = STR_X0
|
105
|
+
STR_END = "\x00\x02"
|
106
|
+
STR_EMPTY = Tools::STR_EMPTY
|
107
|
+
STR_BLA = "%c%c%c%c".encode("ASCII-8BIT")
|
108
|
+
STR_GARBAGE = "\x00\x04\x05\x06\a"
|
109
|
+
|
110
|
+
RX_PLAYER_EMPTY = /^player_\x00\x00\x00/
|
111
|
+
RX_PLAYER_INFO = /\x01(team|player|score|deaths)_.(.)/ # \x00 from previous packet, \x01 from continueing player info, (.) - should it overwrite previous value?
|
112
|
+
|
113
|
+
RX_NO_CHALLENGE = /0@0$/
|
114
|
+
RX_CHALLENGE = /0@/
|
115
|
+
RX_CHALLENGE2 = /[^0-9\-]/si
|
116
|
+
RX_SPLITNUM = /^splitnum\x00(.)/i
|
117
|
+
|
118
|
+
# TODO: Support pings
|
119
|
+
# TODO: Handle .NET native sockets
|
120
|
+
include Funcs
|
121
|
+
STATE_INIT, STATE_SENT_CHALLENGE, STATE_RECEIVED_CHALLENGE, STATE_SENT_CHALLENGE_RESPONSE, STATE_RECEIVE_DATA, STATE_READY = 0, 1, 2, 3, 4, 5
|
122
|
+
|
123
|
+
attr_accessor :addr, :data, :state, :stamp, :needs_challenge, :max_packets, :failed
|
124
|
+
|
125
|
+
def initialize(addr, address_family = ::Socket::AF_INET)
|
126
|
+
@addr, @data, @state, @max_packets = addr, {}, 0, MAX_PACKETS
|
127
|
+
@id_packet = ID_PACKET
|
128
|
+
@packet = CHALLENGE_PACKET + @id_packet
|
129
|
+
|
130
|
+
super(address_family)
|
131
|
+
self.connect(*addr.split(":"))
|
132
|
+
end
|
133
|
+
|
134
|
+
def state=(state); @stamp = Time.now; @state = state; end
|
135
|
+
|
136
|
+
def valid?; @state == STATE_READY; end
|
137
|
+
|
138
|
+
def handle_write
|
139
|
+
#STDOUT.puts "Write: #{self.inspect}, #{self.state}"
|
140
|
+
|
141
|
+
r = true
|
142
|
+
begin
|
143
|
+
case self.state
|
144
|
+
when STATE_INIT
|
145
|
+
STDOUT.puts "Write (0): #{self.inspect}"
|
146
|
+
# Send Challenge
|
147
|
+
self.puts @packet
|
148
|
+
self.state = STATE_SENT_CHALLENGE
|
149
|
+
when STATE_RECEIVED_CHALLENGE
|
150
|
+
STDOUT.puts "Write (2): #{self.inspect}"
|
151
|
+
# Send Challenge response
|
152
|
+
self.puts self.needs_challenge ? BASE_PACKET + @id_packet + self.needs_challenge + FULL_INFO_PACKET_MP : BASE_PACKET + @id_packet + FULL_INFO_PACKET_MP
|
153
|
+
self.state = STATE_SENT_CHALLENGE_RESPONSE
|
154
|
+
end
|
155
|
+
rescue => e
|
156
|
+
STDOUT.puts "Error: #{e.message}, #{self.inspect}"
|
157
|
+
self.failed = true
|
158
|
+
r = false
|
159
|
+
close
|
160
|
+
end
|
161
|
+
|
162
|
+
=begin
|
163
|
+
if Time.now - self.stamp > @timeout
|
164
|
+
STDOUT.puts "TimedOut: #{self.inspect}"
|
165
|
+
self.failed = true
|
166
|
+
r = false
|
167
|
+
close unless closed?
|
168
|
+
end
|
169
|
+
=end
|
170
|
+
r
|
171
|
+
end
|
172
|
+
|
173
|
+
def handle_read
|
174
|
+
#STDOUT.puts "Read: #{self.inspect}, #{self.state}"
|
175
|
+
|
176
|
+
r = true
|
177
|
+
case self.state
|
178
|
+
when STATE_SENT_CHALLENGE
|
179
|
+
begin
|
180
|
+
data = self.recvfrom_nonblock(RECEIVE_SIZE)
|
181
|
+
STDOUT.puts "Read (1): #{self.inspect}: #{data}"
|
182
|
+
|
183
|
+
handle_challenge get_string(data[0])
|
184
|
+
|
185
|
+
self.state = STATE_RECEIVED_CHALLENGE
|
186
|
+
rescue => e
|
187
|
+
STDOUT.puts "Error: #{e.message}, #{self.inspect}"
|
188
|
+
self.failed = true
|
189
|
+
r = false
|
190
|
+
close
|
191
|
+
end
|
192
|
+
when STATE_SENT_CHALLENGE_RESPONSE, STATE_RECEIVE_DATA
|
193
|
+
begin
|
194
|
+
data = self.recvfrom_nonblock(RECEIVE_SIZE)
|
195
|
+
STDOUT.puts "Read (3,4): #{self.inspect}: #{data}"
|
196
|
+
self.state = STATE_RECEIVE_DATA
|
197
|
+
|
198
|
+
game_data = get_string(data[0])
|
199
|
+
Tools.debug {"Received (#{self.data.size + 1}):\n\n#{game_data.inspect}\n\n#{game_data}\n\n"}
|
200
|
+
|
201
|
+
index = handle_splitnum game_data
|
202
|
+
|
203
|
+
self.data[index] = game_data
|
204
|
+
|
205
|
+
if self.data.size >= self.max_packets # OR we received the end-packet and all packets required
|
206
|
+
STDOUT.puts "Received packet limit: #{self.inspect}"
|
207
|
+
self.state = STATE_READY
|
208
|
+
r = false
|
209
|
+
close unless closed?
|
210
|
+
end
|
211
|
+
rescue => e
|
212
|
+
STDOUT.puts "Error: #{e.message}, #{self.inspect}"
|
213
|
+
self.failed = true
|
214
|
+
r = false
|
215
|
+
close
|
216
|
+
end
|
217
|
+
end
|
218
|
+
r
|
219
|
+
end
|
220
|
+
|
221
|
+
def handle_exc
|
222
|
+
STDOUT.puts "Exception: #{self.inspect}"
|
223
|
+
close
|
224
|
+
self.failed = true
|
225
|
+
|
226
|
+
false
|
227
|
+
end
|
228
|
+
|
229
|
+
|
230
|
+
def handle_splitnum game_data
|
231
|
+
index = 0
|
232
|
+
if game_data.sub(STR_GARBAGE, STR_EMPTY)[RX_SPLITNUM]
|
233
|
+
splitnum = $1
|
234
|
+
flag = splitnum.unpack("C")[0]
|
235
|
+
index = (flag & 127).to_i
|
236
|
+
last = flag & 0x80 > 0
|
237
|
+
# Data could be received out of order, use the "index" id when "last" flag is true, to determine total packet_count
|
238
|
+
self.max_packets = index + 1 if last # update the max
|
239
|
+
STDOUT.puts "Splitnum: #{splitnum.inspect} (#{splitnum}) (#{flag}, #{index}, #{last}) Max: #{self.max_packets}"
|
240
|
+
else
|
241
|
+
self.max_packets = 1
|
242
|
+
end
|
243
|
+
|
244
|
+
index
|
245
|
+
end
|
246
|
+
|
247
|
+
def handle_challenge str
|
248
|
+
#STDOUT.puts "Received challenge response (#{str.length}): #{str.inspect}"
|
249
|
+
need_challenge = !(str.sub(STR_X0, STR_EMPTY) =~ RX_NO_CHALLENGE)
|
250
|
+
if need_challenge
|
251
|
+
str = str.sub(RX_CHALLENGE, STR_EMPTY).gsub(RX_CHALLENGE2, STR_EMPTY).to_i
|
252
|
+
challenge_packet = sprintf(STR_BLA, handle_chr(str >> 24), handle_chr(str >> 16), handle_chr(str >> 8), handle_chr(str >> 0))
|
253
|
+
self.needs_challenge = challenge_packet
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
def handle_state; [STATE_INIT, STATE_RECEIVED_CHALLENGE].include? state; end
|
258
|
+
|
259
|
+
# Supports challenge/response and multi-packet
|
260
|
+
def sync
|
261
|
+
game_data, key, reply = {}, nil, self.fetch
|
262
|
+
return game_data if reply.nil?
|
263
|
+
|
264
|
+
parser = Parser.new(reply)
|
265
|
+
data = parser.parse
|
266
|
+
|
267
|
+
game_data.merge!(data[:game])
|
268
|
+
game_data["players"] = Parser.pretty_player_data2(data[:players]).sort {|a, b| a[:name].downcase <=> b[:name].downcase }
|
269
|
+
|
270
|
+
game_data["ping"] = @ping unless @ping.nil?
|
271
|
+
|
272
|
+
game_data
|
273
|
+
end
|
274
|
+
|
275
|
+
def fetch
|
276
|
+
pings = []
|
277
|
+
r = self.data
|
278
|
+
begin
|
279
|
+
until valid?
|
280
|
+
if handle_state
|
281
|
+
if IO.select(nil, [self], nil, DEFAULT_TIMEOUT)
|
282
|
+
handle_write
|
283
|
+
else
|
284
|
+
raise "TimeOut"
|
285
|
+
end
|
286
|
+
else
|
287
|
+
if IO.select([self], nil, nil, DEFAULT_TIMEOUT)
|
288
|
+
handle_read
|
289
|
+
else
|
290
|
+
raise "TimeOut"
|
291
|
+
end
|
292
|
+
end
|
293
|
+
end
|
294
|
+
data.each_pair {|k, d| Tools.debug {"GSPY Infos: #{k} #{d.size}"} } unless @silent || !$debug
|
295
|
+
|
296
|
+
pings.map!{|ping| (ping * 1000).round}
|
297
|
+
pings_c = 0
|
298
|
+
pings.each { |ping| pings_c += ping }
|
299
|
+
|
300
|
+
ping = pings.size == 0 ? nil : pings_c / pings.sizeha
|
301
|
+
Tools.debug{"Gamespy pings: #{pings}, #{ping}"}
|
302
|
+
@ping = ping
|
303
|
+
rescue => e
|
304
|
+
STDOUT.puts "Error during fetch #{self.inspect}: #{e.message}"
|
305
|
+
r = nil
|
306
|
+
end
|
307
|
+
r
|
308
|
+
end
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
312
|
+
if $0 == __FILE__
|
313
|
+
host, port = if ARGV.size > 1
|
314
|
+
ARGV
|
315
|
+
else
|
316
|
+
ARGV[0].split(":")
|
317
|
+
end
|
318
|
+
time_start = Time.now
|
319
|
+
g = GamespyQuery::Socket.new("#{host}:#{port}")
|
320
|
+
r = g.sync
|
321
|
+
time_taken = Time.now - time_start
|
322
|
+
puts "Took: #{time_taken}s"
|
323
|
+
exit unless r
|
324
|
+
puts r.to_yaml
|
325
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require_relative 'socket'
|
2
|
+
|
3
|
+
module GamespyQuery
|
4
|
+
class SocketMaster < Base
|
5
|
+
FILL_UP_ON_SPACE = true
|
6
|
+
DEFAULT_MAX_CONNECTIONS = 128
|
7
|
+
|
8
|
+
attr_accessor :timeout, :max_connections
|
9
|
+
|
10
|
+
def initialize addrs
|
11
|
+
@addrs = addrs
|
12
|
+
|
13
|
+
@timeout, @max_connections = Socket::DEFAULT_TIMEOUT, DEFAULT_MAX_CONNECTIONS # Per select iteration
|
14
|
+
end
|
15
|
+
|
16
|
+
def process!
|
17
|
+
sockets = []
|
18
|
+
|
19
|
+
until @addrs.empty?
|
20
|
+
addrs = @addrs.shift @max_connections
|
21
|
+
queue = addrs.map { |addr| Socket.new(addr) }
|
22
|
+
|
23
|
+
sockets += queue
|
24
|
+
|
25
|
+
until queue.empty?
|
26
|
+
# Fill up the Sockets pool until max_conn
|
27
|
+
if FILL_UP_ON_SPACE && queue.size < @max_connections
|
28
|
+
addrs = @addrs.shift (@max_connections - queue.size)
|
29
|
+
socks = addrs.map { |addr| Socket.new(addr) }
|
30
|
+
|
31
|
+
queue += socks
|
32
|
+
sockets += socks
|
33
|
+
end
|
34
|
+
|
35
|
+
write_sockets, read_sockets = queue.reject {|s| s.valid? }.partition {|s| s.handle_state }
|
36
|
+
|
37
|
+
unless ready = IO.select(read_sockets, write_sockets, nil, @timeout)
|
38
|
+
puts "Timeout, no usable sockets in current queue, within timeout period"
|
39
|
+
queue.each{|s| s.close unless s.closed?}
|
40
|
+
queue = []
|
41
|
+
next
|
42
|
+
end
|
43
|
+
|
44
|
+
puts "Sockets: #{queue.size}, AddrsLeft: #{@addrs.size}, ReadReady: #{"#{ready[0].size} / #{read_sockets.size}, WriteReady: #{ready[1].size} / #{write_sockets.size}, ExcReady: #{ready[2].size} / #{queue.size}" unless ready.nil?}"
|
45
|
+
|
46
|
+
# Read
|
47
|
+
ready[0].each { |s| queue.delete(s) unless s.handle_read() }
|
48
|
+
|
49
|
+
# Write
|
50
|
+
ready[1].each { |s| queue.delete(s) unless s.handle_write() }
|
51
|
+
|
52
|
+
# Exceptions
|
53
|
+
#ready[2].each { |s| queue.delete(s) unless s.handle_exc }
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
return sockets
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
if $0 == __FILE__
|
63
|
+
require_relative 'master'
|
64
|
+
srv = File.open(ARGV[0] || "servers.txt") { |f| f.read }
|
65
|
+
master = GamespyQuery::Master.new
|
66
|
+
addrs = master.get_server_list srv
|
67
|
+
|
68
|
+
time_start = Time.now
|
69
|
+
sm = GamespyQuery::SocketMaster.new(addrs)
|
70
|
+
sockets = sm.process!
|
71
|
+
time_taken = Time.now - time_start
|
72
|
+
|
73
|
+
cool = sockets.count {|v| v.valid? }
|
74
|
+
dude = sockets.size - cool
|
75
|
+
|
76
|
+
puts "Success: #{cool}, Failed: #{dude}"
|
77
|
+
puts "Took: #{time_taken}s"
|
78
|
+
end
|