gamespy_query 0.1.5 → 0.2.0pre
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 +5 -0
- data/README.md +7 -4
- data/Rakefile +14 -0
- data/bin/gamespy_query +8 -0
- data/gamespy_query.gemspec +3 -3
- data/lib/gamespy_query.rb +20 -5
- data/lib/gamespy_query/base.rb +45 -24
- data/lib/gamespy_query/master.rb +40 -13
- data/lib/gamespy_query/options.rb +140 -0
- data/lib/gamespy_query/parser.rb +29 -18
- data/lib/gamespy_query/socket.rb +81 -45
- data/lib/gamespy_query/socket_master.rb +49 -3
- data/lib/gamespy_query/version.rb +2 -1
- data/test/teststrap.rb +4 -0
- data/test/units/base_test.rb +43 -0
- data/test/units/master_test.rb +17 -0
- data/test/units/options_test.rb +27 -0
- data/test/units/parser_test.rb +47 -0
- data/test/units/socket_master_test.rb +17 -0
- data/test/units/socket_test.rb +73 -0
- metadata +51 -7
data/lib/gamespy_query/parser.rb
CHANGED
@@ -7,9 +7,8 @@
|
|
7
7
|
Gamedata values are not split, Player lists can be (names, teams, scores, deaths), while individual values still are not.
|
8
8
|
=end
|
9
9
|
|
10
|
-
require_relative 'base'
|
11
|
-
|
12
10
|
module GamespyQuery
|
11
|
+
# Parsing gamespy query packets and processing them to Hash
|
13
12
|
class Parser < Base
|
14
13
|
STR_SPLIT = STR_X0
|
15
14
|
STR_ID = "\x00\x04\x05\x06\a"
|
@@ -18,7 +17,11 @@ module GamespyQuery
|
|
18
17
|
RX_PLAYER_HEADER = /\x01/
|
19
18
|
RX_END = /\x00\x02$/
|
20
19
|
|
21
|
-
#
|
20
|
+
# Packets to process
|
21
|
+
attr_reader :packets
|
22
|
+
|
23
|
+
# Initializes the object
|
24
|
+
# @param [Hash or Array] packets
|
22
25
|
# - Hash, key: packetID, value: packetDATA
|
23
26
|
# or
|
24
27
|
# - Array, packetDATA ordered already by packetID
|
@@ -33,20 +36,20 @@ module GamespyQuery
|
|
33
36
|
end
|
34
37
|
end
|
35
38
|
|
39
|
+
# Parse game and player data to hash
|
36
40
|
# Returns Hash with parsed data (:game and :players)
|
37
|
-
#
|
38
|
-
#
|
41
|
+
# game Key: InfoKey, Value: InfoValue
|
42
|
+
# players Key: InfoType, Value: Array of Values
|
39
43
|
def parse
|
40
44
|
data = {}
|
41
45
|
data[:game] = {} # Key: InfoKey, Value: InfoValue
|
42
46
|
data[:players] = {} # Key: InfoType, Value: Array of Values
|
43
47
|
player_info = false
|
44
|
-
player_data = ""
|
48
|
+
player_data = "".encode "ASCII-8BIT"
|
45
49
|
|
46
50
|
# Parse the packets
|
47
51
|
@packets.each do |packet|
|
48
|
-
packet = clean_packet
|
49
|
-
|
52
|
+
packet = clean_packet packet
|
50
53
|
if player_info
|
51
54
|
# Player header was found before, add packet to player_data
|
52
55
|
player_data += packet
|
@@ -74,6 +77,8 @@ module GamespyQuery
|
|
74
77
|
data
|
75
78
|
end
|
76
79
|
|
80
|
+
# Clean packet from useless data
|
81
|
+
# @param [String] packet Packet data
|
77
82
|
def clean_packet(packet)
|
78
83
|
packet = packet.clone
|
79
84
|
packet.sub!(STR_ID, STR_EMPTY) # Cut off the identity
|
@@ -82,11 +87,12 @@ module GamespyQuery
|
|
82
87
|
packet.sub!(RX_X0_S, STR_EMPTY) # Cut off first \x00
|
83
88
|
packet.sub!(RX_END, STR_EMPTY) # Cut off the last \x00\x02
|
84
89
|
|
85
|
-
|
86
|
-
get_string(packet)
|
90
|
+
packet
|
87
91
|
end
|
88
|
-
|
89
|
-
|
92
|
+
|
93
|
+
# Parse game data in packet
|
94
|
+
# @param [String] packet Packet to parse
|
95
|
+
def parse_game_data(packet)
|
90
96
|
Tools.debug {"Game Parsing #{packet.inspect}"}
|
91
97
|
|
92
98
|
key = nil
|
@@ -94,9 +100,9 @@ module GamespyQuery
|
|
94
100
|
|
95
101
|
packet.split(STR_SPLIT).each_with_index do |data, index|
|
96
102
|
if (index % 2) == 0
|
97
|
-
key =
|
103
|
+
key = encode_string data
|
98
104
|
else
|
99
|
-
game_data[key] = data.is_a?(String) ?
|
105
|
+
game_data[key] = data.is_a?(String) ? encode_string(data) : data
|
100
106
|
end
|
101
107
|
end
|
102
108
|
|
@@ -112,11 +118,13 @@ module GamespyQuery
|
|
112
118
|
STR_SIX = "$SIX_OVERWRITE_PREVIOUS$"
|
113
119
|
STR_SIX_X0 = "\x00#{STR_SIX}\x00"
|
114
120
|
|
121
|
+
# Parse player data in packet
|
122
|
+
# @param [String] packet Packet to parse
|
115
123
|
# TODO: Cleanup
|
116
124
|
def parse_player_data(packet)
|
117
125
|
Tools.debug {"Player Parsing #{packet.inspect}"}
|
118
126
|
|
119
|
-
player_data = {:
|
127
|
+
player_data = {names: [], teams: [], scores: [], deaths: []} # [[], [], [], []]
|
120
128
|
|
121
129
|
return player_data if packet.nil? || packet.empty?
|
122
130
|
|
@@ -154,7 +162,7 @@ module GamespyQuery
|
|
154
162
|
# Parse the data - \x00 is printed after a non-nil entry, otherwise \x00 means nil (e.g empty team)
|
155
163
|
until str.empty?
|
156
164
|
entry = str[RX_X0_SPEC]
|
157
|
-
player_data[player_data.keys[i]] <<
|
165
|
+
player_data[player_data.keys[i]] << encode_string(entry.sub(STR_X0, STR_EMPTY))
|
158
166
|
str.sub!(entry, STR_EMPTY)
|
159
167
|
end
|
160
168
|
|
@@ -166,6 +174,7 @@ module GamespyQuery
|
|
166
174
|
overwrite = true # tag so that the next entry will overwrite the latest entry
|
167
175
|
next # ignore
|
168
176
|
else
|
177
|
+
info = clean(info) if [2,3].include?(i) # Apply data_type conversion for Score and Deaths
|
169
178
|
if overwrite
|
170
179
|
new_player_data[-1] = info # Overwrite latest entry
|
171
180
|
overwrite = false # done the overwrite
|
@@ -182,7 +191,8 @@ module GamespyQuery
|
|
182
191
|
player_data
|
183
192
|
end
|
184
193
|
|
185
|
-
# Hash of Hashes
|
194
|
+
# Convert player data to Hash of Hashes
|
195
|
+
# @param [Hash] data Original player data split over 4 arrays in hash (:names, :teams, :scores, :deaths)
|
186
196
|
def self.pretty_player_data(data)
|
187
197
|
player_data = {}
|
188
198
|
|
@@ -196,7 +206,8 @@ module GamespyQuery
|
|
196
206
|
player_data
|
197
207
|
end
|
198
208
|
|
199
|
-
# Array of Hashes
|
209
|
+
# Convert player data to Array of Hashes
|
210
|
+
# @param [Hash] data Original player data split over 4 arrays in hash (:names, :teams, :scores, :deaths)
|
200
211
|
def self.pretty_player_data2(data)
|
201
212
|
player_data = []
|
202
213
|
|
data/lib/gamespy_query/socket.rb
CHANGED
@@ -2,28 +2,31 @@
|
|
2
2
|
# GameSpy query class by Sickboy [Patrick Roza] (sb_at_dev-heaven.net)
|
3
3
|
|
4
4
|
require 'yaml'
|
5
|
-
require_relative 'base'
|
6
|
-
require_relative 'parser'
|
7
5
|
require 'socket'
|
8
6
|
|
9
7
|
module GamespyQuery
|
8
|
+
# Provides socket functionality on multiple platforms
|
10
9
|
# TODO
|
11
10
|
module MultiSocket
|
11
|
+
# Create socket
|
12
12
|
def create_socket(*params)
|
13
13
|
Tools.debug {"Creating socket #{params}"}
|
14
14
|
_create_socket(*params)
|
15
15
|
end
|
16
16
|
|
17
|
+
# Write socket
|
17
18
|
def socket_send(*params)
|
18
19
|
Tools.debug {"Sending socket #{params}"}
|
19
20
|
_socket_send(*params)
|
20
21
|
end
|
21
22
|
|
23
|
+
# Read socket
|
22
24
|
def socket_receive(*params)
|
23
25
|
Tools.debug {"Receiving socket #{params}"}
|
24
26
|
_socket_receive(*params)
|
25
27
|
end
|
26
28
|
|
29
|
+
# Close socket
|
27
30
|
def socket_close(*params)
|
28
31
|
Tools.debug {"Closing socket #{params}"}
|
29
32
|
@s.close
|
@@ -33,6 +36,7 @@ module GamespyQuery
|
|
33
36
|
include System::Net
|
34
37
|
include System::Net::Sockets
|
35
38
|
|
39
|
+
# Create socket
|
36
40
|
def _create_socket(host, port)
|
37
41
|
@ip_end_point = IPEndPoint.new(IPAddress.Any, 0)
|
38
42
|
@s = UdpClient.new
|
@@ -40,25 +44,30 @@ module GamespyQuery
|
|
40
44
|
@s.connect(host, port.to_i)
|
41
45
|
end
|
42
46
|
|
47
|
+
# Write socket
|
43
48
|
def _socket_send(packet)
|
44
49
|
@s.Send(packet, packet.length)
|
45
50
|
end
|
46
51
|
|
52
|
+
# Read socket
|
47
53
|
def _socket_receive
|
48
54
|
@s.Receive(@ip_end_point)
|
49
55
|
end
|
50
56
|
|
51
57
|
else
|
52
58
|
|
59
|
+
# Create socket
|
53
60
|
def _create_socket(host, port)
|
54
61
|
@s = UDPSocket.new
|
55
62
|
@s.connect(host, port)
|
56
63
|
end
|
57
64
|
|
65
|
+
# Write socket
|
58
66
|
def _socket_send(packet)
|
59
67
|
@s.puts(packet)
|
60
68
|
end
|
61
69
|
|
70
|
+
# Read socket
|
62
71
|
def _socket_receive
|
63
72
|
begin
|
64
73
|
Timeout::timeout(DEFAULT_TIMEOUT) do
|
@@ -73,12 +82,20 @@ module GamespyQuery
|
|
73
82
|
end
|
74
83
|
end
|
75
84
|
|
85
|
+
# Provides direct connection functionality to gamespy enabled game servers
|
86
|
+
# This query contains up to 7x more information than the gamespy master browser query
|
87
|
+
# For example, player lists with info (teams, scores, deaths) are only available by using direct connection
|
76
88
|
class Socket < UDPSocket
|
77
89
|
include Funcs
|
78
90
|
|
91
|
+
# Default timeout per connection state
|
79
92
|
DEFAULT_TIMEOUT = 3
|
93
|
+
|
94
|
+
# Maximum amount of packets sent by the server
|
95
|
+
# This is a limit set by gamespy
|
80
96
|
MAX_PACKETS = 7
|
81
97
|
|
98
|
+
# Packet bits
|
82
99
|
ID_PACKET = [0x04, 0x05, 0x06, 0x07].pack("c*") # TODO: Randomize?
|
83
100
|
BASE_PACKET = [0xFE, 0xFD, 0x00].pack("c*")
|
84
101
|
CHALLENGE_PACKET = [0xFE, 0xFD, 0x09].pack("c*")
|
@@ -88,6 +105,7 @@ module GamespyQuery
|
|
88
105
|
SERVER_INFO_PACKET = [0xFF, 0x00, 0x00].pack("c*")
|
89
106
|
PLAYER_INFO_PACKET = [0x00, 0xFF, 0x00].pack("c*")
|
90
107
|
|
108
|
+
# Maximum receive size
|
91
109
|
RECEIVE_SIZE = 1500
|
92
110
|
|
93
111
|
STR_HOSTNAME = "hostname"
|
@@ -117,6 +135,9 @@ module GamespyQuery
|
|
117
135
|
|
118
136
|
attr_accessor :addr, :data, :state, :stamp, :needs_challenge, :max_packets, :failed
|
119
137
|
|
138
|
+
# Initializes the object
|
139
|
+
# @param [String] addr Server address ("ip:port")
|
140
|
+
# @param [Address Family] address_family
|
120
141
|
def initialize(addr, address_family = ::Socket::AF_INET)
|
121
142
|
@addr, @data, @state, @max_packets = addr, {}, 0, MAX_PACKETS
|
122
143
|
@id_packet = ID_PACKET
|
@@ -126,10 +147,22 @@ module GamespyQuery
|
|
126
147
|
self.connect(*addr.split(":"))
|
127
148
|
end
|
128
149
|
|
150
|
+
# Exception
|
151
|
+
class NotInWriteState < StandardError
|
152
|
+
end
|
153
|
+
|
154
|
+
# Exception
|
155
|
+
class NotInReadState < StandardError
|
156
|
+
end
|
157
|
+
|
158
|
+
# Sets the state of the socket
|
159
|
+
# @param [Integer] state State to set
|
129
160
|
def state=(state); @stamp = Time.now; @state = state; end
|
130
161
|
|
162
|
+
# Is the socket state valid? Only if all states have passed
|
131
163
|
def valid?; @state == STATE_READY; end
|
132
164
|
|
165
|
+
# Handle the write state
|
133
166
|
def handle_write
|
134
167
|
#Tools.debug {"Write: #{self.inspect}, #{self.state}"}
|
135
168
|
|
@@ -146,11 +179,17 @@ module GamespyQuery
|
|
146
179
|
# Send Challenge response
|
147
180
|
self.puts self.needs_challenge ? BASE_PACKET + @id_packet + self.needs_challenge + FULL_INFO_PACKET_MP : BASE_PACKET + @id_packet + FULL_INFO_PACKET_MP
|
148
181
|
self.state = STATE_SENT_CHALLENGE_RESPONSE
|
182
|
+
else
|
183
|
+
raise NotInWriteState, "NotInWriteState"
|
149
184
|
end
|
185
|
+
rescue NotInWriteState => e
|
186
|
+
r = false
|
187
|
+
self.failed = true
|
188
|
+
close unless closed?
|
150
189
|
rescue => e
|
151
190
|
Tools.log_exception e
|
152
191
|
self.failed = true
|
153
|
-
r =
|
192
|
+
r = nil
|
154
193
|
close unless closed?
|
155
194
|
end
|
156
195
|
|
@@ -165,32 +204,26 @@ module GamespyQuery
|
|
165
204
|
r
|
166
205
|
end
|
167
206
|
|
207
|
+
# Handle the read state
|
168
208
|
def handle_read
|
169
209
|
# Tools.debug {"Read: #{self.inspect}, #{self.state}"}
|
170
210
|
|
171
211
|
r = true
|
172
|
-
|
173
|
-
|
174
|
-
|
212
|
+
begin
|
213
|
+
case self.state
|
214
|
+
when STATE_SENT_CHALLENGE
|
175
215
|
data = self.recvfrom_nonblock(RECEIVE_SIZE)
|
176
216
|
Tools.debug {"Read (1): #{self.inspect}: #{data}"}
|
177
217
|
|
178
|
-
handle_challenge
|
218
|
+
handle_challenge data[0]
|
179
219
|
|
180
220
|
self.state = STATE_RECEIVED_CHALLENGE
|
181
|
-
|
182
|
-
Tools.log_exception e
|
183
|
-
self.failed = true
|
184
|
-
r = false
|
185
|
-
close unless closed?
|
186
|
-
end
|
187
|
-
when STATE_SENT_CHALLENGE_RESPONSE, STATE_RECEIVE_DATA
|
188
|
-
begin
|
221
|
+
when STATE_SENT_CHALLENGE_RESPONSE, STATE_RECEIVE_DATA
|
189
222
|
data = self.recvfrom_nonblock(RECEIVE_SIZE)
|
190
223
|
Tools.debug {"Read (3,4): #{self.inspect}: #{data}"}
|
191
224
|
self.state = STATE_RECEIVE_DATA
|
192
225
|
|
193
|
-
game_data =
|
226
|
+
game_data = data[0]
|
194
227
|
Tools.debug {"Received (#{self.data.size + 1}):\n\n#{game_data.inspect}\n\n#{game_data}\n\n"}
|
195
228
|
|
196
229
|
index = handle_splitnum game_data
|
@@ -203,16 +236,25 @@ module GamespyQuery
|
|
203
236
|
r = false
|
204
237
|
close unless closed?
|
205
238
|
end
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
239
|
+
else
|
240
|
+
raise NotInReadState, "NotInReadState"
|
241
|
+
end
|
242
|
+
rescue NotInReadState => e
|
243
|
+
r = false
|
244
|
+
self.failed = true
|
245
|
+
close unless closed?
|
246
|
+
rescue => e
|
247
|
+
# TODO: Simply raise the exception?
|
248
|
+
Tools.log_exception(e)
|
249
|
+
self.failed = true
|
250
|
+
r = nil
|
251
|
+
close unless closed?
|
212
252
|
end
|
213
253
|
r
|
214
254
|
end
|
215
255
|
|
256
|
+
# Handle the exception state
|
257
|
+
# TODO
|
216
258
|
def handle_exc
|
217
259
|
Tools.debug {"Exception: #{self.inspect}"}
|
218
260
|
close unless closed?
|
@@ -221,9 +263,11 @@ module GamespyQuery
|
|
221
263
|
false
|
222
264
|
end
|
223
265
|
|
224
|
-
|
266
|
+
# Process the splitnum provided in the packet
|
267
|
+
# @param [String] packet Packet data
|
268
|
+
def handle_splitnum packet
|
225
269
|
index = 0
|
226
|
-
if
|
270
|
+
if packet.sub(STR_GARBAGE, STR_EMPTY)[RX_SPLITNUM]
|
227
271
|
splitnum = $1
|
228
272
|
flag = splitnum.unpack("C")[0]
|
229
273
|
index = (flag & 127).to_i
|
@@ -238,19 +282,24 @@ module GamespyQuery
|
|
238
282
|
index
|
239
283
|
end
|
240
284
|
|
241
|
-
|
242
|
-
|
243
|
-
|
285
|
+
# Handle the challenge/response, if the server requires it
|
286
|
+
# @param [String] packet Packet to process for challenge/response
|
287
|
+
def handle_challenge packet
|
288
|
+
# Tools.debug{"Received challenge response (#{packet.length}): #{packet.inspect}"}
|
289
|
+
need_challenge = !(packet.sub(STR_X0, STR_EMPTY) =~ RX_NO_CHALLENGE)
|
244
290
|
if need_challenge
|
245
|
-
str =
|
291
|
+
str = packet.sub(RX_CHALLENGE, STR_EMPTY).gsub(RX_CHALLENGE2, STR_EMPTY).to_i
|
246
292
|
challenge_packet = sprintf(STR_BLA, handle_chr(str >> 24), handle_chr(str >> 16), handle_chr(str >> 8), handle_chr(str >> 0))
|
247
293
|
self.needs_challenge = challenge_packet
|
248
294
|
end
|
249
295
|
end
|
250
296
|
|
297
|
+
# Determine Read/Write/Exception state
|
251
298
|
def handle_state; [STATE_INIT, STATE_RECEIVED_CHALLENGE].include? state; end
|
252
299
|
|
300
|
+
# Process data
|
253
301
|
# Supports challenge/response and multi-packet
|
302
|
+
# @param [String] reply Reply from server
|
254
303
|
def sync reply = self.fetch
|
255
304
|
game_data, key = {}, nil
|
256
305
|
return game_data if reply.nil? || reply.empty?
|
@@ -266,6 +315,7 @@ module GamespyQuery
|
|
266
315
|
game_data
|
267
316
|
end
|
268
317
|
|
318
|
+
# Fetch all packets from socket
|
269
319
|
def fetch
|
270
320
|
pings = []
|
271
321
|
r = self.data
|
@@ -275,13 +325,13 @@ module GamespyQuery
|
|
275
325
|
if IO.select(nil, [self], nil, DEFAULT_TIMEOUT)
|
276
326
|
handle_write
|
277
327
|
else
|
278
|
-
raise "TimeOut"
|
328
|
+
raise TimeOutError, "TimeOut"
|
279
329
|
end
|
280
330
|
else
|
281
331
|
if IO.select([self], nil, nil, DEFAULT_TIMEOUT)
|
282
332
|
handle_read
|
283
333
|
else
|
284
|
-
raise "TimeOut"
|
334
|
+
raise TimeOutError, "TimeOut"
|
285
335
|
end
|
286
336
|
end
|
287
337
|
end
|
@@ -295,6 +345,7 @@ module GamespyQuery
|
|
295
345
|
Tools.debug{"Gamespy pings: #{pings}, #{ping}"}
|
296
346
|
@ping = ping
|
297
347
|
rescue => e
|
348
|
+
# TODO: Simply raise the exception?
|
298
349
|
Tools.log_exception(e)
|
299
350
|
r = nil
|
300
351
|
close unless closed?
|
@@ -303,18 +354,3 @@ module GamespyQuery
|
|
303
354
|
end
|
304
355
|
end
|
305
356
|
end
|
306
|
-
|
307
|
-
if $0 == __FILE__
|
308
|
-
host, port = if ARGV.size > 1
|
309
|
-
ARGV
|
310
|
-
else
|
311
|
-
ARGV[0].split(":")
|
312
|
-
end
|
313
|
-
time_start = Time.now
|
314
|
-
g = GamespyQuery::Socket.new("#{host}:#{port}")
|
315
|
-
r = g.sync
|
316
|
-
time_taken = Time.now - time_start
|
317
|
-
puts "Took: #{time_taken}s"
|
318
|
-
exit unless r
|
319
|
-
puts r.to_yaml
|
320
|
-
end
|
@@ -1,18 +1,28 @@
|
|
1
|
-
require_relative 'socket'
|
2
|
-
|
3
1
|
module GamespyQuery
|
2
|
+
# Provides mass processing of Gamespy UDP sockets, by using Socket/IO select
|
4
3
|
class SocketMaster < Base
|
4
|
+
# Should the current queue be extended to the maximum amount of connections, or should the queue be emptied first,
|
5
|
+
# before adding more?
|
5
6
|
FILL_UP_ON_SPACE = true
|
7
|
+
|
8
|
+
# Default maximum concurrent connections
|
6
9
|
DEFAULT_MAX_CONNECTIONS = 128
|
7
10
|
|
8
|
-
|
11
|
+
# Configurable timeout in seconds
|
12
|
+
attr_accessor :timeout
|
13
|
+
|
14
|
+
# Configurable max concurrenct connections
|
15
|
+
attr_accessor :max_connections
|
9
16
|
|
17
|
+
# Initializes the object
|
18
|
+
# @param [Array] addrs List of addresses to process
|
10
19
|
def initialize addrs
|
11
20
|
@addrs = addrs
|
12
21
|
|
13
22
|
@timeout, @max_connections = Socket::DEFAULT_TIMEOUT, DEFAULT_MAX_CONNECTIONS # Per select iteration
|
14
23
|
end
|
15
24
|
|
25
|
+
# Process the list of addresses
|
16
26
|
def process!
|
17
27
|
sockets = []
|
18
28
|
|
@@ -56,6 +66,42 @@ module GamespyQuery
|
|
56
66
|
|
57
67
|
return sockets
|
58
68
|
end
|
69
|
+
|
70
|
+
class <<self
|
71
|
+
# Fetch the gamespy master browser list
|
72
|
+
# Connect to each individual server to receive player data etc
|
73
|
+
# @param [String] game Game to fetch info from
|
74
|
+
# @param [String] geo Geo location enabled?
|
75
|
+
# @param [Array] remote Hostname and path+filename strings if the list needs to be fetched from http server
|
76
|
+
def process_master(game = "arma2oapc", geo = nil, remote = nil)
|
77
|
+
master = GamespyQuery::Master.new(geo, game)
|
78
|
+
list = if remote
|
79
|
+
Net::HTTP.start(remote[0]) do |http|
|
80
|
+
resp = http.get(remote[1])
|
81
|
+
resp.body.split("\n")
|
82
|
+
end
|
83
|
+
else
|
84
|
+
master.get_server_list(nil, true, geo)
|
85
|
+
end
|
86
|
+
|
87
|
+
dat = master.process list
|
88
|
+
|
89
|
+
sm = GamespyQuery::SocketMaster.new(dat.keys)
|
90
|
+
sockets = sm.process!
|
91
|
+
sockets.select{|s| s.valid? }.each do |s|
|
92
|
+
begin
|
93
|
+
data = dat[s.addr]
|
94
|
+
data[:ip], data[:port] = s.addr.split(":")
|
95
|
+
data[:gamename] = game
|
96
|
+
data[:gamedata].merge!(s.sync(s.data))
|
97
|
+
rescue => e
|
98
|
+
Tools.log_exception e
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
dat.values
|
103
|
+
end
|
104
|
+
end
|
59
105
|
end
|
60
106
|
end
|
61
107
|
|