apktools 0.5.2 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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