gamespy_query 0.0.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/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in gamespy_query.gemspec
4
+ gemspec
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,24 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "gamespy_query/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "gamespy_query"
7
+ s.version = GamespyQuery::VERSION
8
+ s.authors = ["Patrick Roza"]
9
+ s.email = ["sb@dev-heaven.net"]
10
+ s.homepage = "http://dev-heaven.net"
11
+ s.summary = %q{Ruby library for accessing Gamespy services}
12
+ s.description = %q{}
13
+
14
+ s.rubyforge_project = "gamespy_query"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ # specify any dependencies here; for example:
22
+ # s.add_development_dependency "rspec"
23
+ # s.add_runtime_dependency "rest-client"
24
+ end
@@ -0,0 +1,43 @@
1
+ #require 'six/tools'
2
+ require 'action_controller'
3
+ require 'logger'
4
+
5
+ module GamespyQuery
6
+ module Tools
7
+ STR_EMPTY = ""
8
+
9
+ module_function
10
+ def logger
11
+ ActionController::Base.logger ||= Logger.new("logger.log")
12
+ end
13
+
14
+ def debug(&block)
15
+ logger.debug yield
16
+ end
17
+ end
18
+
19
+ class Base
20
+ def strip_tags(str)
21
+ # TODO: Strip tags!!
22
+ str
23
+ end
24
+
25
+ STR_X00 = "\x00"
26
+ RX_F = /\A\-?[0-9][0-9]*\.[0-9]*\Z/
27
+ RX_I = /\A\-?[0-9][0-9]*\Z/
28
+ RX_S = /\A\-?0[0-9]+.*\Z/
29
+
30
+ def clean(value) # TODO: Force String, Integer, Float etc?
31
+ case value
32
+ when STR_X00
33
+ nil
34
+ when RX_F
35
+ value =~ RX_S ? strip_tags(value) : value.to_f
36
+ when RX_I
37
+ value =~ RX_S ? strip_tags(value) : value.to_i
38
+ else
39
+ strip_tags(value)
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,81 @@
1
+ require_relative 'base'
2
+
3
+ module GamespyQuery
4
+ class Master < Base
5
+ PARAMS = [:hostname, :gamever, :gametype, :gamemode, :numplayers, :maxplayers, :password, :equalModRequired, :mission, :mapname,
6
+ :mod, :signatures, :verifysignatures, :gamestate, :dedicated, :platform, :sv_battleeye, :language, :difficulty]
7
+
8
+ # TODO: Gspy v3 multipacket
9
+
10
+ DELIMIT = case RUBY_PLATFORM
11
+ when /-mingw32$/, /-mswin32$/
12
+ "\\"
13
+ else
14
+ "\\\\"
15
+ end
16
+
17
+ def geoip_path
18
+ return "" unless defined?(Rails)
19
+
20
+ case RUBY_PLATFORM
21
+ when /-mingw32$/, /-mswin32$/
22
+ File.join(Rails.root, "config").gsub("/", "\\")
23
+ else
24
+ File.join(Rails.root, "config")
25
+ end
26
+ end
27
+
28
+ def initialize(geo = nil, game = "arma2oapc")
29
+ @geo, @game = geo, game
30
+ end
31
+
32
+ def process
33
+ @list = Hash.new
34
+ self.to_hash(self.read)
35
+ end
36
+
37
+ def read
38
+ geo = @geo ? @geo : "-Q 11 "
39
+ unless File.exists?(File.join(geoip_path, "GeoIP.dat"))
40
+ puts
41
+ puts "Warning: GeoIP.dat database missing. Can't parse countries. #{GEOIP_PATH}"
42
+ geo = nil
43
+ end
44
+ reply = %x[gslist -p "#{GEOIP_PATH}" -n #{@game} #{geo}-X #{PARAMS.clone.map{|e| "#{DELIMIT}#{e}"}.join("")}]
45
+ reply.gsub!("\\\\\\", "") if geo
46
+ reply.split("\n")
47
+ end
48
+
49
+ RX_H = /\A([\.0-9]*):([0-9]*) *\\(.*)/
50
+ STR_SPLIT = "\\"
51
+ def to_hash(ar)
52
+ ar.each_with_index do |entry, index|
53
+ str = entry[RX_H]
54
+ next unless str
55
+ ip, port, content = $1, $2, $3
56
+ content = content.split(STR_SPLIT)
57
+ content << "" unless (content.size % 2 == 0)
58
+ i = 0
59
+ content.map! do |e|
60
+ i += 1
61
+ i % 2 == 0 ? e : clean(e)
62
+ end
63
+ addr = "#{ip}:#{port}"
64
+ if @list.has_key?(addr)
65
+ e = @list[addr]
66
+ else
67
+ e = Hash.new
68
+ e[:ip] = ip
69
+ e[:port] = port
70
+ @list[addr] = e
71
+ end
72
+ if e[:gamedata]
73
+ e[:gamedata].merge!(Hash[*content])
74
+ else
75
+ e[:gamedata] = Hash[*content]
76
+ end
77
+ end
78
+ @list
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,231 @@
1
+ # encoding: utf-8
2
+
3
+ =begin
4
+ GameSpy parser class by Sickboy [Patrick Roza] (sb_at_dev-heaven.net)
5
+
6
+ Notes:
7
+ Gamedata values are not split, Player lists can be (names, teams, scores, deaths), while individual values still are not.
8
+ =end
9
+
10
+ require_relative 'base'
11
+
12
+ module GamespyQuery
13
+ class Parser
14
+ STR_X0 = "\x00"
15
+ STR_SPLIT = STR_X0
16
+ STR_EMPTY = ""
17
+ STR_ID = "\x00\x04\x05\x06\a"
18
+
19
+ RX_SPLITNUM = /^splitnum\x00(.)/i
20
+ RX_X0_E = /\x00$/
21
+ RX_PLAYER_HEADER = /\x01/
22
+ RX_END = /\x00\x02$/
23
+
24
+ # packets:
25
+ # - Hash, key: packetID, value: packetDATA
26
+ # or
27
+ # - Array, packetDATA ordered already by packetID
28
+ def initialize(packets)
29
+ @packets = case packets
30
+ when Hash
31
+ packets.keys.sort.map{|key| packets[key] }
32
+ when Array
33
+ packets
34
+ else
35
+ raise "Unsupported format"
36
+ end
37
+ end
38
+
39
+ # Returns Hash with parsed data (:game and :players)
40
+ # :game => Hash, Key: InfoKey, Value: InfoValue
41
+ # :players => Hash, Key: InfoType, Value: Array of Values
42
+ def parse
43
+ data = {}
44
+ data[:game] = {} # Key: InfoKey, Value: InfoValue
45
+ data[:players] = {} # Key: InfoType, Value: Array of Values
46
+ player_info = false
47
+ player_data = ""
48
+
49
+ # Parse the packets
50
+ @packets.each do |packet|
51
+ packet = clean_packet(packet)
52
+
53
+ if player_info
54
+ # Player header was found before, add packet to player_data
55
+ player_data += packet
56
+ else
57
+ if packet =~ RX_PLAYER_HEADER
58
+ # Found Player header, packet possibly contains partial gamedata too
59
+ player_info = true
60
+ packets = packet.split(RX_PLAYER_HEADER, 2)
61
+
62
+ # Parse last game_data piece if available
63
+ data[:game].merge!(parse_game_data(packets[0])) unless packets[0].empty?
64
+
65
+ # Collect the player_data if available
66
+ player_data += packets[1]
67
+ else
68
+ # GameData-only
69
+ data[:game].merge!(parse_game_data(packet))
70
+ end
71
+ end
72
+ end
73
+
74
+ # Parse player_data
75
+ unless player_data.empty?
76
+ data[:players] = parse_player_data(player_data)
77
+ end
78
+
79
+ data
80
+ end
81
+
82
+ if RUBY_PLATFORM =~ /mswin32/
83
+ def get_string(str)
84
+ System::Text::Encoding.UTF8.GetString(System::Array.of(System::Byte).new(str.bytes.to_a)).to_s
85
+ end
86
+ else
87
+ def get_string(str)
88
+ #(str + ' ').encode("UTF-8", :invalid => :replace, :undef => :replace)[0..-2]
89
+ str
90
+ end
91
+ end
92
+
93
+ def clean_packet(packet)
94
+ packet = packet.clone
95
+ packet.sub!(STR_ID, STR_EMPTY) # Cut off the identity
96
+ packet.sub!(RX_SPLITNUM, STR_EMPTY) # Cut off the splitnum
97
+ packet.sub!(RX_X0_E, STR_EMPTY) # Cut off last \x00
98
+ packet.sub!(RX_X0_S, STR_EMPTY) # Cut off first \x00
99
+ packet.sub!(RX_END, STR_EMPTY) # Cut off the last \x00\x02
100
+
101
+ # Encoding
102
+ get_string(packet)
103
+ end
104
+
105
+ def parse_game_data(packet)
106
+ Tools.debug {"Game Parsing #{packet.inspect}"}
107
+
108
+ key = nil
109
+ game_data = {}
110
+
111
+ packet.split(STR_SPLIT).each_with_index do |data, index|
112
+ if (index % 2) == 0
113
+ key = data
114
+ else
115
+ game_data[key] = data
116
+ end
117
+ end
118
+
119
+ game_data
120
+ end
121
+
122
+ RX_PLAYER_EMPTY = /^player_\x00\x00\x00/
123
+ RX_PLAYER_INFO = /\x01(team|player|score|deaths)_.(.)/ # \x00 from previous packet, \x01 from continueing player info, (.) - should it overwrite previous value?
124
+ STR_DEATHS = "deaths_\x00\x00"
125
+ STR_PLAYER = "player_\x00\x00"
126
+ STR_TEAM = "team_\x00\x00"
127
+ STR_SCORE = "score_\x00\x00"
128
+
129
+ # TODO: Cleanup
130
+ def parse_player_data(packet)
131
+ Tools.debug {"Player Parsing #{packet.inspect}"}
132
+
133
+ player_data = {:names => [], :teams => [], :scores => [], :deaths => []} # [[], [], [], []]
134
+
135
+ return player_data if packet.nil? || packet.empty?
136
+
137
+ data = packet.clone
138
+ unless data =~ RX_PLAYER_EMPTY
139
+
140
+ # Leave out the character or Replace character with special string later used to replace the previous value
141
+ data.sub!(RX_PLAYER_INFO) { |r|
142
+ str = $1
143
+ if $2 == STR_X0
144
+ # If a proper primary info header of this type was not yet found, replace this secondary header with a proper primary header
145
+ # This will add the broken info header to the previous info list (name for team, team for score, score for deaths)
146
+ # However the resulting arrays are limited to num_players, so the info is discared anyway.
147
+ # TODO: Cleaner implementation!
148
+ data =~ /(^|[^\x01])#{str}_\x00\x00/ ? STR_X0 : :"#{str}_\x00\x00"
149
+ else
150
+ STR_SIX_X0
151
+ end
152
+ }
153
+
154
+ data, deaths = data.split(STR_DEATHS, 2)
155
+ data, scores = data.split(STR_SCORE, 2)
156
+ data, teams = data.split(STR_TEAM, 2)
157
+ data, names = data.split(STR_PLAYER, 2)
158
+
159
+ orig_data = [names, teams, scores, deaths]
160
+
161
+ # TODO: Handle seperate score
162
+ orig_data.each_with_index do |data, i|
163
+ next if data.nil? || data.empty?
164
+ str = data.clone
165
+
166
+ str.sub!(RX_X0_E, STR_EMPTY) # Remove last \x00
167
+
168
+ # Parse the data - \x00 is printed after a non-nil entry, otherwise \x00 means nil (e.g empty team)
169
+ until str.empty?
170
+ entry = str[RX_X0_SPEC]
171
+ player_data[player_data.keys[i]] << entry.sub(STR_X0, STR_EMPTY)
172
+ str.sub!(entry, STR_EMPTY)
173
+ end
174
+
175
+ # Search for SIX string to overwrite last entry
176
+ new_player_data = []
177
+ overwrite = false
178
+ player_data[player_data.keys[i]].each do |info|
179
+ if info == STR_SIX
180
+ overwrite = true # tag so that the next entry will overwrite the latest entry
181
+ next # ignore
182
+ else
183
+ if overwrite
184
+ new_player_data[-1] = info # Overwrite latest entry
185
+ overwrite = false # done the overwrite
186
+ else
187
+ #break if new_player_data.size == num_players
188
+ new_player_data << info # insert entry
189
+ end
190
+ end
191
+ end
192
+ player_data[player_data.keys[i]] = new_player_data
193
+ end
194
+ end
195
+
196
+ player_data
197
+ end
198
+
199
+ # Hash of Hashes
200
+ def self.pretty_player_data(data)
201
+ player_data = {}
202
+
203
+ data[:names].each_with_index do |name, index|
204
+ player_data[name] = {}
205
+ player_data[name][:team] = data[:teams][index]
206
+ player_data[name][:score] = data[:scores][index]
207
+ player_data[name][:deaths] = data[:deaths][index]
208
+ end
209
+
210
+ player_data
211
+ end
212
+
213
+ # Array of Hashes
214
+ def self.pretty_player_data2(data)
215
+ player_data = []
216
+
217
+ data[:names].each_with_index do |name, index|
218
+ player = {}
219
+
220
+ player[:name] = name
221
+ player[:team] = data[:teams][index]
222
+ player[:score] = data[:scores][index]
223
+ player[:deaths] = data[:deaths][index]
224
+
225
+ player_data << player
226
+ end
227
+
228
+ player_data
229
+ end
230
+ end
231
+ end
@@ -0,0 +1,268 @@
1
+ # encoding: utf-8
2
+ # GameSpy query class by Sickboy [Patrick Roza] (sb_at_dev-heaven.net)
3
+
4
+ require 'yaml'
5
+ require_relative 'base'
6
+ require_relative 'parser'
7
+
8
+ module GamespyQuery
9
+ class Socket < Base
10
+ TIMEOUT = 3
11
+ MAX_PACKETS = 7
12
+
13
+ ID_PACKET = [0x04, 0x05, 0x06, 0x07].pack("c*") # TODO: Randomize
14
+ BASE_PACKET = [0xFE, 0xFD, 0x00].pack("c*")
15
+ CHALLENGE_PACKET = [0xFE, 0xFD, 0x09].pack("c*")
16
+
17
+ FULL_INFO_PACKET_MP = [0xFF, 0xFF, 0xFF, 0x01].pack("c*")
18
+ FULL_INFO_PACKET = [0xFF, 0xFF, 0xFF].pack("c*")
19
+ SERVER_INFO_PACKET = [0xFF, 0x00, 0x00].pack("c*")
20
+ PLAYER_INFO_PACKET = [0x00, 0xFF, 0x00].pack("c*")
21
+
22
+ STR_HOSTNAME = "hostname"
23
+ STR_PLAYERS = "players"
24
+ STR_DEATHS = "deaths_\x00\x00"
25
+ STR_PLAYER = "player_\x00\x00"
26
+ STR_TEAM = "team_\x00\x00"
27
+ STR_SCORE = "score_\x00\x00"
28
+ STR_X0, STR_X1, STR_X2 = "\x00", "\x01", "\x02"
29
+ SPLIT = STR_X0
30
+ STR_END = "\x00\x02"
31
+ STR_EMPTY = Tools::STR_EMPTY
32
+ STR_BLA = "%c%c%c%c".encode("ASCII-8BIT")
33
+ STR_GARBAGE = "\x00\x04\x05\x06\a"
34
+ STR_SIX = "$SIX_OVERWRITE_PREVIOUS$"
35
+ STR_SIX_X0 = "\x00#{STR_SIX}\x00"
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
+ RX_X0, RX_X0_S, RX_X0_E = /\x00/, /^\x00/, /\x00$/
40
+ RX_X0_SPEC = /^\x00|[^\x00]+\x00?/
41
+
42
+ RX_NO_CHALLENGE = /0@0$/
43
+ RX_CHALLENGE = /0@/
44
+ RX_CHALLENGE2 = /[^0-9\-]/si
45
+ RX_SPLITNUM = /^splitnum\x00(.)/i
46
+
47
+ def create_socket(*params)
48
+ puts "Creating socket #{params}"
49
+ _create_socket(*params)
50
+ end
51
+
52
+ def socket_send(*params)
53
+ puts "Sending socket #{params}"
54
+ _socket_send(*params)
55
+ end
56
+
57
+ def socket_receive(*params)
58
+ puts "Receiving socket #{params}"
59
+ _socket_receive(*params)
60
+ end
61
+
62
+ def socket_close(*params)
63
+ puts "Closing socket #{params}"
64
+ _socket_close(*params)
65
+ end
66
+
67
+ def get_string(*params)
68
+ puts "Getting string #{params}"
69
+ _get_string(*params)
70
+ end
71
+
72
+ if RUBY_PLATFORM =~ /mswin32/
73
+ include System::Net
74
+ include System::Net::Sockets
75
+
76
+ def get_string(str)
77
+ 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
78
+ end
79
+
80
+ def _create_socket(host, port)
81
+ @ip_end_point = IPEndPoint.new(IPAddress.Any, 0)
82
+ @s = UdpClient.new
83
+ @s.client.receive_timeout = TIMEOUT * 1000
84
+ @s.connect(host, port.to_i)
85
+ end
86
+
87
+ def _socket_send(packet)
88
+ @s.Send(packet, packet.length)
89
+ end
90
+
91
+ def _socket_receive
92
+ @s.Receive(@ip_end_point)
93
+ end
94
+
95
+ def _socket_close
96
+ @s.close
97
+ end
98
+ else
99
+ require 'socket'
100
+ require 'timeout'
101
+
102
+ def get_string(str)
103
+ str
104
+ end
105
+
106
+ def _create_socket(host, port)
107
+ @s = UDPSocket.new
108
+ @s.connect(host, port)
109
+ end
110
+
111
+ def _socket_send(packet)
112
+ @s.puts(packet)
113
+ end
114
+
115
+ def _socket_receive
116
+ begin
117
+ Timeout::timeout(TIMEOUT) do
118
+ @s.recvfrom(4096)
119
+ end
120
+ rescue Timeout::Error
121
+ #socket_close
122
+ raise TimeoutError
123
+ end
124
+ end
125
+
126
+ def _socket_close
127
+ @s.close
128
+ end
129
+ end
130
+
131
+ attr_accessor :silent
132
+ def initialize(host, port, silent = nil)
133
+ @host, @port, @silent = host, port, silent
134
+ end
135
+
136
+ # Supports challenge/response and multi-packet
137
+ def sync
138
+ game_data, key, reply = {}, nil, self.fetch
139
+ return game_data if reply.nil?
140
+
141
+ parser = Parser.new(reply)
142
+ data = parser.parse
143
+
144
+ game_data.merge!(data[:game])
145
+ game_data["players"] = Parser.pretty_player_data2(data[:players]).sort {|a, b| a[:name].downcase <=> b[:name].downcase }
146
+
147
+ game_data["ping"] = @ping unless @ping.nil?
148
+
149
+ game_data
150
+ end
151
+
152
+ def fetch
153
+ data = {}
154
+ status, reply = nil, nil
155
+
156
+ # Prepare socket / endpoint and connect
157
+ create_socket(@host, @port)
158
+
159
+ # Prepare and send challenge request
160
+ # TODO: Randomize
161
+ id_packet = ID_PACKET
162
+ packet = CHALLENGE_PACKET + id_packet
163
+ Tools.debug{"Sending Challenge (#{packet.length}): #{packet.inspect}"}
164
+ sent = Time.now
165
+
166
+ socket_send(packet)
167
+
168
+ pings = []
169
+
170
+ challenge, received = nil, nil
171
+ begin
172
+ # By default, Blocks until a message returns on this socket from a remote host.
173
+ reply = socket_receive
174
+ received = Time.now
175
+ # TODO: Improve ping test?
176
+ ping = received - sent
177
+ pings << ping
178
+ Tools.debug {"PingTest: #{ping}"}
179
+ challenge = reply[0]
180
+ rescue nil, Exception => e # Cannot use ensure as we want to keep the socket open :P
181
+ socket_close
182
+ raise e
183
+ end
184
+ return nil if challenge.nil? || challenge.empty?
185
+
186
+ # Prepare challenge response, if needed
187
+ str = get_string(challenge)
188
+ Tools.debug{"Received challenge response (#{str.length}): #{str.inspect}"}
189
+ need_challenge = !(str.sub(STR_X0, STR_EMPTY) =~ RX_NO_CHALLENGE)
190
+
191
+ if need_challenge
192
+ Tools.debug {"Needs challenge!"}
193
+ str = str.sub(RX_CHALLENGE, STR_EMPTY).gsub(RX_CHALLENGE2, STR_EMPTY).to_i
194
+ challenge_packet = sprintf(STR_BLA, handle_chr(str >> 24), handle_chr(str >> 16), handle_chr(str >> 8), handle_chr(str >> 0))
195
+ end
196
+
197
+ # Prepare and send info request packet
198
+ packet = need_challenge ? BASE_PACKET + id_packet + challenge_packet + FULL_INFO_PACKET_MP : BASE_PACKET + id_packet + FULL_INFO_PACKET_MP
199
+ Tools.debug{"Sending:\n#{packet.inspect}"}
200
+ sent = Time.now
201
+ socket_send(packet)
202
+
203
+ # Receive response to info request packet, up to 7 packets of information, each limited to 1400 bytes
204
+ max_packets = MAX_PACKETS # Default max
205
+ begin
206
+ # In case some server info didn't fit in a single packet, there will be no proper END OF DATA signal
207
+ # So we manually quit after reaching MAX_PACKETS.
208
+ until data.size >= max_packets
209
+ reply = socket_receive
210
+
211
+ if data.empty?
212
+ received = Time.now
213
+ ping = received - sent
214
+ pings << ping
215
+ Tools.debug {"PingTest: #{ping}"}
216
+ end
217
+ index = 0
218
+
219
+ game_data = get_string(reply[0])
220
+ Tools.debug {"Received (#{data.size + 1}):\n\n#{game_data.inspect}\n\n#{game_data}\n\n"}
221
+
222
+ if game_data.sub(STR_GARBAGE, STR_EMPTY)[RX_SPLITNUM]
223
+ splitnum = $1
224
+ flag = splitnum.unpack("C")[0]
225
+ index = (flag & 127).to_i
226
+ last = flag & 0x80 > 0
227
+ # Data could be received out of order, use the "index" id when "last" flag is true, to determine total packet_count
228
+ max_packets = index + 1 if last # update the max
229
+ Tools.debug {"Splitnum: #{splitnum.inspect} (#{splitnum}) (#{flag}, #{index}, #{last}) Max: #{max_packets}"}
230
+ else
231
+ max_packets = 1
232
+ end
233
+ data[index] = game_data #.sub(RX_X0_S, STR_EMPTY) # Cut off first \x00 from package
234
+ end
235
+ ensure
236
+ socket_close
237
+ end
238
+
239
+ pings.map!{|ping| (ping * 1000).round}
240
+ pings_c = 0
241
+ pings.each { |ping| pings_c += ping }
242
+
243
+ ping = pings_c / pings.size
244
+ Tools.debug{"Gamespy pings: #{pings}, #{ping}"}
245
+
246
+ return nil if data.keys.empty?
247
+ @ping = ping
248
+ data.each_pair {|k, d| Tools.debug {"GSPY Infos: #{k} #{d.size}"} } unless @silent || !$debug
249
+
250
+ data
251
+ end
252
+
253
+ def handle_chr(number)
254
+ number = ((number % 256)+256) if number < 0
255
+ number = number % 256 if number > 255
256
+ number
257
+ end
258
+ end
259
+ end
260
+
261
+ if $0 == __FILE__
262
+ host = ARGV[0]
263
+ port = ARGV[1]
264
+ g = Six::Query::Gamespy.new(host, port)
265
+ r = g.sync
266
+ exit unless r
267
+ puts r.to_yaml
268
+ end
@@ -0,0 +1,3 @@
1
+ module GamespyQuery
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,8 @@
1
+ require "gamespy_query/version"
2
+ require "gamespy_query/base"
3
+ require "gamespy_query/socket"
4
+ require "gamespy_query/master"
5
+
6
+ module GamespyQuery
7
+ # Your code goes here...
8
+ end
metadata ADDED
@@ -0,0 +1,55 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: gamespy_query
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Patrick Roza
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-01-06 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: ''
15
+ email:
16
+ - sb@dev-heaven.net
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - .gitignore
22
+ - Gemfile
23
+ - Rakefile
24
+ - gamespy_query.gemspec
25
+ - lib/gamespy_query.rb
26
+ - lib/gamespy_query/base.rb
27
+ - lib/gamespy_query/master.rb
28
+ - lib/gamespy_query/parser.rb
29
+ - lib/gamespy_query/socket.rb
30
+ - lib/gamespy_query/version.rb
31
+ homepage: http://dev-heaven.net
32
+ licenses: []
33
+ post_install_message:
34
+ rdoc_options: []
35
+ require_paths:
36
+ - lib
37
+ required_ruby_version: !ruby/object:Gem::Requirement
38
+ none: false
39
+ requirements:
40
+ - - ! '>='
41
+ - !ruby/object:Gem::Version
42
+ version: '0'
43
+ required_rubygems_version: !ruby/object:Gem::Requirement
44
+ none: false
45
+ requirements:
46
+ - - ! '>='
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
49
+ requirements: []
50
+ rubyforge_project: gamespy_query
51
+ rubygems_version: 1.8.13
52
+ signing_key:
53
+ specification_version: 3
54
+ summary: Ruby library for accessing Gamespy services
55
+ test_files: []