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.
@@ -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