gamespy_query 0.0.5 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|