gamespy_query 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/gamespy_query/base.rb +24 -8
- data/lib/gamespy_query/master.rb +4 -5
- data/lib/gamespy_query/parser.rb +5 -16
- data/lib/gamespy_query/socket.rb +28 -29
- data/lib/gamespy_query/socket_master.rb +2 -2
- data/lib/gamespy_query/version.rb +1 -1
- data/lib/gamespy_query.rb +9 -3
- metadata +1 -1
data/lib/gamespy_query/base.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
#require 'six/tools'
|
2
|
-
require 'action_controller'
|
3
2
|
require 'logger'
|
4
3
|
|
5
4
|
module GamespyQuery
|
@@ -12,12 +11,29 @@ module GamespyQuery
|
|
12
11
|
|
13
12
|
module Tools
|
14
13
|
STR_EMPTY = ""
|
14
|
+
CHAR_N = "\n"
|
15
15
|
|
16
16
|
module_function
|
17
17
|
def logger
|
18
|
-
@logger ||= ActionController::Base.logger || Logger.new("logger.log")
|
18
|
+
@logger ||= if defined?(::Tools); ::Tools.logger; else; defined?(ActionController) ? ActionController::Base.logger || Logger.new("logger.log") : Logger.new("logger.log"); end
|
19
19
|
end
|
20
|
-
|
20
|
+
|
21
|
+
def dbg_msg(e)
|
22
|
+
"#{e.class}: #{e.message if e.respond_to?(:backtrace)}
|
23
|
+
BackTrace: #{e.backtrace.join(CHAR_N) unless !e.respond_to?(:backtrace) || e.backtrace.nil?}"
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
def log_exception(e, as_error = true, msg = "")
|
28
|
+
if defined?(::Tools)
|
29
|
+
::Tools.log_exception(e, as_error, msg)
|
30
|
+
else
|
31
|
+
puts "Error: #{e.class} #{e.message}, #{e.backtrace.join("\n")}"
|
32
|
+
logger.error "#{"#{msg}:" unless msg.empty?}#{e.class} #{e.message}" if as_error
|
33
|
+
logger.debug dbg_msg(e)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
21
37
|
def debug(&block)
|
22
38
|
return unless DEBUG
|
23
39
|
logger.debug yield
|
@@ -59,7 +75,7 @@ module GamespyQuery
|
|
59
75
|
end
|
60
76
|
|
61
77
|
def get_string(*params)
|
62
|
-
|
78
|
+
Tools.debug {"Getting string #{params}"}
|
63
79
|
_get_string(*params)
|
64
80
|
end
|
65
81
|
|
@@ -67,14 +83,14 @@ module GamespyQuery
|
|
67
83
|
include System::Net
|
68
84
|
include System::Net::Sockets
|
69
85
|
|
70
|
-
def
|
71
|
-
str.
|
86
|
+
def get_string(str)
|
87
|
+
System::Text::Encoding.UTF8.GetString(System::Array.of(System::Byte).new(str.bytes.to_a)).to_s # # 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
|
72
88
|
end
|
73
89
|
else
|
74
90
|
require 'socket'
|
75
91
|
require 'timeout'
|
76
|
-
|
77
|
-
|
92
|
+
def get_string(str)
|
93
|
+
#(str + ' ').encode("UTF-8", :invalid => :replace, :undef => :replace)[0..-2]
|
78
94
|
str
|
79
95
|
end
|
80
96
|
end
|
data/lib/gamespy_query/master.rb
CHANGED
@@ -5,7 +5,7 @@ module GamespyQuery
|
|
5
5
|
PARAMS = [:hostname, :gamever, :gametype, :gamemode, :numplayers, :maxplayers, :password, :equalModRequired, :mission, :mapname,
|
6
6
|
:mod, :signatures, :verifysignatures, :gamestate, :dedicated, :platform, :sv_battleeye, :language, :difficulty]
|
7
7
|
|
8
|
-
|
8
|
+
RX_ADDR_LINE = /([\d\.]+)[\s\t]*(\d+)/
|
9
9
|
|
10
10
|
DELIMIT = case RUBY_PLATFORM
|
11
11
|
when /-mingw32$/, /-mswin32$/
|
@@ -38,7 +38,7 @@ module GamespyQuery
|
|
38
38
|
addrs = []
|
39
39
|
list = %x[gslist -n #{@game}] if list.nil?
|
40
40
|
list.split("\n").each do |line|
|
41
|
-
addrs << "#{$1}:#{$2}" if line =~
|
41
|
+
addrs << "#{$1}:#{$2}" if line =~ RX_ADDR_LINE
|
42
42
|
end
|
43
43
|
addrs
|
44
44
|
end
|
@@ -46,8 +46,7 @@ module GamespyQuery
|
|
46
46
|
def read
|
47
47
|
geo = @geo ? @geo : "-Q 11 "
|
48
48
|
unless File.exists?(File.join(geoip_path, "GeoIP.dat"))
|
49
|
-
|
50
|
-
puts "Warning: GeoIP.dat database missing. Can't parse countries. #{geoip_path}"
|
49
|
+
Tools.logger.warn "Warning: GeoIP.dat database missing. Can't parse countries. #{geoip_path}"
|
51
50
|
geo = nil
|
52
51
|
end
|
53
52
|
reply = %x[gslist -p "#{geoip_path}" -n #{@game} #{geo}-X #{PARAMS.clone.map{|e| "#{DELIMIT}#{e}"}.join("")}]
|
@@ -67,7 +66,7 @@ module GamespyQuery
|
|
67
66
|
i = 0
|
68
67
|
content.map! do |e|
|
69
68
|
i += 1
|
70
|
-
i % 2 == 0 ? e :
|
69
|
+
i % 2 == 0 ? e : clean_string(e)
|
71
70
|
end
|
72
71
|
addr = "#{ip}:#{port}"
|
73
72
|
if @list.has_key?(addr)
|
data/lib/gamespy_query/parser.rb
CHANGED
@@ -10,7 +10,7 @@
|
|
10
10
|
require_relative 'base'
|
11
11
|
|
12
12
|
module GamespyQuery
|
13
|
-
class Parser
|
13
|
+
class Parser < Base
|
14
14
|
STR_SPLIT = STR_X0
|
15
15
|
STR_ID = "\x00\x04\x05\x06\a"
|
16
16
|
|
@@ -76,17 +76,6 @@ module GamespyQuery
|
|
76
76
|
data
|
77
77
|
end
|
78
78
|
|
79
|
-
if RUBY_PLATFORM =~ /mswin32/
|
80
|
-
def get_string(str)
|
81
|
-
System::Text::Encoding.UTF8.GetString(System::Array.of(System::Byte).new(str.bytes.to_a)).to_s
|
82
|
-
end
|
83
|
-
else
|
84
|
-
def get_string(str)
|
85
|
-
#(str + ' ').encode("UTF-8", :invalid => :replace, :undef => :replace)[0..-2]
|
86
|
-
str
|
87
|
-
end
|
88
|
-
end
|
89
|
-
|
90
79
|
def clean_packet(packet)
|
91
80
|
packet = packet.clone
|
92
81
|
packet.sub!(STR_ID, STR_EMPTY) # Cut off the identity
|
@@ -99,7 +88,7 @@ module GamespyQuery
|
|
99
88
|
get_string(packet)
|
100
89
|
end
|
101
90
|
|
102
|
-
def
|
91
|
+
def clean_string(str)
|
103
92
|
str.encode("UTF-8", invalid: :replace, undef: :replace)
|
104
93
|
end
|
105
94
|
|
@@ -111,9 +100,9 @@ module GamespyQuery
|
|
111
100
|
|
112
101
|
packet.split(STR_SPLIT).each_with_index do |data, index|
|
113
102
|
if (index % 2) == 0
|
114
|
-
key =
|
103
|
+
key = clean_string data
|
115
104
|
else
|
116
|
-
game_data[key] = data.is_a?(String) ?
|
105
|
+
game_data[key] = data.is_a?(String) ? clean_string(data) : data
|
117
106
|
end
|
118
107
|
end
|
119
108
|
|
@@ -171,7 +160,7 @@ module GamespyQuery
|
|
171
160
|
# Parse the data - \x00 is printed after a non-nil entry, otherwise \x00 means nil (e.g empty team)
|
172
161
|
until str.empty?
|
173
162
|
entry = str[RX_X0_SPEC]
|
174
|
-
player_data[player_data.keys[i]] <<
|
163
|
+
player_data[player_data.keys[i]] << clean_string(entry.sub(STR_X0, STR_EMPTY))
|
175
164
|
str.sub!(entry, STR_EMPTY)
|
176
165
|
end
|
177
166
|
|
data/lib/gamespy_query/socket.rb
CHANGED
@@ -1,32 +1,31 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
# GameSpy query class by Sickboy [Patrick Roza] (sb_at_dev-heaven.net)
|
3
3
|
|
4
|
-
|
5
|
-
|
6
4
|
require 'yaml'
|
7
5
|
require_relative 'base'
|
8
6
|
require_relative 'parser'
|
9
7
|
require 'socket'
|
10
8
|
|
11
9
|
module GamespyQuery
|
10
|
+
# TODO
|
12
11
|
module MultiSocket
|
13
12
|
def create_socket(*params)
|
14
|
-
|
13
|
+
Tools.debug {"Creating socket #{params}"}
|
15
14
|
_create_socket(*params)
|
16
15
|
end
|
17
16
|
|
18
17
|
def socket_send(*params)
|
19
|
-
|
18
|
+
Tools.debug {"Sending socket #{params}"}
|
20
19
|
_socket_send(*params)
|
21
20
|
end
|
22
21
|
|
23
22
|
def socket_receive(*params)
|
24
|
-
|
23
|
+
Tools.debug {"Receiving socket #{params}"}
|
25
24
|
_socket_receive(*params)
|
26
25
|
end
|
27
26
|
|
28
27
|
def socket_close(*params)
|
29
|
-
|
28
|
+
Tools.debug {"Closing socket #{params}"}
|
30
29
|
_socket_close(*params)
|
31
30
|
end
|
32
31
|
|
@@ -136,32 +135,32 @@ module GamespyQuery
|
|
136
135
|
def valid?; @state == STATE_READY; end
|
137
136
|
|
138
137
|
def handle_write
|
139
|
-
#
|
138
|
+
#Tools.debug {"Write: #{self.inspect}, #{self.state}"}
|
140
139
|
|
141
140
|
r = true
|
142
141
|
begin
|
143
142
|
case self.state
|
144
143
|
when STATE_INIT
|
145
|
-
|
144
|
+
Tools.debug {"Write (0): #{self.inspect}"}
|
146
145
|
# Send Challenge
|
147
146
|
self.puts @packet
|
148
147
|
self.state = STATE_SENT_CHALLENGE
|
149
148
|
when STATE_RECEIVED_CHALLENGE
|
150
|
-
|
149
|
+
Tools.debug {"Write (2): #{self.inspect}"}
|
151
150
|
# Send Challenge response
|
152
151
|
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
152
|
self.state = STATE_SENT_CHALLENGE_RESPONSE
|
154
153
|
end
|
155
154
|
rescue => e
|
156
|
-
|
155
|
+
Tools.log_exception e
|
157
156
|
self.failed = true
|
158
157
|
r = false
|
159
|
-
close
|
158
|
+
close unless closed?
|
160
159
|
end
|
161
160
|
|
162
161
|
=begin
|
163
162
|
if Time.now - self.stamp > @timeout
|
164
|
-
|
163
|
+
Tools.debug {"TimedOut: #{self.inspect}"}
|
165
164
|
self.failed = true
|
166
165
|
r = false
|
167
166
|
close unless closed?
|
@@ -171,28 +170,28 @@ module GamespyQuery
|
|
171
170
|
end
|
172
171
|
|
173
172
|
def handle_read
|
174
|
-
#
|
173
|
+
# Tools.debug {"Read: #{self.inspect}, #{self.state}"}
|
175
174
|
|
176
175
|
r = true
|
177
176
|
case self.state
|
178
177
|
when STATE_SENT_CHALLENGE
|
179
178
|
begin
|
180
179
|
data = self.recvfrom_nonblock(RECEIVE_SIZE)
|
181
|
-
|
180
|
+
Tools.debug {"Read (1): #{self.inspect}: #{data}"}
|
182
181
|
|
183
182
|
handle_challenge get_string(data[0])
|
184
183
|
|
185
184
|
self.state = STATE_RECEIVED_CHALLENGE
|
186
185
|
rescue => e
|
187
|
-
|
186
|
+
Tools.log_exception e
|
188
187
|
self.failed = true
|
189
188
|
r = false
|
190
|
-
close
|
189
|
+
close unless closed?
|
191
190
|
end
|
192
191
|
when STATE_SENT_CHALLENGE_RESPONSE, STATE_RECEIVE_DATA
|
193
192
|
begin
|
194
193
|
data = self.recvfrom_nonblock(RECEIVE_SIZE)
|
195
|
-
|
194
|
+
Tools.debug {"Read (3,4): #{self.inspect}: #{data}"}
|
196
195
|
self.state = STATE_RECEIVE_DATA
|
197
196
|
|
198
197
|
game_data = get_string(data[0])
|
@@ -203,30 +202,29 @@ module GamespyQuery
|
|
203
202
|
self.data[index] = game_data
|
204
203
|
|
205
204
|
if self.data.size >= self.max_packets # OR we received the end-packet and all packets required
|
206
|
-
|
205
|
+
Tools.debug {"Received packet limit: #{self.inspect}"}
|
207
206
|
self.state = STATE_READY
|
208
207
|
r = false
|
209
208
|
close unless closed?
|
210
209
|
end
|
211
210
|
rescue => e
|
212
|
-
|
211
|
+
Tools.log_exception(e)
|
213
212
|
self.failed = true
|
214
213
|
r = false
|
215
|
-
close
|
214
|
+
close unless closed?
|
216
215
|
end
|
217
216
|
end
|
218
217
|
r
|
219
218
|
end
|
220
219
|
|
221
220
|
def handle_exc
|
222
|
-
|
223
|
-
close
|
221
|
+
Tools.debug {"Exception: #{self.inspect}"}
|
222
|
+
close unless closed?
|
224
223
|
self.failed = true
|
225
224
|
|
226
225
|
false
|
227
226
|
end
|
228
227
|
|
229
|
-
|
230
228
|
def handle_splitnum game_data
|
231
229
|
index = 0
|
232
230
|
if game_data.sub(STR_GARBAGE, STR_EMPTY)[RX_SPLITNUM]
|
@@ -236,7 +234,7 @@ module GamespyQuery
|
|
236
234
|
last = flag & 0x80 > 0
|
237
235
|
# Data could be received out of order, use the "index" id when "last" flag is true, to determine total packet_count
|
238
236
|
self.max_packets = index + 1 if last # update the max
|
239
|
-
|
237
|
+
Tools.debug {"Splitnum: #{splitnum.inspect} (#{splitnum}) (#{flag}, #{index}, #{last}) Max: #{self.max_packets}"}
|
240
238
|
else
|
241
239
|
self.max_packets = 1
|
242
240
|
end
|
@@ -245,7 +243,7 @@ module GamespyQuery
|
|
245
243
|
end
|
246
244
|
|
247
245
|
def handle_challenge str
|
248
|
-
#
|
246
|
+
# Tools.debug{"Received challenge response (#{str.length}): #{str.inspect}"}
|
249
247
|
need_challenge = !(str.sub(STR_X0, STR_EMPTY) =~ RX_NO_CHALLENGE)
|
250
248
|
if need_challenge
|
251
249
|
str = str.sub(RX_CHALLENGE, STR_EMPTY).gsub(RX_CHALLENGE2, STR_EMPTY).to_i
|
@@ -257,8 +255,8 @@ module GamespyQuery
|
|
257
255
|
def handle_state; [STATE_INIT, STATE_RECEIVED_CHALLENGE].include? state; end
|
258
256
|
|
259
257
|
# Supports challenge/response and multi-packet
|
260
|
-
def sync
|
261
|
-
game_data, key
|
258
|
+
def sync reply = self.fetch
|
259
|
+
game_data, key = {}, nil
|
262
260
|
return game_data if reply.nil?
|
263
261
|
|
264
262
|
parser = Parser.new(reply)
|
@@ -297,12 +295,13 @@ module GamespyQuery
|
|
297
295
|
pings_c = 0
|
298
296
|
pings.each { |ping| pings_c += ping }
|
299
297
|
|
300
|
-
ping = pings.size == 0 ? nil : pings_c / pings.
|
298
|
+
ping = pings.size == 0 ? nil : pings_c / pings.size
|
301
299
|
Tools.debug{"Gamespy pings: #{pings}, #{ping}"}
|
302
300
|
@ping = ping
|
303
301
|
rescue => e
|
304
|
-
|
302
|
+
Tools.log_exception(e)
|
305
303
|
r = nil
|
304
|
+
close unless closed?
|
306
305
|
end
|
307
306
|
r
|
308
307
|
end
|
@@ -35,13 +35,13 @@ module GamespyQuery
|
|
35
35
|
write_sockets, read_sockets = queue.reject {|s| s.valid? }.partition {|s| s.handle_state }
|
36
36
|
|
37
37
|
unless ready = IO.select(read_sockets, write_sockets, nil, @timeout)
|
38
|
-
|
38
|
+
Tools.logger.warn "Timeout, no usable sockets in current queue, within timeout period (#{@timeout}s)"
|
39
39
|
queue.each{|s| s.close unless s.closed?}
|
40
40
|
queue = []
|
41
41
|
next
|
42
42
|
end
|
43
43
|
|
44
|
-
|
44
|
+
Tools.debug {"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
45
|
|
46
46
|
# Read
|
47
47
|
ready[0].each { |s| queue.delete(s) unless s.handle_read() }
|
data/lib/gamespy_query.rb
CHANGED
@@ -10,10 +10,16 @@ end
|
|
10
10
|
|
11
11
|
|
12
12
|
if $0 == __FILE__
|
13
|
-
host = ARGV
|
14
|
-
|
15
|
-
|
13
|
+
host, port = if ARGV.size > 1
|
14
|
+
ARGV
|
15
|
+
else
|
16
|
+
ARGV[0].split(":")
|
17
|
+
end
|
18
|
+
time_start = Time.now
|
19
|
+
g = GamespyQuery::Socket.new("#{host}:#{port}")
|
16
20
|
r = g.sync
|
21
|
+
time_taken = Time.now - time_start
|
22
|
+
puts "Took: #{time_taken}s"
|
17
23
|
exit unless r
|
18
24
|
puts r.to_yaml
|
19
25
|
end
|