gamespy_query 0.1.0 → 0.1.1
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/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
|