apktools 0.6.0 → 0.7.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/lib/apktools/apkresources.rb +683 -623
- data/lib/apktools/apkxml.rb +422 -422
- metadata +10 -12
data/lib/apktools/apkxml.rb
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
# Copyright (C) 2014 Dave Smith
|
|
2
|
-
#
|
|
2
|
+
#
|
|
3
3
|
# Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
|
4
4
|
# software and associated documentation files (the "Software"), to deal in the Software
|
|
5
5
|
# without restriction, including without limitation the rights to use, copy, modify,
|
|
6
6
|
# merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
|
|
7
7
|
# persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
8
|
-
#
|
|
8
|
+
#
|
|
9
9
|
# The above copyright notice and this permission notice shall be included in all copies
|
|
10
10
|
# or substantial portions of the Software.
|
|
11
|
-
#
|
|
11
|
+
#
|
|
12
12
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
|
13
13
|
# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
|
14
14
|
# PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
|
@@ -16,427 +16,427 @@
|
|
|
16
16
|
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
17
17
|
# DEALINGS IN THE SOFTWARE.
|
|
18
18
|
|
|
19
|
-
require 'zip
|
|
19
|
+
require 'zip'
|
|
20
20
|
require 'apktools/apkresources'
|
|
21
21
|
|
|
22
22
|
##
|
|
23
23
|
# Class to parse an APK's binary XML format back into textual XML
|
|
24
24
|
class ApkXml
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
end
|
|
25
|
+
|
|
26
|
+
DEBUG = false # :nodoc:
|
|
27
|
+
|
|
28
|
+
##
|
|
29
|
+
# Structure defining the type and size of each resource chunk
|
|
30
|
+
#
|
|
31
|
+
# ChunkHeader = Struct.new(:type, :size, :chunk_size)
|
|
32
|
+
ChunkHeader = Struct.new(:type, :size, :chunk_size)
|
|
33
|
+
|
|
34
|
+
##
|
|
35
|
+
# Structure that houses a group of strings
|
|
36
|
+
#
|
|
37
|
+
# StringPool = Struct.new(:header, :string_count, :style_count, :values)
|
|
38
|
+
#
|
|
39
|
+
# * +header+ = ChunkHeader
|
|
40
|
+
# * +string_count+ = Number of normal strings in the pool
|
|
41
|
+
# * +style_count+ = Number of styled strings in the pool
|
|
42
|
+
# * +values+ = Array of the string values
|
|
43
|
+
StringPool = Struct.new(:header, :string_count, :style_count, :values)
|
|
44
|
+
|
|
45
|
+
##
|
|
46
|
+
# Structure to house mappings of resource ids to strings
|
|
47
|
+
#
|
|
48
|
+
# XmlResourceMap = Struct.new(:header, :ids, :strings)
|
|
49
|
+
#
|
|
50
|
+
# * +header+ = ChunkHeader
|
|
51
|
+
# * +ids+ = Array of resource ids
|
|
52
|
+
# * +strings+ = Matching Array of resource strings
|
|
53
|
+
XmlResourceMap = Struct.new(:header, :ids, :strings)
|
|
54
|
+
|
|
55
|
+
##
|
|
56
|
+
# Structure defining header of an XML node
|
|
57
|
+
#
|
|
58
|
+
# XmlTreeHeader = Struct.new(:header, :line_num, :comment)
|
|
59
|
+
#
|
|
60
|
+
# * +header+ = ChunkHeader
|
|
61
|
+
# * +line_num+ = Line number in original file
|
|
62
|
+
# * +comment+ = Optional comment
|
|
63
|
+
XmlTreeHeader = Struct.new(:header, :line_num, :comment)
|
|
64
|
+
|
|
65
|
+
##
|
|
66
|
+
# Structure defining an XML element
|
|
67
|
+
#
|
|
68
|
+
# XmlElement = Struct.new(:header, :namespace, :name, :id_idx, :class_idx, :style_idx, :attributes, :is_root)
|
|
69
|
+
#
|
|
70
|
+
# * +header+ = XmlTreeHeader
|
|
71
|
+
# * +namespace+ = Namespace prefix of the element
|
|
72
|
+
# * +name+ = Name of the element
|
|
73
|
+
# * +id_idx+ = Index of the attribute that represents the "id" in this element, if any
|
|
74
|
+
# * +class_idx+ = Index of the attribute that represents the "class" in this element, if any
|
|
75
|
+
# * +style_idx+ = Index of the attribute that represents the "style" in this element, if any
|
|
76
|
+
# * +attributes+ = Array of XmlAttribute elements
|
|
77
|
+
# * +is_root+ = Marks if this is the root element
|
|
78
|
+
XmlElement = Struct.new(:header, :namespace, :name, :id_idx, :class_idx, :style_idx, :attributes, :is_root)
|
|
79
|
+
|
|
80
|
+
##
|
|
81
|
+
# Structure defining an XML element's attribute
|
|
82
|
+
#
|
|
83
|
+
# XmlAttribute = Struct.new(:namespace, :name, :raw, :value)
|
|
84
|
+
#
|
|
85
|
+
# * +namespace+ = Namespace prefix of the attribute
|
|
86
|
+
# * +name+ = Name of the attribute
|
|
87
|
+
# * +value+ = Value of the attribute
|
|
88
|
+
XmlAttribute = Struct.new(:namespace, :name, :value)
|
|
89
|
+
|
|
90
|
+
##
|
|
91
|
+
# Structure that houses the data for a given resource entry
|
|
92
|
+
#
|
|
93
|
+
# ResTypeEntry = Struct.new(:flags, :key, :data_type, :data)
|
|
94
|
+
#
|
|
95
|
+
# * +flags+ = Flags marking if the resource is complex or public
|
|
96
|
+
# * +key+ = Key string for the resource (e.g. "ic_launcher" of R.drawable.ic_launcher")
|
|
97
|
+
# * +data_type+ = Type identifier. The meaning of this value varies with the type of resource
|
|
98
|
+
# * +data+ = Resource value (e.g. "res/drawable/ic_launcher" for R.drawable.ic_launcher")
|
|
99
|
+
#
|
|
100
|
+
# A single resource key can have multiple entries depending on configuration, so these structs
|
|
101
|
+
# are often returned in groups, keyed by a ResTypeConfig
|
|
102
|
+
ResTypeEntry = Struct.new(:flags, :key, :data_type, :data)
|
|
103
|
+
|
|
104
|
+
# APK file where parser will search for XML
|
|
105
|
+
attr_reader :current_apk
|
|
106
|
+
# ApkResources instance used to resolve resources in this APK
|
|
107
|
+
attr_reader :apk_resources
|
|
108
|
+
# Array of XmlElements from the last parse operation
|
|
109
|
+
attr_reader :xml_elements
|
|
110
|
+
|
|
111
|
+
##
|
|
112
|
+
# Create a new ApkXml instance from the specified +apk_file+
|
|
113
|
+
#
|
|
114
|
+
# This opens and parses the contents of the APK's resources.arsc file.
|
|
115
|
+
def initialize(apk_file)
|
|
116
|
+
@current_apk = apk_file
|
|
117
|
+
@apk_resources = ApkResources.new(apk_file)
|
|
118
|
+
end #initialize
|
|
119
|
+
|
|
120
|
+
##
|
|
121
|
+
# Read the requested XML file from inside the APK and parse out into
|
|
122
|
+
# readable textual XML. Returns a string of the parsed XML.
|
|
123
|
+
#
|
|
124
|
+
# xml_file: ID value of a resource as a FixNum or String representation (i.e. 0x7F060001)
|
|
125
|
+
# pretty: Optionally format the XML output as human readable
|
|
126
|
+
# resolve_resources: Optionally, where possible, resolve resource references to their default value
|
|
127
|
+
#
|
|
128
|
+
# This opens and parses the contents of the APK's resources.arsc file.
|
|
129
|
+
def parse_xml(xml_file, pretty = false, resolve_resources = false)
|
|
130
|
+
# Reset variables
|
|
131
|
+
@xml_elements = Array.new()
|
|
132
|
+
xml_output = ""
|
|
133
|
+
indent = 0
|
|
134
|
+
data = nil
|
|
135
|
+
|
|
136
|
+
# Get the XML from the APK file
|
|
137
|
+
Zip::File.foreach(@current_apk) do |f|
|
|
138
|
+
if f.name.match(xml_file)
|
|
139
|
+
data = f.get_input_stream.read
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Parse the Header Chunk
|
|
144
|
+
header = ChunkHeader.new( read_short(data, HEADER_START),
|
|
145
|
+
read_short(data, HEADER_START+2),
|
|
146
|
+
read_word(data, HEADER_START+4) )
|
|
147
|
+
|
|
148
|
+
# Parse the StringPool Chunk
|
|
149
|
+
startoffset_pool = HEADER_START + header.size
|
|
150
|
+
puts "Parse Main StringPool Chunk" if DEBUG
|
|
151
|
+
stringpool_main = parse_stringpool(data, startoffset_pool)
|
|
152
|
+
puts "#{stringpool_main.values.length} strings found" if DEBUG
|
|
153
|
+
|
|
154
|
+
# Parse the remainder of the file chunks based on type
|
|
155
|
+
namespaces = Hash.new()
|
|
156
|
+
current = startoffset_pool + stringpool_main.header.chunk_size
|
|
157
|
+
puts "Parse Remaining Chunks" if DEBUG
|
|
158
|
+
while current < data.length
|
|
159
|
+
## Parse Header
|
|
160
|
+
header = ChunkHeader.new( read_short(data, current),
|
|
161
|
+
read_short(data, current+2),
|
|
162
|
+
read_word(data, current+4) )
|
|
163
|
+
## Check Type
|
|
164
|
+
if header.type == TYPE_XML_RESOURCEMAP
|
|
165
|
+
## Maps resource ids to strings in the pool
|
|
166
|
+
map_ids = Array.new()
|
|
167
|
+
map_strings = Array.new()
|
|
168
|
+
|
|
169
|
+
index_offset = current + header.size
|
|
170
|
+
i = 0
|
|
171
|
+
while index_offset < (current + header.chunk_size)
|
|
172
|
+
map_ids << read_word(data, index_offset)
|
|
173
|
+
map_strings << stringpool_main.values[i]
|
|
174
|
+
|
|
175
|
+
i += 1
|
|
176
|
+
index_offset = i * 4 + (current + header.size)
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
current += header.chunk_size
|
|
180
|
+
elsif header.type == TYPE_XML_STARTNAMESPACE
|
|
181
|
+
tree_header = parse_tree_header(header, data, current)
|
|
182
|
+
body_start = current+header.size
|
|
183
|
+
prefix = stringpool_main.values[read_word(data, body_start)]
|
|
184
|
+
uri = stringpool_main.values[read_word(data, body_start+4)]
|
|
185
|
+
namespaces[uri] = prefix
|
|
186
|
+
puts "NAMESPACE_START: xmlns:#{prefix} = '#{uri}'" if DEBUG
|
|
187
|
+
current += header.chunk_size
|
|
188
|
+
elsif header.type == TYPE_XML_ENDNAMESPACE
|
|
189
|
+
tree_header = parse_tree_header(header, data, current)
|
|
190
|
+
body_start = current+header.size
|
|
191
|
+
prefix = stringpool_main.values[read_word(data, body_start)]
|
|
192
|
+
uri = stringpool_main.values[read_word(data, body_start+4)]
|
|
193
|
+
puts "NAMESPACE_END: xmlns:#{prefix} = '#{uri}'" if DEBUG
|
|
194
|
+
current += header.chunk_size
|
|
195
|
+
elsif header.type == TYPE_XML_STARTELEMENT
|
|
196
|
+
tree_header = parse_tree_header(header, data, current)
|
|
197
|
+
body_start = current+header.size
|
|
198
|
+
# Parse the element/attribute data
|
|
199
|
+
namespace = nil
|
|
200
|
+
if read_word(data, body_start) != OFFSET_NO_ENTRY
|
|
201
|
+
namespace = stringpool_main.values[read_word(data, body_start)]
|
|
202
|
+
end
|
|
203
|
+
name = stringpool_main.values[read_word(data, body_start+4)]
|
|
204
|
+
|
|
205
|
+
attribute_offset = read_short(data, body_start+8)
|
|
206
|
+
attribute_size = read_short(data, body_start+10)
|
|
207
|
+
attribute_count = read_short(data, body_start+12)
|
|
208
|
+
id_idx = read_short(data, body_start+14)
|
|
209
|
+
class_idx = read_short(data, body_start+16)
|
|
210
|
+
style_idx = read_short(data, body_start+18)
|
|
211
|
+
|
|
212
|
+
attributes = Array.new()
|
|
213
|
+
i=0
|
|
214
|
+
while i < attribute_count
|
|
215
|
+
index_offset = i * attribute_size + (body_start + attribute_offset)
|
|
216
|
+
attr_namespace = nil
|
|
217
|
+
if read_word(data, index_offset) != OFFSET_NO_ENTRY
|
|
218
|
+
attr_uri = stringpool_main.values[read_word(data, index_offset)]
|
|
219
|
+
attr_namespace = namespaces[attr_uri]
|
|
220
|
+
end
|
|
221
|
+
attr_name = stringpool_main.values[read_word(data, index_offset+4)]
|
|
222
|
+
attr_raw = nil
|
|
223
|
+
if read_word(data, index_offset+8) != OFFSET_NO_ENTRY
|
|
224
|
+
# Attribute has a raw value, use it
|
|
225
|
+
attr_raw = stringpool_main.values[read_word(data, index_offset+8)]
|
|
226
|
+
end
|
|
227
|
+
entry = ResTypeEntry.new(0, nil, read_byte(data, index_offset+15), read_word(data, index_offset+16))
|
|
228
|
+
|
|
229
|
+
attr_value = nil
|
|
230
|
+
if attr_raw != nil # Use raw value
|
|
231
|
+
attr_value = attr_raw
|
|
232
|
+
elsif entry.data_type == 1 # Value is a references to a resource
|
|
233
|
+
# Find the resource
|
|
234
|
+
default_res = apk_resources.get_default_resource_value(entry.data)
|
|
235
|
+
if resolve_resources && default_res != nil
|
|
236
|
+
# Use the default resource value
|
|
237
|
+
attr_value = default_res.data
|
|
238
|
+
else
|
|
239
|
+
key_value = apk_resources.get_resource_key(entry.data, true)
|
|
240
|
+
if key_value != nil
|
|
241
|
+
# Use the key string
|
|
242
|
+
attr_value = key_value
|
|
243
|
+
else
|
|
244
|
+
#No key found, use raw id marked as a resource
|
|
245
|
+
attr_value = "res:0x#{entry.data.to_s(16)}"
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
else # Value is a constant
|
|
249
|
+
attr_value = "0x#{entry.data.to_s(16)}"
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
attributes << XmlAttribute.new(attr_namespace, attr_name, attr_value)
|
|
254
|
+
i += 1
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
element = XmlElement.new(tree_header, namespace, name, id_idx, class_idx, style_idx, attributes, xml_output == "")
|
|
258
|
+
|
|
259
|
+
# Print the element/attribute data
|
|
260
|
+
puts "ELEMENT_START: #{element.namespace} #{element.name}" if DEBUG
|
|
261
|
+
display_name = element.namespace == nil ? element.name : "#{element.namespace}:#{element.name}"
|
|
262
|
+
|
|
263
|
+
if pretty
|
|
264
|
+
xml_output += "\n" + (" " * indent)
|
|
265
|
+
indent += 1
|
|
266
|
+
end
|
|
267
|
+
xml_output += "<#{display_name} "
|
|
268
|
+
# Only print namespaces on the root element
|
|
269
|
+
if element.is_root
|
|
270
|
+
keys = namespaces.keys
|
|
271
|
+
keys.each do |key|
|
|
272
|
+
xml_output += "xmlns:#{namespaces[key]}=\"#{key}\" "
|
|
273
|
+
if pretty && key != keys.last
|
|
274
|
+
xml_output += "\n" + (" " * indent)
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
element.attributes.each do |attr|
|
|
280
|
+
puts "---ATTRIBUTE: #{attr.namespace} #{attr.name} #{attr.value}" if DEBUG
|
|
281
|
+
display_name = attr.namespace == nil ? attr.name : "#{attr.namespace}:#{attr.name}"
|
|
282
|
+
if pretty
|
|
283
|
+
xml_output += "\n" + (" " * indent)
|
|
284
|
+
end
|
|
285
|
+
xml_output += "#{display_name}=\"#{attr.value}\" "
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
xml_output += ">"
|
|
289
|
+
|
|
290
|
+
# Push every new element onto the array
|
|
291
|
+
@xml_elements << element
|
|
292
|
+
|
|
293
|
+
current += header.chunk_size
|
|
294
|
+
elsif header.type == TYPE_XML_ENDELEMENT
|
|
295
|
+
tree_header = parse_tree_header(header, data, current)
|
|
296
|
+
body_start = current+header.size
|
|
297
|
+
namespace = nil
|
|
298
|
+
if read_word(data, body_start) != OFFSET_NO_ENTRY
|
|
299
|
+
namespace = stringpool_main.values[read_word(data, body_start)]
|
|
300
|
+
end
|
|
301
|
+
name = stringpool_main.values[read_word(data, body_start+4)]
|
|
302
|
+
|
|
303
|
+
puts "ELEMENT END: #{namespace} #{name}" if DEBUG
|
|
304
|
+
display_name = namespace == nil ? name : "#{namespace}:#{name}"
|
|
305
|
+
if pretty
|
|
306
|
+
indent -= 1
|
|
307
|
+
if indent < 0
|
|
308
|
+
indent = 0
|
|
309
|
+
end
|
|
310
|
+
xml_output += "\n" + (" " * indent)
|
|
311
|
+
end
|
|
312
|
+
xml_output += "</#{display_name}>"
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
current += header.chunk_size
|
|
316
|
+
elsif header.type == TYPE_XML_CDATA
|
|
317
|
+
tree_header = parse_tree_header(header, data, current)
|
|
318
|
+
body_start = current+header.size
|
|
319
|
+
|
|
320
|
+
cdata = stringpool_main.values[read_word(data, body_start)]
|
|
321
|
+
cdata_type = read_word(data, body_start+7)
|
|
322
|
+
cdata_value = read_word(data, body_start+8)
|
|
323
|
+
puts "CDATA: #{cdata} #{cdata_type} #{cdata_value}" if DEBUG
|
|
324
|
+
|
|
325
|
+
cdata.split(/\r?\n/).each do |item|
|
|
326
|
+
if pretty
|
|
327
|
+
xml_output += "\n" + (" " * indent)
|
|
328
|
+
end
|
|
329
|
+
xml_output += "<![CDATA[#{item.strip}]]>"
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
current += header.chunk_size
|
|
333
|
+
else
|
|
334
|
+
puts "Unknown Chunk Found: #{header.type} #{header.size}" if DEBUG
|
|
335
|
+
## End Immediately
|
|
336
|
+
current = data.length
|
|
337
|
+
end
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
return xml_output
|
|
341
|
+
end #parse_xml
|
|
342
|
+
|
|
343
|
+
private # Private Helper Methods
|
|
344
|
+
|
|
345
|
+
#Flag Constants
|
|
346
|
+
FLAG_UTF8 = 0x100 # :nodoc:
|
|
347
|
+
|
|
348
|
+
OFFSET_NO_ENTRY = 0xFFFFFFFF # :nodoc:
|
|
349
|
+
HEADER_START = 0 # :nodoc:
|
|
350
|
+
|
|
351
|
+
TYPE_XML_RESOURCEMAP = 0x180 # :nodoc:
|
|
352
|
+
TYPE_XML_STARTNAMESPACE = 0x100 # :nodoc:
|
|
353
|
+
TYPE_XML_ENDNAMESPACE = 0x101 # :nodoc:
|
|
354
|
+
TYPE_XML_STARTELEMENT = 0x102 # :nodoc:
|
|
355
|
+
TYPE_XML_ENDELEMENT = 0x103 # :nodoc:
|
|
356
|
+
TYPE_XML_CDATA = 0x104 # :nodoc:
|
|
357
|
+
|
|
358
|
+
# Read a 32-bit word from a specific location in the data
|
|
359
|
+
def read_word(data, offset)
|
|
360
|
+
out = data[offset,4].unpack('V').first rescue 0
|
|
361
|
+
return out
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
# Read a 16-bit short from a specific location in the data
|
|
365
|
+
def read_short(data, offset)
|
|
366
|
+
out = data[offset,2].unpack('v').first rescue 0
|
|
367
|
+
return out
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
# Read a 8-bit byte from a specific location in the data
|
|
371
|
+
def read_byte(data, offset)
|
|
372
|
+
out = data[offset,1].unpack('C').first rescue 0
|
|
373
|
+
return out
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
# Read in length bytes in as a String
|
|
377
|
+
def read_string(data, offset, length, encoding)
|
|
378
|
+
if "UTF-16".casecmp(encoding) == 0
|
|
379
|
+
out = data[offset, length].unpack('v*').pack('U*')
|
|
380
|
+
else
|
|
381
|
+
out = data[offset, length].unpack('C*').pack('U*')
|
|
382
|
+
end
|
|
383
|
+
return out
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
# Parse out an XmlTreeHeader
|
|
387
|
+
def parse_tree_header(chunk_header, data, offset)
|
|
388
|
+
line_num = read_word(data, offset+8)
|
|
389
|
+
comment = nil
|
|
390
|
+
if read_word(data, offset+12) != OFFSET_NO_ENTRY
|
|
391
|
+
comment = stringpool_main.values[read_word(data, offset+12)]
|
|
392
|
+
end
|
|
393
|
+
return XmlTreeHeader.new(chunk_header, line_num, comment)
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
# Parse out a StringPool chunk
|
|
397
|
+
def parse_stringpool(data, offset)
|
|
398
|
+
pool_header = ChunkHeader.new( read_short(data, offset),
|
|
399
|
+
read_short(data, offset+2),
|
|
400
|
+
read_word(data, offset+4) )
|
|
401
|
+
|
|
402
|
+
pool_string_count = read_word(data, offset+8)
|
|
403
|
+
pool_style_count = read_word(data, offset+12)
|
|
404
|
+
pool_flags = read_word(data, offset+16)
|
|
405
|
+
format_utf8 = (pool_flags & FLAG_UTF8) != 0
|
|
406
|
+
puts 'StringPool format is %s' % [format_utf8 ? "UTF-8" : "UTF-16"] if DEBUG
|
|
407
|
+
|
|
408
|
+
pool_string_offset = read_word(data, offset+20)
|
|
409
|
+
pool_style_offset = read_word(data, offset+24)
|
|
410
|
+
|
|
411
|
+
values = Array.new()
|
|
412
|
+
i = 0
|
|
413
|
+
while i < pool_string_count
|
|
414
|
+
# Read the string value
|
|
415
|
+
index = i * 4 + (offset+28)
|
|
416
|
+
offset_addr = pool_string_offset + offset + read_word(data, index)
|
|
417
|
+
if format_utf8
|
|
418
|
+
length = read_byte(data, offset_addr)
|
|
419
|
+
if (length & 0x80) != 0
|
|
420
|
+
length = ((length & 0x7F) << 8) + read_byte(data, offset_addr+1)
|
|
421
|
+
end
|
|
422
|
+
|
|
423
|
+
values << read_string(data, offset_addr + 2, length, "UTF-8")
|
|
424
|
+
else
|
|
425
|
+
length = read_short(data, offset_addr)
|
|
426
|
+
if (length & 0x8000) != 0
|
|
427
|
+
#There is one more length value before the data
|
|
428
|
+
length = ((length & 0x7FFF) << 16) + read_short(data, offset_addr+2)
|
|
429
|
+
values << read_string(data, offset_addr + 4, length * 2, "UTF-16")
|
|
430
|
+
else
|
|
431
|
+
# Read the data
|
|
432
|
+
values << read_string(data, offset_addr + 2, length * 2, "UTF-16")
|
|
433
|
+
end
|
|
434
|
+
end
|
|
435
|
+
|
|
436
|
+
i += 1
|
|
437
|
+
end
|
|
438
|
+
|
|
439
|
+
return StringPool.new(pool_header, pool_string_count, pool_style_count, values)
|
|
440
|
+
end
|
|
441
|
+
|
|
442
|
+
end
|