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.
@@ -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
- module GamespyQuery
11
- class Socket < Base
12
- TIMEOUT = 3
13
- MAX_PACKETS = 7
14
-
15
- ID_PACKET = [0x04, 0x05, 0x06, 0x07].pack("c*") # TODO: Randomize
16
- BASE_PACKET = [0xFE, 0xFD, 0x00].pack("c*")
17
- CHALLENGE_PACKET = [0xFE, 0xFD, 0x09].pack("c*")
18
-
19
- FULL_INFO_PACKET_MP = [0xFF, 0xFF, 0xFF, 0x01].pack("c*")
20
- FULL_INFO_PACKET = [0xFF, 0xFF, 0xFF].pack("c*")
21
- SERVER_INFO_PACKET = [0xFF, 0x00, 0x00].pack("c*")
22
- PLAYER_INFO_PACKET = [0x00, 0xFF, 0x00].pack("c*")
23
-
24
- STR_HOSTNAME = "hostname"
25
- STR_PLAYERS = "players"
26
- STR_DEATHS = "deaths_\x00\x00"
27
- STR_PLAYER = "player_\x00\x00"
28
- STR_TEAM = "team_\x00\x00"
29
- STR_SCORE = "score_\x00\x00"
30
-
31
- SPLIT = STR_X0
32
- STR_END = "\x00\x02"
33
- STR_EMPTY = Tools::STR_EMPTY
34
- STR_BLA = "%c%c%c%c".encode("ASCII-8BIT")
35
- STR_GARBAGE = "\x00\x04\x05\x06\a"
36
-
37
- RX_PLAYER_EMPTY = /^player_\x00\x00\x00/
38
- RX_PLAYER_INFO = /\x01(team|player|score|deaths)_.(.)/ # \x00 from previous packet, \x01 from continueing player info, (.) - should it overwrite previous value?
39
-
40
- RX_NO_CHALLENGE = /0@0$/
41
- RX_CHALLENGE = /0@/
42
- RX_CHALLENGE2 = /[^0-9\-]/si
43
- RX_SPLITNUM = /^splitnum\x00(.)/i
44
-
45
- def create_socket(*params)
46
- puts "Creating socket #{params}"
47
- _create_socket(*params)
48
- end
49
-
50
- def socket_send(*params)
51
- puts "Sending socket #{params}"
52
- _socket_send(*params)
53
- end
54
-
55
- def socket_receive(*params)
56
- puts "Receiving socket #{params}"
57
- _socket_receive(*params)
58
- end
59
-
60
- def socket_close(*params)
61
- puts "Closing socket #{params}"
62
- _socket_close(*params)
63
- end
64
-
65
- def get_string(*params)
66
- puts "Getting string #{params}"
67
- _get_string(*params)
68
- end
69
-
70
- if RUBY_PLATFORM =~ /mswin32/
71
- include System::Net
72
- include System::Net::Sockets
73
-
74
- def get_string(str)
75
- str.map {|e| e.chr}.join # begin; System::Text::Encoding.USASCII.GetString(reply[0]).to_s; rescue nil, Exception => e; Tools.log_exception(e); reply[0].map {|e| e.chr}.join; end
76
- end
77
-
78
- def _create_socket(host, port)
79
- @ip_end_point = IPEndPoint.new(IPAddress.Any, 0)
80
- @s = UdpClient.new
81
- @s.client.receive_timeout = TIMEOUT * 1000
82
- @s.connect(host, port.to_i)
83
- end
84
-
85
- def _socket_send(packet)
86
- @s.Send(packet, packet.length)
87
- end
88
-
89
- def _socket_receive
90
- @s.Receive(@ip_end_point)
91
- end
92
-
93
- def _socket_close
94
- @s.close
95
- end
96
- else
97
- require 'socket'
98
- require 'timeout'
99
-
100
- def get_string(str)
101
- str
102
- end
103
-
104
- def _create_socket(host, port)
105
- @s = UDPSocket.new
106
- @s.connect(host, port)
107
- end
108
-
109
- def _socket_send(packet)
110
- @s.puts(packet)
111
- end
112
-
113
- def _socket_receive
114
- begin
115
- Timeout::timeout(TIMEOUT) do
116
- @s.recvfrom(4096)
117
- end
118
- rescue Timeout::Error
119
- #socket_close
120
- raise TimeoutError
121
- end
122
- end
123
-
124
- def _socket_close
125
- @s.close
126
- end
127
- end
128
-
129
- attr_accessor :silent
130
- def initialize(host, port, silent = nil)
131
- @host, @port, @silent = host, port, silent
132
- end
133
-
134
- # Supports challenge/response and multi-packet
135
- def sync
136
- game_data, key, reply = {}, nil, self.fetch
137
- return game_data if reply.nil?
138
-
139
- parser = Parser.new(reply)
140
- data = parser.parse
141
-
142
- game_data.merge!(data[:game])
143
- game_data["players"] = Parser.pretty_player_data2(data[:players]).sort {|a, b| a[:name].downcase <=> b[:name].downcase }
144
-
145
- game_data["ping"] = @ping unless @ping.nil?
146
-
147
- game_data
148
- end
149
-
150
- def fetch
151
- data = {}
152
- status, reply = nil, nil
153
-
154
- # Prepare socket / endpoint and connect
155
- create_socket(@host, @port)
156
-
157
- # Prepare and send challenge request
158
- # TODO: Randomize
159
- id_packet = ID_PACKET
160
- packet = CHALLENGE_PACKET + id_packet
161
- Tools.debug{"Sending Challenge (#{packet.length}): #{packet.inspect}"}
162
- sent = Time.now
163
-
164
- socket_send(packet)
165
-
166
- pings = []
167
-
168
- challenge, received = nil, nil
169
- begin
170
- # By default, Blocks until a message returns on this socket from a remote host.
171
- reply = socket_receive
172
- received = Time.now
173
- # TODO: Improve ping test?
174
- ping = received - sent
175
- pings << ping
176
- Tools.debug {"PingTest: #{ping}"}
177
- challenge = reply[0]
178
- rescue nil, Exception => e # Cannot use ensure as we want to keep the socket open :P
179
- socket_close
180
- raise e
181
- end
182
- return nil if challenge.nil? || challenge.empty?
183
-
184
- # Prepare challenge response, if needed
185
- str = get_string(challenge)
186
- Tools.debug{"Received challenge response (#{str.length}): #{str.inspect}"}
187
- need_challenge = !(str.sub(STR_X0, STR_EMPTY) =~ RX_NO_CHALLENGE)
188
-
189
- if need_challenge
190
- Tools.debug {"Needs challenge!"}
191
- str = str.sub(RX_CHALLENGE, STR_EMPTY).gsub(RX_CHALLENGE2, STR_EMPTY).to_i
192
- challenge_packet = sprintf(STR_BLA, handle_chr(str >> 24), handle_chr(str >> 16), handle_chr(str >> 8), handle_chr(str >> 0))
193
- end
194
-
195
- # Prepare and send info request packet
196
- packet = need_challenge ? BASE_PACKET + id_packet + challenge_packet + FULL_INFO_PACKET_MP : BASE_PACKET + id_packet + FULL_INFO_PACKET_MP
197
- Tools.debug{"Sending:\n#{packet.inspect}"}
198
- sent = Time.now
199
- socket_send(packet)
200
-
201
- # Receive response to info request packet, up to 7 packets of information, each limited to 1400 bytes
202
- max_packets = MAX_PACKETS # Default max
203
- begin
204
- # In case some server info didn't fit in a single packet, there will be no proper END OF DATA signal
205
- # So we manually quit after reaching MAX_PACKETS.
206
- until data.size >= max_packets
207
- reply = socket_receive
208
-
209
- if data.empty?
210
- received = Time.now
211
- ping = received - sent
212
- pings << ping
213
- Tools.debug {"PingTest: #{ping}"}
214
- end
215
- index = 0
216
-
217
- game_data = get_string(reply[0])
218
- Tools.debug {"Received (#{data.size + 1}):\n\n#{game_data.inspect}\n\n#{game_data}\n\n"}
219
-
220
- if game_data.sub(STR_GARBAGE, STR_EMPTY)[RX_SPLITNUM]
221
- splitnum = $1
222
- flag = splitnum.unpack("C")[0]
223
- index = (flag & 127).to_i
224
- last = flag & 0x80 > 0
225
- # Data could be received out of order, use the "index" id when "last" flag is true, to determine total packet_count
226
- max_packets = index + 1 if last # update the max
227
- Tools.debug {"Splitnum: #{splitnum.inspect} (#{splitnum}) (#{flag}, #{index}, #{last}) Max: #{max_packets}"}
228
- else
229
- max_packets = 1
230
- end
231
- data[index] = game_data #.sub(RX_X0_S, STR_EMPTY) # Cut off first \x00 from package
232
- end
233
- ensure
234
- socket_close
235
- end
236
-
237
- pings.map!{|ping| (ping * 1000).round}
238
- pings_c = 0
239
- pings.each { |ping| pings_c += ping }
240
-
241
- ping = pings_c / pings.size
242
- Tools.debug{"Gamespy pings: #{pings}, #{ping}"}
243
-
244
- return nil if data.keys.empty?
245
- @ping = ping
246
- data.each_pair {|k, d| Tools.debug {"GSPY Infos: #{k} #{d.size}"} } unless @silent || !$debug
247
-
248
- data
249
- end
250
-
251
- def handle_chr(number)
252
- number = ((number % 256)+256) if number < 0
253
- number = number % 256 if number > 255
254
- number
255
- end
256
- end
257
- end
258
-
259
- if $0 == __FILE__
260
- host = ARGV[0]
261
- port = ARGV[1]
262
- g = GamespyQuery::Socket.new(host, port)
263
- r = g.sync
264
- exit unless r
265
- puts r.to_yaml
266
- end
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