gamespy_query 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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: []