apktools 0.5.2 → 0.6.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/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
|