gamespy_query 0.0.5 → 0.1.0
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 +0 -0
- data/Gemfile +0 -0
- data/README.md +0 -0
- data/Rakefile +0 -0
- data/gamespy_query.gemspec +0 -0
- data/lib/gamespy_query/base.rb +87 -55
- data/lib/gamespy_query/master.rb +90 -81
- data/lib/gamespy_query/parser.rb +234 -234
- data/lib/gamespy_query/socket.rb +325 -266
- data/lib/gamespy_query/socket_master.rb +78 -0
- data/lib/gamespy_query/version.rb +1 -1
- data/lib/gamespy_query.rb +1 -0
- metadata +3 -2
data/lib/gamespy_query/parser.rb
CHANGED
@@ -1,234 +1,234 @@
|
|
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_SPLIT = STR_X0
|
15
|
-
STR_ID = "\x00\x04\x05\x06\a"
|
16
|
-
|
17
|
-
RX_SPLITNUM = /^splitnum\x00(.)/i
|
18
|
-
RX_PLAYER_HEADER = /\x01/
|
19
|
-
RX_END = /\x00\x02$/
|
20
|
-
|
21
|
-
# packets:
|
22
|
-
# - Hash, key: packetID, value: packetDATA
|
23
|
-
# or
|
24
|
-
# - Array, packetDATA ordered already by packetID
|
25
|
-
def initialize(packets)
|
26
|
-
@packets = case packets
|
27
|
-
when Hash
|
28
|
-
packets.keys.sort.map{|key| packets[key] }
|
29
|
-
when Array
|
30
|
-
packets
|
31
|
-
else
|
32
|
-
raise "Unsupported format"
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
# Returns Hash with parsed data (:game and :players)
|
37
|
-
# :game => Hash, Key: InfoKey, Value: InfoValue
|
38
|
-
# :players => Hash, Key: InfoType, Value: Array of Values
|
39
|
-
def parse
|
40
|
-
data = {}
|
41
|
-
data[:game] = {} # Key: InfoKey, Value: InfoValue
|
42
|
-
data[:players] = {} # Key: InfoType, Value: Array of Values
|
43
|
-
player_info = false
|
44
|
-
player_data = ""
|
45
|
-
|
46
|
-
# Parse the packets
|
47
|
-
@packets.each do |packet|
|
48
|
-
packet = clean_packet(packet)
|
49
|
-
|
50
|
-
if player_info
|
51
|
-
# Player header was found before, add packet to player_data
|
52
|
-
player_data += packet
|
53
|
-
else
|
54
|
-
if packet =~ RX_PLAYER_HEADER
|
55
|
-
# Found Player header, packet possibly contains partial gamedata too
|
56
|
-
player_info = true
|
57
|
-
packets = packet.split(RX_PLAYER_HEADER, 2)
|
58
|
-
|
59
|
-
# Parse last game_data piece if available
|
60
|
-
data[:game].merge!(parse_game_data(packets[0])) unless packets[0].empty?
|
61
|
-
|
62
|
-
# Collect the player_data if available
|
63
|
-
player_data += packets[1]
|
64
|
-
else
|
65
|
-
# GameData-only
|
66
|
-
data[:game].merge!(parse_game_data(packet))
|
67
|
-
end
|
68
|
-
end
|
69
|
-
end
|
70
|
-
|
71
|
-
# Parse player_data
|
72
|
-
unless player_data.empty?
|
73
|
-
data[:players] = parse_player_data(player_data)
|
74
|
-
end
|
75
|
-
|
76
|
-
data
|
77
|
-
end
|
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
|
-
def clean_packet(packet)
|
91
|
-
packet = packet.clone
|
92
|
-
packet.sub!(STR_ID, STR_EMPTY) # Cut off the identity
|
93
|
-
packet.sub!(RX_SPLITNUM, STR_EMPTY) # Cut off the splitnum
|
94
|
-
packet.sub!(RX_X0_E, STR_EMPTY) # Cut off last \x00
|
95
|
-
packet.sub!(RX_X0_S, STR_EMPTY) # Cut off first \x00
|
96
|
-
packet.sub!(RX_END, STR_EMPTY) # Cut off the last \x00\x02
|
97
|
-
|
98
|
-
# Encoding
|
99
|
-
get_string(packet)
|
100
|
-
end
|
101
|
-
|
102
|
-
def clean(str)
|
103
|
-
str.encode("UTF-8", invalid: :replace, undef: :replace)
|
104
|
-
end
|
105
|
-
|
106
|
-
def parse_game_data(packet)
|
107
|
-
Tools.debug {"Game Parsing #{packet.inspect}"}
|
108
|
-
|
109
|
-
key = nil
|
110
|
-
game_data = {}
|
111
|
-
|
112
|
-
packet.split(STR_SPLIT).each_with_index do |data, index|
|
113
|
-
if (index % 2) == 0
|
114
|
-
key = data
|
115
|
-
else
|
116
|
-
game_data[key] = data.is_a?(String) ? clean(data) : data
|
117
|
-
end
|
118
|
-
end
|
119
|
-
|
120
|
-
game_data
|
121
|
-
end
|
122
|
-
|
123
|
-
RX_PLAYER_EMPTY = /^player_\x00\x00\x00/
|
124
|
-
RX_PLAYER_INFO = /\x01(team|player|score|deaths)_.(.)/ # \x00 from previous packet, \x01 from continueing player info, (.) - should it overwrite previous value?
|
125
|
-
STR_DEATHS = "deaths_\x00\x00"
|
126
|
-
STR_PLAYER = "player_\x00\x00"
|
127
|
-
STR_TEAM = "team_\x00\x00"
|
128
|
-
STR_SCORE = "score_\x00\x00"
|
129
|
-
STR_SIX = "$SIX_OVERWRITE_PREVIOUS$"
|
130
|
-
STR_SIX_X0 = "\x00#{STR_SIX}\x00"
|
131
|
-
|
132
|
-
# TODO: Cleanup
|
133
|
-
def parse_player_data(packet)
|
134
|
-
Tools.debug {"Player Parsing #{packet.inspect}"}
|
135
|
-
|
136
|
-
player_data = {:names => [], :teams => [], :scores => [], :deaths => []} # [[], [], [], []]
|
137
|
-
|
138
|
-
return player_data if packet.nil? || packet.empty?
|
139
|
-
|
140
|
-
data = packet.clone
|
141
|
-
unless data =~ RX_PLAYER_EMPTY
|
142
|
-
|
143
|
-
# Leave out the character or Replace character with special string later used to replace the previous value
|
144
|
-
data.sub!(RX_PLAYER_INFO) { |r|
|
145
|
-
str = $1
|
146
|
-
if $2 == STR_X0
|
147
|
-
# If a proper primary info header of this type was not yet found, replace this secondary header with a proper primary header
|
148
|
-
# This will add the broken info header to the previous info list (name for team, team for score, score for deaths)
|
149
|
-
# However the resulting arrays are limited to num_players, so the info is discared anyway.
|
150
|
-
# TODO: Cleaner implementation!
|
151
|
-
data =~ /(^|[^\x01])#{str}_\x00\x00/ ? STR_X0 : :"#{str}_\x00\x00"
|
152
|
-
else
|
153
|
-
STR_SIX_X0
|
154
|
-
end
|
155
|
-
}
|
156
|
-
|
157
|
-
data, deaths = data.split(STR_DEATHS, 2)
|
158
|
-
data, scores = data.split(STR_SCORE, 2)
|
159
|
-
data, teams = data.split(STR_TEAM, 2)
|
160
|
-
data, names = data.split(STR_PLAYER, 2)
|
161
|
-
|
162
|
-
orig_data = [names, teams, scores, deaths]
|
163
|
-
|
164
|
-
# TODO: Handle seperate score
|
165
|
-
orig_data.each_with_index do |data, i|
|
166
|
-
next if data.nil? || data.empty?
|
167
|
-
str = data.clone
|
168
|
-
|
169
|
-
str.sub!(RX_X0_E, STR_EMPTY) # Remove last \x00
|
170
|
-
|
171
|
-
# Parse the data - \x00 is printed after a non-nil entry, otherwise \x00 means nil (e.g empty team)
|
172
|
-
until str.empty?
|
173
|
-
entry = str[RX_X0_SPEC]
|
174
|
-
player_data[player_data.keys[i]] << clean(entry.sub(STR_X0, STR_EMPTY))
|
175
|
-
str.sub!(entry, STR_EMPTY)
|
176
|
-
end
|
177
|
-
|
178
|
-
# Search for SIX string to overwrite last entry
|
179
|
-
new_player_data = []
|
180
|
-
overwrite = false
|
181
|
-
player_data[player_data.keys[i]].each do |info|
|
182
|
-
if info == STR_SIX
|
183
|
-
overwrite = true # tag so that the next entry will overwrite the latest entry
|
184
|
-
next # ignore
|
185
|
-
else
|
186
|
-
if overwrite
|
187
|
-
new_player_data[-1] = info # Overwrite latest entry
|
188
|
-
overwrite = false # done the overwrite
|
189
|
-
else
|
190
|
-
#break if new_player_data.size == num_players
|
191
|
-
new_player_data << info # insert entry
|
192
|
-
end
|
193
|
-
end
|
194
|
-
end
|
195
|
-
player_data[player_data.keys[i]] = new_player_data
|
196
|
-
end
|
197
|
-
end
|
198
|
-
|
199
|
-
player_data
|
200
|
-
end
|
201
|
-
|
202
|
-
# Hash of Hashes
|
203
|
-
def self.pretty_player_data(data)
|
204
|
-
player_data = {}
|
205
|
-
|
206
|
-
data[:names].each_with_index do |name, index|
|
207
|
-
player_data[name] = {}
|
208
|
-
player_data[name][:team] = data[:teams][index]
|
209
|
-
player_data[name][:score] = data[:scores][index]
|
210
|
-
player_data[name][:deaths] = data[:deaths][index]
|
211
|
-
end
|
212
|
-
|
213
|
-
player_data
|
214
|
-
end
|
215
|
-
|
216
|
-
# Array of Hashes
|
217
|
-
def self.pretty_player_data2(data)
|
218
|
-
player_data = []
|
219
|
-
|
220
|
-
data[:names].each_with_index do |name, index|
|
221
|
-
player = {}
|
222
|
-
|
223
|
-
player[:name] = name
|
224
|
-
player[:team] = data[:teams][index]
|
225
|
-
player[:score] = data[:scores][index]
|
226
|
-
player[:deaths] = data[:deaths][index]
|
227
|
-
|
228
|
-
player_data << player
|
229
|
-
end
|
230
|
-
|
231
|
-
player_data
|
232
|
-
end
|
233
|
-
end
|
234
|
-
end
|
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_SPLIT = STR_X0
|
15
|
+
STR_ID = "\x00\x04\x05\x06\a"
|
16
|
+
|
17
|
+
RX_SPLITNUM = /^splitnum\x00(.)/i
|
18
|
+
RX_PLAYER_HEADER = /\x01/
|
19
|
+
RX_END = /\x00\x02$/
|
20
|
+
|
21
|
+
# packets:
|
22
|
+
# - Hash, key: packetID, value: packetDATA
|
23
|
+
# or
|
24
|
+
# - Array, packetDATA ordered already by packetID
|
25
|
+
def initialize(packets)
|
26
|
+
@packets = case packets
|
27
|
+
when Hash
|
28
|
+
packets.keys.sort.map{|key| packets[key] }
|
29
|
+
when Array
|
30
|
+
packets
|
31
|
+
else
|
32
|
+
raise "Unsupported format"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Returns Hash with parsed data (:game and :players)
|
37
|
+
# :game => Hash, Key: InfoKey, Value: InfoValue
|
38
|
+
# :players => Hash, Key: InfoType, Value: Array of Values
|
39
|
+
def parse
|
40
|
+
data = {}
|
41
|
+
data[:game] = {} # Key: InfoKey, Value: InfoValue
|
42
|
+
data[:players] = {} # Key: InfoType, Value: Array of Values
|
43
|
+
player_info = false
|
44
|
+
player_data = ""
|
45
|
+
|
46
|
+
# Parse the packets
|
47
|
+
@packets.each do |packet|
|
48
|
+
packet = clean_packet(packet)
|
49
|
+
|
50
|
+
if player_info
|
51
|
+
# Player header was found before, add packet to player_data
|
52
|
+
player_data += packet
|
53
|
+
else
|
54
|
+
if packet =~ RX_PLAYER_HEADER
|
55
|
+
# Found Player header, packet possibly contains partial gamedata too
|
56
|
+
player_info = true
|
57
|
+
packets = packet.split(RX_PLAYER_HEADER, 2)
|
58
|
+
|
59
|
+
# Parse last game_data piece if available
|
60
|
+
data[:game].merge!(parse_game_data(packets[0])) unless packets[0].empty?
|
61
|
+
|
62
|
+
# Collect the player_data if available
|
63
|
+
player_data += packets[1]
|
64
|
+
else
|
65
|
+
# GameData-only
|
66
|
+
data[:game].merge!(parse_game_data(packet))
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# Parse player_data
|
72
|
+
unless player_data.empty?
|
73
|
+
data[:players] = parse_player_data(player_data)
|
74
|
+
end
|
75
|
+
|
76
|
+
data
|
77
|
+
end
|
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
|
+
def clean_packet(packet)
|
91
|
+
packet = packet.clone
|
92
|
+
packet.sub!(STR_ID, STR_EMPTY) # Cut off the identity
|
93
|
+
packet.sub!(RX_SPLITNUM, STR_EMPTY) # Cut off the splitnum
|
94
|
+
packet.sub!(RX_X0_E, STR_EMPTY) # Cut off last \x00
|
95
|
+
packet.sub!(RX_X0_S, STR_EMPTY) # Cut off first \x00
|
96
|
+
packet.sub!(RX_END, STR_EMPTY) # Cut off the last \x00\x02
|
97
|
+
|
98
|
+
# Encoding
|
99
|
+
get_string(packet)
|
100
|
+
end
|
101
|
+
|
102
|
+
def clean(str)
|
103
|
+
str.encode("UTF-8", invalid: :replace, undef: :replace)
|
104
|
+
end
|
105
|
+
|
106
|
+
def parse_game_data(packet)
|
107
|
+
Tools.debug {"Game Parsing #{packet.inspect}"}
|
108
|
+
|
109
|
+
key = nil
|
110
|
+
game_data = {}
|
111
|
+
|
112
|
+
packet.split(STR_SPLIT).each_with_index do |data, index|
|
113
|
+
if (index % 2) == 0
|
114
|
+
key = clean data
|
115
|
+
else
|
116
|
+
game_data[key] = data.is_a?(String) ? clean(data) : data
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
game_data
|
121
|
+
end
|
122
|
+
|
123
|
+
RX_PLAYER_EMPTY = /^player_\x00\x00\x00/
|
124
|
+
RX_PLAYER_INFO = /\x01(team|player|score|deaths)_.(.)/ # \x00 from previous packet, \x01 from continueing player info, (.) - should it overwrite previous value?
|
125
|
+
STR_DEATHS = "deaths_\x00\x00"
|
126
|
+
STR_PLAYER = "player_\x00\x00"
|
127
|
+
STR_TEAM = "team_\x00\x00"
|
128
|
+
STR_SCORE = "score_\x00\x00"
|
129
|
+
STR_SIX = "$SIX_OVERWRITE_PREVIOUS$"
|
130
|
+
STR_SIX_X0 = "\x00#{STR_SIX}\x00"
|
131
|
+
|
132
|
+
# TODO: Cleanup
|
133
|
+
def parse_player_data(packet)
|
134
|
+
Tools.debug {"Player Parsing #{packet.inspect}"}
|
135
|
+
|
136
|
+
player_data = {:names => [], :teams => [], :scores => [], :deaths => []} # [[], [], [], []]
|
137
|
+
|
138
|
+
return player_data if packet.nil? || packet.empty?
|
139
|
+
|
140
|
+
data = packet.clone
|
141
|
+
unless data =~ RX_PLAYER_EMPTY
|
142
|
+
|
143
|
+
# Leave out the character or Replace character with special string later used to replace the previous value
|
144
|
+
data.sub!(RX_PLAYER_INFO) { |r|
|
145
|
+
str = $1
|
146
|
+
if $2 == STR_X0
|
147
|
+
# If a proper primary info header of this type was not yet found, replace this secondary header with a proper primary header
|
148
|
+
# This will add the broken info header to the previous info list (name for team, team for score, score for deaths)
|
149
|
+
# However the resulting arrays are limited to num_players, so the info is discared anyway.
|
150
|
+
# TODO: Cleaner implementation!
|
151
|
+
data =~ /(^|[^\x01])#{str}_\x00\x00/ ? STR_X0 : :"#{str}_\x00\x00"
|
152
|
+
else
|
153
|
+
STR_SIX_X0
|
154
|
+
end
|
155
|
+
}
|
156
|
+
|
157
|
+
data, deaths = data.split(STR_DEATHS, 2)
|
158
|
+
data, scores = data.split(STR_SCORE, 2)
|
159
|
+
data, teams = data.split(STR_TEAM, 2)
|
160
|
+
data, names = data.split(STR_PLAYER, 2)
|
161
|
+
|
162
|
+
orig_data = [names, teams, scores, deaths]
|
163
|
+
|
164
|
+
# TODO: Handle seperate score
|
165
|
+
orig_data.each_with_index do |data, i|
|
166
|
+
next if data.nil? || data.empty?
|
167
|
+
str = data.clone
|
168
|
+
|
169
|
+
str.sub!(RX_X0_E, STR_EMPTY) # Remove last \x00
|
170
|
+
|
171
|
+
# Parse the data - \x00 is printed after a non-nil entry, otherwise \x00 means nil (e.g empty team)
|
172
|
+
until str.empty?
|
173
|
+
entry = str[RX_X0_SPEC]
|
174
|
+
player_data[player_data.keys[i]] << clean(entry.sub(STR_X0, STR_EMPTY))
|
175
|
+
str.sub!(entry, STR_EMPTY)
|
176
|
+
end
|
177
|
+
|
178
|
+
# Search for SIX string to overwrite last entry
|
179
|
+
new_player_data = []
|
180
|
+
overwrite = false
|
181
|
+
player_data[player_data.keys[i]].each do |info|
|
182
|
+
if info == STR_SIX
|
183
|
+
overwrite = true # tag so that the next entry will overwrite the latest entry
|
184
|
+
next # ignore
|
185
|
+
else
|
186
|
+
if overwrite
|
187
|
+
new_player_data[-1] = info # Overwrite latest entry
|
188
|
+
overwrite = false # done the overwrite
|
189
|
+
else
|
190
|
+
#break if new_player_data.size == num_players
|
191
|
+
new_player_data << info # insert entry
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
player_data[player_data.keys[i]] = new_player_data
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
player_data
|
200
|
+
end
|
201
|
+
|
202
|
+
# Hash of Hashes
|
203
|
+
def self.pretty_player_data(data)
|
204
|
+
player_data = {}
|
205
|
+
|
206
|
+
data[:names].each_with_index do |name, index|
|
207
|
+
player_data[name] = {}
|
208
|
+
player_data[name][:team] = data[:teams][index]
|
209
|
+
player_data[name][:score] = data[:scores][index]
|
210
|
+
player_data[name][:deaths] = data[:deaths][index]
|
211
|
+
end
|
212
|
+
|
213
|
+
player_data
|
214
|
+
end
|
215
|
+
|
216
|
+
# Array of Hashes
|
217
|
+
def self.pretty_player_data2(data)
|
218
|
+
player_data = []
|
219
|
+
|
220
|
+
data[:names].each_with_index do |name, index|
|
221
|
+
player = {}
|
222
|
+
|
223
|
+
player[:name] = name
|
224
|
+
player[:team] = data[:teams][index]
|
225
|
+
player[:score] = data[:scores][index]
|
226
|
+
player[:deaths] = data[:deaths][index]
|
227
|
+
|
228
|
+
player_data << player
|
229
|
+
end
|
230
|
+
|
231
|
+
player_data
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|