apktools 0.5.2 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/read_manifest.rb +1 -1
- data/lib/apktools/apkresources.rb +195 -149
- data/lib/apktools/apkxml.rb +1 -1
- metadata +2 -2
data/bin/read_manifest.rb
CHANGED
@@ -34,6 +34,6 @@ outfile = ARGV[1]
|
|
34
34
|
|
35
35
|
# You can also optionally enable indented (pretty) output,
|
36
36
|
# and resolving of resource values
|
37
|
-
manifest_xml = xml.parse_xml("AndroidManifest.xml", true,
|
37
|
+
manifest_xml = xml.parse_xml("AndroidManifest.xml", true, false)
|
38
38
|
|
39
39
|
File.open(outfile, 'w') { |file| file.write(manifest_xml) }
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright (C)
|
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
|
@@ -54,6 +54,17 @@ class ApkResources
|
|
54
54
|
# * +key_strings+ = Array of the key string values present (e.g. "ic_launcher")
|
55
55
|
PackageHeader = Struct.new(:header, :id, :name, :type_strings, :key_strings)
|
56
56
|
|
57
|
+
##
|
58
|
+
# Structure defining the resource contents for a package chunk
|
59
|
+
#
|
60
|
+
# Package = Struct.new(:header, :stringpool_typestrings, :stringpool_keystrings, :type_data)
|
61
|
+
#
|
62
|
+
# * +package_header+ = PackageHeader
|
63
|
+
# * +stringpool_typestrings+ = StringPool containing all type strings in the package
|
64
|
+
# * +stringpool_keystrings+ = StringPool containing all key strings in the package
|
65
|
+
# * +type_data+ = Array of ResTypeSpec chunks in the package
|
66
|
+
Package = Struct.new(:package_header, :stringpool_typestrings, :stringpool_keystrings, :type_data)
|
67
|
+
|
57
68
|
##
|
58
69
|
# Structure defining the flags for a block of common resources
|
59
70
|
#
|
@@ -113,12 +124,8 @@ class ApkResources
|
|
113
124
|
attr_reader :package_header
|
114
125
|
# StringPool containing all value strings in the package
|
115
126
|
attr_reader :stringpool_main
|
116
|
-
#
|
117
|
-
attr_reader :
|
118
|
-
# StringPool containing all key strings in the package
|
119
|
-
attr_reader :stringpool_keystrings
|
120
|
-
# Array of the ResTypeSpec chunks in the package
|
121
|
-
attr_reader :type_data
|
127
|
+
# Hash of Package chunks, keyed by package id
|
128
|
+
attr_reader :packages
|
122
129
|
|
123
130
|
##
|
124
131
|
# Create a new ApkResources instance from the specified +apk_file+
|
@@ -141,10 +148,6 @@ class ApkResources
|
|
141
148
|
header_chunk_size = read_word(data, HEADER_START+4)
|
142
149
|
header_package_count = read_word(data, HEADER_START+8)
|
143
150
|
puts "Resource Package Count = #{header_package_count}" if DEBUG
|
144
|
-
if header_package_count > 1
|
145
|
-
puts "ApkResources only supports single package resources."
|
146
|
-
exit(1)
|
147
|
-
end
|
148
151
|
|
149
152
|
# Parse the StringPool Chunk
|
150
153
|
## Header
|
@@ -156,133 +159,15 @@ class ApkResources
|
|
156
159
|
# Parse the Package Chunk
|
157
160
|
## Header
|
158
161
|
startoffset_package = startoffset_pool + @stringpool_main.header.chunk_size
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
package_last_type = read_word(data, startoffset_package+272)
|
167
|
-
package_key_strings = read_word(data, startoffset_package+276)
|
168
|
-
package_last_key = read_word(data, startoffset_package+280)
|
169
|
-
|
170
|
-
@package_header = PackageHeader.new(header, package_id, package_name, package_type_strings, package_key_strings)
|
171
|
-
|
172
|
-
## typeStrings StringPool
|
173
|
-
startoffset_typestrings = startoffset_package + package_type_strings
|
174
|
-
puts "Parse typeStrings StringPool Chunk" if DEBUG
|
175
|
-
@stringpool_typestrings = parse_stringpool(data, startoffset_typestrings)
|
176
|
-
|
177
|
-
## keyStrings StringPool
|
178
|
-
startoffset_keystrings = startoffset_package + package_key_strings
|
179
|
-
puts "Parse keyStrings StringPool Chunk" if DEBUG
|
180
|
-
@stringpool_keystrings = parse_stringpool(data, startoffset_keystrings)
|
181
|
-
|
182
|
-
## typeSpec/type Chunks
|
183
|
-
@type_data = Array.new()
|
184
|
-
current_spec = nil
|
185
|
-
|
186
|
-
current = startoffset_keystrings + @stringpool_keystrings.header.chunk_size
|
187
|
-
puts "Parse Type/TypeSpec Chunks" if DEBUG
|
188
|
-
while current < data.length
|
189
|
-
## Parse Header
|
190
|
-
header = ChunkHeader.new( read_short(data, current),
|
191
|
-
read_short(data, current+2),
|
192
|
-
read_word(data, current+4) )
|
193
|
-
## Check Type
|
194
|
-
if header.type == CHUNKTYPE_TYPESPEC
|
195
|
-
typespec_id = read_byte(data, current+8)
|
196
|
-
typespec_entrycount = read_word(data, current+12)
|
197
|
-
|
198
|
-
## Parse the config flags for each entry
|
199
|
-
typespec_entries = Array.new()
|
200
|
-
i=0
|
201
|
-
while i < typespec_entrycount
|
202
|
-
offset = i * 4 + (current+16)
|
203
|
-
typespec_entries << read_word(data, offset)
|
204
|
-
|
205
|
-
i += 1
|
206
|
-
end
|
207
|
-
|
208
|
-
typespec_name = @stringpool_typestrings.values[typespec_id - 1]
|
209
|
-
current_spec = ResTypeSpec.new(header, typespec_name, typespec_entrycount, typespec_entries, nil)
|
210
|
-
|
211
|
-
@type_data << current_spec
|
212
|
-
current += header.chunk_size
|
213
|
-
elsif header.type == CHUNKTYPE_TYPE
|
214
|
-
type_id = read_byte(data, current+8)
|
215
|
-
type_entrycount = read_word(data, current+12)
|
216
|
-
type_entryoffset = read_word(data, current+16)
|
217
|
-
|
218
|
-
## The config flags set for this type chunk
|
219
|
-
## TODO: Vary the size of the config structure based on size to accomodate for new flags
|
220
|
-
config_size = read_word(data, current+20) # Number of bytes in structure
|
221
|
-
type_config = ResTypeConfig.new( read_word(data, current+24),
|
222
|
-
read_word(data, current+28),
|
223
|
-
read_word(data, current+32),
|
224
|
-
read_word(data, current+36 ),
|
225
|
-
read_word(data, current+40),
|
226
|
-
read_word(data, current+44),
|
227
|
-
read_word(data, current+48),
|
228
|
-
read_word(data, current+52) )
|
229
|
-
|
230
|
-
## The remainder of the chunk is a list of the entry values for that type/configuration
|
231
|
-
type_name = @stringpool_typestrings.values[type_id - 1]
|
232
|
-
if current_spec.types == nil
|
233
|
-
current_spec.types = ResType.new(header, type_name, type_config, type_entrycount, Array.new())
|
234
|
-
end
|
235
|
-
|
236
|
-
i=0
|
237
|
-
while i < type_entrycount
|
238
|
-
## Ensure a hash exists for each type
|
239
|
-
if current_spec.types.entries[i] == nil
|
240
|
-
current_spec.types.entries[i] = Hash.new()
|
241
|
-
end
|
242
|
-
current_entry = current_spec.types.entries[i]
|
162
|
+
@packages = Hash.new()
|
163
|
+
i = 0
|
164
|
+
while i < header_package_count
|
165
|
+
package_element = parse_package(data, startoffset_package)
|
166
|
+
puts "Package #{package_element.package_header.id}" if DEBUG
|
167
|
+
startoffset_package = startoffset_package + package_element.package_header.header.chunk_size
|
168
|
+
@packages[package_element.package_header.id] = package_element
|
243
169
|
|
244
|
-
|
245
|
-
index_offset = i * 4 + (current+56)
|
246
|
-
start_offset = read_word(data, index_offset)
|
247
|
-
if start_offset != OFFSET_NO_ENTRY
|
248
|
-
## Set the index_offset to the start of the current entry
|
249
|
-
index_offset = current + type_entryoffset + start_offset
|
250
|
-
|
251
|
-
entry_flags = read_short(data, index_offset+2)
|
252
|
-
entry_key = read_word(data, index_offset+4)
|
253
|
-
entry_data_type = read_byte(data, index_offset+11)
|
254
|
-
entry_data = read_word(data, index_offset+12)
|
255
|
-
|
256
|
-
# Find the key in our strings index
|
257
|
-
key_name = @stringpool_keystrings.values[entry_key]
|
258
|
-
# Parse the value into a string
|
259
|
-
data_value = nil
|
260
|
-
case type_name
|
261
|
-
when TYPE_STRING, TYPE_DRAWABLE
|
262
|
-
data_value = get_resource_string(entry_data_type, entry_data)
|
263
|
-
when TYPE_COLOR
|
264
|
-
data_value = get_resource_color(entry_data_type, entry_data)
|
265
|
-
when TYPE_DIMENSION
|
266
|
-
data_value = get_resource_dimension(entry_data_type, entry_data)
|
267
|
-
when TYPE_INTEGER
|
268
|
-
data_value = get_resource_integer(entry_data_type, entry_data)
|
269
|
-
when TYPE_BOOLEAN
|
270
|
-
data_value = get_resource_bool(entry_data_type, entry_data)
|
271
|
-
else
|
272
|
-
puts "Complex Resources not yet supported." if DEBUG
|
273
|
-
data_value = entry_data.to_s
|
274
|
-
end
|
275
|
-
current_entry[type_config] = ResTypeEntry.new(entry_flags, key_name, entry_data_type, data_value)
|
276
|
-
end
|
277
|
-
i += 1
|
278
|
-
end
|
279
|
-
|
280
|
-
current += header.chunk_size
|
281
|
-
else
|
282
|
-
puts "Unknown Chunk Found: #{header.type} #{header.size}" if DEBUG
|
283
|
-
## End Immediately
|
284
|
-
current = data.length
|
285
|
-
end
|
170
|
+
i += 1
|
286
171
|
end
|
287
172
|
|
288
173
|
end #initalize
|
@@ -295,17 +180,29 @@ class ApkResources
|
|
295
180
|
end
|
296
181
|
|
297
182
|
##
|
298
|
-
# Return
|
183
|
+
# Return hash of all the type values in the file
|
184
|
+
# keyed by package id
|
299
185
|
|
300
186
|
def get_all_types
|
301
|
-
|
187
|
+
types = Hash.new()
|
188
|
+
@packages.each do |key, value|
|
189
|
+
types[key] = value.stringpool_typestrings.values
|
190
|
+
end
|
191
|
+
|
192
|
+
return types
|
302
193
|
end
|
303
194
|
|
304
195
|
##
|
305
|
-
# Return
|
196
|
+
# Return hash of all the key values in the file
|
197
|
+
# keyed by package id
|
306
198
|
|
307
199
|
def get_all_keys
|
308
|
-
|
200
|
+
keys = Hash.new()
|
201
|
+
@packages.each do |key, value|
|
202
|
+
keys[key] = value.stringpool_keystrings.values
|
203
|
+
end
|
204
|
+
|
205
|
+
return keys
|
309
206
|
end
|
310
207
|
|
311
208
|
##
|
@@ -328,14 +225,19 @@ class ApkResources
|
|
328
225
|
res_type = (res_id >> 16) & 0xFF
|
329
226
|
res_index = res_id & 0xFFFF
|
330
227
|
|
331
|
-
|
228
|
+
package_element = @packages[res_package]
|
229
|
+
if package_element == nil
|
332
230
|
# This is not a resource we can parse
|
333
231
|
return nil
|
334
232
|
end
|
335
233
|
|
336
|
-
res_spec =
|
337
|
-
|
338
|
-
|
234
|
+
res_spec = package_element.type_data[res_type-1]
|
235
|
+
if res_spec == nil
|
236
|
+
puts "Could not find ResTypeSpec for #{res_package} #{res_type}" if DEBUG
|
237
|
+
return nil
|
238
|
+
end
|
239
|
+
|
240
|
+
entry = res_spec.types.entries[res_index]
|
339
241
|
if entry == nil
|
340
242
|
# There is no entry in our table for this resource
|
341
243
|
return nil
|
@@ -389,16 +291,21 @@ class ApkResources
|
|
389
291
|
res_type = (res_id >> 16) & 0xFF
|
390
292
|
res_index = res_id & 0xFFFF
|
391
293
|
|
392
|
-
|
294
|
+
package_element = @packages[res_package]
|
295
|
+
if package_element == nil
|
393
296
|
# This is not a resource we can parse
|
394
297
|
return nil
|
395
298
|
end
|
396
299
|
|
397
|
-
res_spec =
|
398
|
-
|
300
|
+
res_spec = package_element.type_data[res_type-1]
|
301
|
+
if res_spec == nil
|
302
|
+
puts "Could not find ResTypeSpec for #{res_package} #{res_type}" if DEBUG
|
303
|
+
return nil
|
304
|
+
end
|
305
|
+
|
399
306
|
entries = res_spec.types.entries[res_index]
|
400
307
|
if entries == nil
|
401
|
-
puts "Could not find #{
|
308
|
+
puts "Could not find #{res_spec.types.id} ResType chunk" if DEBUG
|
402
309
|
return nil
|
403
310
|
end
|
404
311
|
|
@@ -443,6 +350,7 @@ class ApkResources
|
|
443
350
|
# Header Constants
|
444
351
|
CHUNKTYPE_TYPESPEC = 0x202 # :nodoc:
|
445
352
|
CHUNKTYPE_TYPE = 0x201 # :nodoc:
|
353
|
+
CHUNKTYPE_PACKAGE = 0x200 # :nodoc:
|
446
354
|
|
447
355
|
#Flag Constants
|
448
356
|
FLAG_UTF8 = 0x100 # :nodoc:
|
@@ -531,6 +439,144 @@ class ApkResources
|
|
531
439
|
return StringPool.new(pool_header, pool_string_count, pool_style_count, values)
|
532
440
|
end
|
533
441
|
|
442
|
+
# Parse out a Package Chunk
|
443
|
+
def parse_package(data, offset)
|
444
|
+
header = ChunkHeader.new( read_short(data, offset),
|
445
|
+
read_short(data, offset+2),
|
446
|
+
read_word(data, offset+4) )
|
447
|
+
|
448
|
+
package_id = read_word(data, offset+8)
|
449
|
+
package_name = read_string(data, offset+12, 256, "UTF-8")
|
450
|
+
package_type_strings = read_word(data, offset+268)
|
451
|
+
package_last_type = read_word(data, offset+272)
|
452
|
+
package_key_strings = read_word(data, offset+276)
|
453
|
+
package_last_key = read_word(data, offset+280)
|
454
|
+
|
455
|
+
package_header = PackageHeader.new(header, package_id, package_name, package_type_strings, package_key_strings)
|
456
|
+
|
457
|
+
## typeStrings StringPool
|
458
|
+
startoffset_typestrings = offset + package_type_strings
|
459
|
+
puts "Parse typeStrings StringPool Chunk" if DEBUG
|
460
|
+
stringpool_typestrings = parse_stringpool(data, startoffset_typestrings)
|
461
|
+
|
462
|
+
## keyStrings StringPool
|
463
|
+
startoffset_keystrings = offset + package_key_strings
|
464
|
+
puts "Parse keyStrings StringPool Chunk" if DEBUG
|
465
|
+
stringpool_keystrings = parse_stringpool(data, startoffset_keystrings)
|
466
|
+
|
467
|
+
## typeSpec/type Chunks
|
468
|
+
type_data = Array.new()
|
469
|
+
current_spec = nil
|
470
|
+
|
471
|
+
current = startoffset_keystrings + stringpool_keystrings.header.chunk_size
|
472
|
+
puts "Parse Type/TypeSpec Chunks" if DEBUG
|
473
|
+
while current < data.length
|
474
|
+
## Parse Header
|
475
|
+
header = ChunkHeader.new( read_short(data, current),
|
476
|
+
read_short(data, current+2),
|
477
|
+
read_word(data, current+4) )
|
478
|
+
## Check Type
|
479
|
+
if header.type == CHUNKTYPE_TYPESPEC
|
480
|
+
typespec_id = read_byte(data, current+8)
|
481
|
+
typespec_entrycount = read_word(data, current+12)
|
482
|
+
|
483
|
+
## Parse the config flags for each entry
|
484
|
+
typespec_entries = Array.new()
|
485
|
+
i=0
|
486
|
+
while i < typespec_entrycount
|
487
|
+
offset = i * 4 + (current+16)
|
488
|
+
typespec_entries << read_word(data, offset)
|
489
|
+
|
490
|
+
i += 1
|
491
|
+
end
|
492
|
+
|
493
|
+
typespec_name = stringpool_typestrings.values[typespec_id - 1]
|
494
|
+
current_spec = ResTypeSpec.new(header, typespec_name, typespec_entrycount, typespec_entries, nil)
|
495
|
+
|
496
|
+
type_data << current_spec
|
497
|
+
current += header.chunk_size
|
498
|
+
elsif header.type == CHUNKTYPE_TYPE
|
499
|
+
type_id = read_byte(data, current+8)
|
500
|
+
type_entrycount = read_word(data, current+12)
|
501
|
+
type_entryoffset = read_word(data, current+16)
|
502
|
+
|
503
|
+
## The config flags set for this type chunk
|
504
|
+
## TODO: Vary the size of the config structure based on size to accomodate for new flags
|
505
|
+
config_size = read_word(data, current+20) # Number of bytes in structure
|
506
|
+
type_config = ResTypeConfig.new( read_word(data, current+24),
|
507
|
+
read_word(data, current+28),
|
508
|
+
read_word(data, current+32),
|
509
|
+
read_word(data, current+36 ),
|
510
|
+
read_word(data, current+40),
|
511
|
+
read_word(data, current+44),
|
512
|
+
read_word(data, current+48),
|
513
|
+
read_word(data, current+52) )
|
514
|
+
|
515
|
+
## The remainder of the chunk is a list of the entry values for that type/configuration
|
516
|
+
type_name = stringpool_typestrings.values[type_id - 1]
|
517
|
+
if current_spec.types == nil
|
518
|
+
current_spec.types = ResType.new(header, type_name, type_config, type_entrycount, Array.new())
|
519
|
+
end
|
520
|
+
|
521
|
+
i=0
|
522
|
+
while i < type_entrycount
|
523
|
+
## Ensure a hash exists for each type
|
524
|
+
if current_spec.types.entries[i] == nil
|
525
|
+
current_spec.types.entries[i] = Hash.new()
|
526
|
+
end
|
527
|
+
current_entry = current_spec.types.entries[i]
|
528
|
+
|
529
|
+
## Get the start of the type from the offsets table
|
530
|
+
index_offset = i * 4 + (current+56)
|
531
|
+
start_offset = read_word(data, index_offset)
|
532
|
+
if start_offset != OFFSET_NO_ENTRY
|
533
|
+
## Set the index_offset to the start of the current entry
|
534
|
+
index_offset = current + type_entryoffset + start_offset
|
535
|
+
|
536
|
+
entry_flags = read_short(data, index_offset+2)
|
537
|
+
entry_key = read_word(data, index_offset+4)
|
538
|
+
entry_data_type = read_byte(data, index_offset+11)
|
539
|
+
entry_data = read_word(data, index_offset+12)
|
540
|
+
|
541
|
+
# Find the key in our strings index
|
542
|
+
key_name = stringpool_keystrings.values[entry_key]
|
543
|
+
# Parse the value into a string
|
544
|
+
data_value = nil
|
545
|
+
case type_name
|
546
|
+
when TYPE_STRING, TYPE_DRAWABLE
|
547
|
+
data_value = get_resource_string(entry_data_type, entry_data)
|
548
|
+
when TYPE_COLOR
|
549
|
+
data_value = get_resource_color(entry_data_type, entry_data)
|
550
|
+
when TYPE_DIMENSION
|
551
|
+
data_value = get_resource_dimension(entry_data_type, entry_data)
|
552
|
+
when TYPE_INTEGER
|
553
|
+
data_value = get_resource_integer(entry_data_type, entry_data)
|
554
|
+
when TYPE_BOOLEAN
|
555
|
+
data_value = get_resource_bool(entry_data_type, entry_data)
|
556
|
+
else
|
557
|
+
puts "Complex Resources not yet supported." if DEBUG
|
558
|
+
data_value = entry_data.to_s
|
559
|
+
end
|
560
|
+
current_entry[type_config] = ResTypeEntry.new(entry_flags, key_name, entry_data_type, data_value)
|
561
|
+
end
|
562
|
+
i += 1
|
563
|
+
end
|
564
|
+
|
565
|
+
current += header.chunk_size
|
566
|
+
elsif header.type == CHUNKTYPE_PACKAGE
|
567
|
+
## This is the next package chunk, move along
|
568
|
+
puts "Next Package Chunk Found...Ending" if DEBUG
|
569
|
+
current = data.length
|
570
|
+
else
|
571
|
+
puts "Unknown Chunk Found: #{header.type} #{header.size}" if DEBUG
|
572
|
+
## End Immediately
|
573
|
+
current = data.length
|
574
|
+
end
|
575
|
+
end
|
576
|
+
|
577
|
+
return Package.new(package_header, stringpool_typestrings, stringpool_keystrings, type_data)
|
578
|
+
end
|
579
|
+
|
534
580
|
# Obtain string value for resource id
|
535
581
|
def get_resource_string(entry_datatype, entry_data)
|
536
582
|
result = @stringpool_main.values[entry_data]
|
data/lib/apktools/apkxml.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: apktools
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-
|
12
|
+
date: 2013-12-17 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rubyzip
|