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.
@@ -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, 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) 2012 Dave Smith
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
- # StringPool containing all type strings in the package
117
- attr_reader :stringpool_typestrings
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
- header = ChunkHeader.new( read_short(data, startoffset_package),
160
- read_short(data, startoffset_package+2),
161
- read_word(data, startoffset_package+4) )
162
-
163
- package_id = read_word(data, startoffset_package+8)
164
- package_name = read_string(data, startoffset_package+12, 256, "UTF-8")
165
- package_type_strings = read_word(data, startoffset_package+268)
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
- ## Get the start of the type from the offsets table
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 array of all the type values in the file
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
- return @stringpool_typestrings.values
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 array of all the key values in the file
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
- return @stringpool_keystrings.values
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
- if res_package != @package_header.id
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 = @type_data[res_type-1]
337
- entry = res_spec.types.entries[res_index]
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
- if res_package != @package_header.id
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 = @type_data[res_type-1]
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 #{type_name} ResType chunk" if DEBUG
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]
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2012 Dave Smith
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
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.5.2
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-11-25 00:00:00.000000000 Z
12
+ date: 2013-12-17 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rubyzip