ruby_android 0.0.2 → 0.7.2
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.
- checksums.yaml +4 -4
- data/.idea/.name +1 -0
- data/.idea/.rakeTasks +7 -0
- data/.idea/encodings.xml +5 -0
- data/.idea/misc.xml +5 -0
- data/.idea/modules.xml +9 -0
- data/.idea/ruby_apk.iml +51 -0
- data/.idea/scopes/scope_settings.xml +5 -0
- data/.idea/vcs.xml +7 -0
- data/.idea/workspace.xml +508 -0
- data/.travis.yml +4 -0
- data/CHANGELOG.md +51 -0
- data/Gemfile +3 -3
- data/Gemfile.lock +73 -0
- data/LICENSE.txt +2 -2
- data/Rakefile +42 -1
- data/VERSION +1 -0
- data/lib/android/apk.rb +207 -0
- data/lib/android/axml_parser.rb +173 -0
- data/lib/android/dex/access_flag.rb +74 -0
- data/lib/android/dex/dex_object.rb +475 -0
- data/lib/android/dex/info.rb +151 -0
- data/lib/android/dex/utils.rb +45 -0
- data/lib/android/dex.rb +92 -0
- data/lib/android/layout.rb +44 -0
- data/lib/android/manifest.rb +249 -0
- data/lib/android/resource.rb +529 -0
- data/lib/android/utils.rb +55 -0
- data/lib/ruby_apk.rb +7 -0
- data/ruby_android.gemspec +103 -17
- data/spec/apk_spec.rb +301 -0
- data/spec/axml_parser_spec.rb +67 -0
- data/spec/data/sample.apk +0 -0
- data/spec/data/sample_AndroidManifest.xml +0 -0
- data/spec/data/sample_classes.dex +0 -0
- data/spec/data/sample_resources.arsc +0 -0
- data/spec/data/sample_resources_utf16.arsc +0 -0
- data/spec/data/str_resources.arsc +0 -0
- data/spec/dex/access_flag_spec.rb +42 -0
- data/spec/dex/dex_object_spec.rb +118 -0
- data/spec/dex/info_spec.rb +121 -0
- data/spec/dex/utils_spec.rb +56 -0
- data/spec/dex_spec.rb +59 -0
- data/spec/layout_spec.rb +27 -0
- data/spec/manifest_spec.rb +221 -0
- data/spec/resource_spec.rb +170 -0
- data/spec/ruby_apk_spec.rb +4 -0
- data/spec/spec_helper.rb +17 -0
- data/spec/utils_spec.rb +90 -0
- metadata +112 -27
- data/.gitignore +0 -14
- data/lib/ruby_android/version.rb +0 -3
- data/lib/ruby_android.rb +0 -7
@@ -0,0 +1,529 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'stringio'
|
3
|
+
|
4
|
+
module Android
|
5
|
+
# based on Android OS source code
|
6
|
+
# /frameworks/base/include/utils/ResourceTypes.h
|
7
|
+
# @see http://justanapplication.wordpress.com/category/android/android-resources/
|
8
|
+
class Resource
|
9
|
+
class Chunk
|
10
|
+
def initialize(data, offset)
|
11
|
+
@data = data
|
12
|
+
@offset = offset
|
13
|
+
exec_parse
|
14
|
+
end
|
15
|
+
def exec_parse
|
16
|
+
@data_io = StringIO.new(@data, 'rb')
|
17
|
+
@data_io.seek(@offset)
|
18
|
+
parse
|
19
|
+
@data_io.close
|
20
|
+
end
|
21
|
+
def read_int32
|
22
|
+
@data_io.read(4).unpack('V')[0]
|
23
|
+
end
|
24
|
+
def read_int16
|
25
|
+
@data_io.read(2).unpack('v')[0]
|
26
|
+
end
|
27
|
+
def read_int8
|
28
|
+
@data_io.read(1).ord
|
29
|
+
end
|
30
|
+
def current_position
|
31
|
+
@data_io.pos
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class ChunkHeader < Chunk
|
36
|
+
attr_reader :type, :header_size, :size
|
37
|
+
private
|
38
|
+
def parse
|
39
|
+
@type = read_int16
|
40
|
+
@header_size = read_int16
|
41
|
+
@size = read_int32
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
class ResTableHeader < ChunkHeader
|
46
|
+
attr_reader :package_count
|
47
|
+
def parse
|
48
|
+
super
|
49
|
+
@package_count = read_int32
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
class ResStringPool < ChunkHeader
|
54
|
+
SORTED_FLAG = 1 << 0
|
55
|
+
UTF8_FLAG = 1 << 8
|
56
|
+
|
57
|
+
attr_reader :strings
|
58
|
+
private
|
59
|
+
def parse
|
60
|
+
super
|
61
|
+
@string_count = read_int32
|
62
|
+
@style_count = read_int32
|
63
|
+
@flags = read_int32
|
64
|
+
@string_start = read_int32
|
65
|
+
@style_start = read_int32
|
66
|
+
@strings = []
|
67
|
+
@string_count.times do
|
68
|
+
offset = @offset + @string_start + read_int32
|
69
|
+
if (@flags & UTF8_FLAG != 0)
|
70
|
+
# read length twice(utf16 length and utf8 length)
|
71
|
+
# const uint16_t* ResStringPool::stringAt(size_t idx, size_t* u16len) const
|
72
|
+
u16len, o16 = ResStringPool.utf8_len(@data[offset, 2])
|
73
|
+
u8len, o8 = ResStringPool.utf8_len(@data[offset+o16, 2])
|
74
|
+
str = @data[offset+o16+o8, u8len]
|
75
|
+
@strings << str.force_encoding(Encoding::UTF_8)
|
76
|
+
else
|
77
|
+
u16len, o16 = ResStringPool.utf16_len(@data[offset, 4])
|
78
|
+
str = @data[offset+o16, u16len*2]
|
79
|
+
str.force_encoding(Encoding::UTF_16LE)
|
80
|
+
@strings << str.encode(Encoding::UTF_8)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# @note refer to /frameworks/base/libs/androidfw/ResourceTypes.cpp
|
86
|
+
# static inline size_t decodeLength(const uint8_t** str)
|
87
|
+
# @param [String] data parse target
|
88
|
+
# @return[Integer, Integer] string length and parsed length
|
89
|
+
def self.utf8_len(data)
|
90
|
+
first, second = data.unpack('CC')
|
91
|
+
if (first & 0x80) != 0
|
92
|
+
return (((first & 0x7F) << 8) + second), 2
|
93
|
+
else
|
94
|
+
return first, 1
|
95
|
+
end
|
96
|
+
end
|
97
|
+
# @note refer to /frameworks/base/libs/androidfw/ResourceTypes.cpp
|
98
|
+
# static inline size_t decodeLength(const char16_t** str)
|
99
|
+
# @param [String] data parse target
|
100
|
+
# @return[Integer, Integer] string length and parsed length
|
101
|
+
def self.utf16_len(data)
|
102
|
+
first, second = data.unpack('vv')
|
103
|
+
if (first & 0x8000) != 0
|
104
|
+
return (((first & 0x7FFF) << 16) + second), 4
|
105
|
+
else
|
106
|
+
return first, 2
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
class ResTablePackage < ChunkHeader
|
112
|
+
attr_reader :name
|
113
|
+
|
114
|
+
def global_string_pool=(pool)
|
115
|
+
@global_string_pool = pool
|
116
|
+
extract_res_strings
|
117
|
+
end
|
118
|
+
|
119
|
+
# find resource by resource id
|
120
|
+
# @param [String] res_id (like '@0x7f010001' or '@string/key')
|
121
|
+
# @param [Hash] opts option
|
122
|
+
# @option opts [String] :lang language code like 'ja', 'cn'...
|
123
|
+
# @option opts [String] :contry cantry code like 'jp'...
|
124
|
+
# @raise [ArgumentError] invalid id format
|
125
|
+
# @note
|
126
|
+
# This method only support string and drawable resource for now.
|
127
|
+
# @note
|
128
|
+
# Always return nil if assign not string type res id.
|
129
|
+
#
|
130
|
+
def find(res_id, opts={})
|
131
|
+
hex_id = strid2int(res_id)
|
132
|
+
tid = ((hex_id&0xff0000) >>16)
|
133
|
+
key = hex_id&0xffff
|
134
|
+
|
135
|
+
case type(tid)
|
136
|
+
when 'string'
|
137
|
+
return find_res_string(key, opts)
|
138
|
+
when 'drawable'
|
139
|
+
drawables = []
|
140
|
+
@types[tid].each do |type|
|
141
|
+
unless type[key].nil?
|
142
|
+
drawables << @global_string_pool.strings[type[key].val.data]
|
143
|
+
end
|
144
|
+
end
|
145
|
+
return drawables
|
146
|
+
else
|
147
|
+
nil
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def res_types
|
152
|
+
end
|
153
|
+
def find_res_string(key, opts={})
|
154
|
+
unless opts[:lang].nil?
|
155
|
+
string = @res_strings_lang[opts[:lang]]
|
156
|
+
end
|
157
|
+
unless opts[:contry].nil?
|
158
|
+
string = @res_strings_contry[opts[:contry]]
|
159
|
+
end
|
160
|
+
string = @res_strings_default if string.nil?
|
161
|
+
raise NotFoundError unless string.has_key? key
|
162
|
+
return string[key]
|
163
|
+
end
|
164
|
+
private :find_res_string
|
165
|
+
|
166
|
+
# convert string resource id to fixnum
|
167
|
+
# @param [String] res_id (like '@0x7f010001' or '@string/key')
|
168
|
+
# @return [Fixnum] integer id (like 0x7f010001)
|
169
|
+
# @raise [ArgumentError] invalid format
|
170
|
+
def strid2int(res_id)
|
171
|
+
case res_id
|
172
|
+
when /^@?0x[0-9a-fA-F]{8}$/
|
173
|
+
return res_id.sub(/^@/,'').to_i(16)
|
174
|
+
when /^@?\w+\/\w+/
|
175
|
+
return res_hex_id(res_id).sub(/^@/,'').to_i(16)
|
176
|
+
else
|
177
|
+
raise ArgumentError
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
def res_readable_id(hex_id)
|
182
|
+
if hex_id.kind_of? String
|
183
|
+
hex_id = hex_id.sub(/^@/,'').to_i(16)
|
184
|
+
end
|
185
|
+
tid = ((hex_id&0xff0000) >>16)
|
186
|
+
key = hex_id&0xffff
|
187
|
+
raise NotFoundError if !@types.has_key?(tid) || @types[tid][0][key].nil?
|
188
|
+
keyid= @types[tid][0][key].key # ugh!
|
189
|
+
"@#{type(tid)}/#{key(keyid)}"
|
190
|
+
end
|
191
|
+
def res_hex_id(readable_id, opt={})
|
192
|
+
dummy, typestr, keystr = readable_id.match(/^@?(\w+)\/(\w+)$/).to_a
|
193
|
+
tid = type_id(typestr)
|
194
|
+
raise NotFoundError unless @types.has_key?(tid)
|
195
|
+
keyid = @types[tid][0].keys[keystr]
|
196
|
+
raise NotFoundError if keyid.nil?
|
197
|
+
"@0x7f%02x%04x" % [tid, keyid]
|
198
|
+
end
|
199
|
+
|
200
|
+
def type_strings
|
201
|
+
@type_strings.strings
|
202
|
+
end
|
203
|
+
def type(id)
|
204
|
+
type_strings[id-1]
|
205
|
+
end
|
206
|
+
def type_id(str)
|
207
|
+
raise NotFoundError unless type_strings.include? str
|
208
|
+
type_strings.index(str) + 1
|
209
|
+
end
|
210
|
+
def key_strings
|
211
|
+
@key_strings.strings
|
212
|
+
end
|
213
|
+
def key(id)
|
214
|
+
key_strings[id]
|
215
|
+
end
|
216
|
+
def key_id(str)
|
217
|
+
raise NotFoundError unless key_strings.include? str
|
218
|
+
key_strings.index(str)
|
219
|
+
end
|
220
|
+
|
221
|
+
def parse
|
222
|
+
super
|
223
|
+
@id = read_int32
|
224
|
+
@name = @data_io.read(256).force_encoding(Encoding::UTF_16LE)
|
225
|
+
@name.encode!(Encoding::UTF_8).strip!
|
226
|
+
type_strings_offset = read_int32
|
227
|
+
@type_strings = ResStringPool.new(@data, @offset + type_strings_offset)
|
228
|
+
@last_public_type = read_int32
|
229
|
+
key_strings_offset = read_int32
|
230
|
+
@key_strings = ResStringPool.new(@data, @offset + key_strings_offset)
|
231
|
+
@last_public_key = read_int32
|
232
|
+
|
233
|
+
offset = @offset + key_strings_offset + @key_strings.size
|
234
|
+
|
235
|
+
@types = {}
|
236
|
+
@specs = {}
|
237
|
+
while offset < (@offset + @size)
|
238
|
+
type = @data[offset, 2].unpack('v')[0]
|
239
|
+
case type
|
240
|
+
when 0x0201 # RES_TABLE_TYPE_TYPE
|
241
|
+
type = ResTableType.new(@data, offset, self)
|
242
|
+
offset += type.size
|
243
|
+
@types[type.id] = [] if @types[type.id].nil?
|
244
|
+
@types[type.id] << type
|
245
|
+
when 0x0202 # RES_TABLE_TYPE_SPEC_TYPE`
|
246
|
+
spec = ResTableTypeSpec.new(@data, offset)
|
247
|
+
offset += spec.size
|
248
|
+
@specs[spec.id] = [] if @specs[spec.id].nil?
|
249
|
+
@specs[spec.id] << spec
|
250
|
+
else
|
251
|
+
raise "chunk type error: type:%#04x" % type
|
252
|
+
end
|
253
|
+
end
|
254
|
+
end
|
255
|
+
private :parse
|
256
|
+
|
257
|
+
def extract_res_strings
|
258
|
+
@res_strings_lang = {}
|
259
|
+
@res_strings_contry = {}
|
260
|
+
begin
|
261
|
+
type = type_id('string')
|
262
|
+
rescue NotFoundError
|
263
|
+
return
|
264
|
+
end
|
265
|
+
@types[type_id('string')].each do |type|
|
266
|
+
str_hash = {}
|
267
|
+
type.entry_count.times do |i|
|
268
|
+
entry = type[i]
|
269
|
+
if entry.nil?
|
270
|
+
str_hash[i] = nil
|
271
|
+
else
|
272
|
+
str_hash[i] = @global_string_pool.strings[type[i].val.data]
|
273
|
+
end
|
274
|
+
end
|
275
|
+
lang = type.config.locale_lang
|
276
|
+
contry = type.config.locale_contry
|
277
|
+
if lang.nil? && contry.nil?
|
278
|
+
@res_strings_default = str_hash
|
279
|
+
else
|
280
|
+
@res_strings_lang[lang] = str_hash unless lang.nil?
|
281
|
+
@res_strings_contry[contry] = str_hash unless contry.nil?
|
282
|
+
end
|
283
|
+
end
|
284
|
+
end
|
285
|
+
private :extract_res_strings
|
286
|
+
|
287
|
+
def inspect
|
288
|
+
"<ResTablePackage offset:%#08x, size:%#x, name:\"%s\">" % [@offset, @size, @name]
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
class ResTableType < ChunkHeader
|
293
|
+
attr_reader :id, :entry_count, :entry_start, :config
|
294
|
+
attr_reader :keys
|
295
|
+
|
296
|
+
def initialize(data, offset, pkg)
|
297
|
+
@pkg = pkg
|
298
|
+
super(data, offset)
|
299
|
+
end
|
300
|
+
# @param [String] index key name
|
301
|
+
# @param [Fixnum] index key index
|
302
|
+
# @return [ResTableEntry]
|
303
|
+
# @return [ResTableMapEntry]
|
304
|
+
# @return nil if entry index is NO_ENTRY(0xFFFFFFFF)
|
305
|
+
def [](index)
|
306
|
+
@entries[index]
|
307
|
+
end
|
308
|
+
|
309
|
+
def parse
|
310
|
+
super
|
311
|
+
@id = read_int8
|
312
|
+
res0 = read_int8 # must be 0.(maybe 4byte align)
|
313
|
+
res1 = read_int16 # must be 0.(maybe 4byte align)
|
314
|
+
@entry_count = read_int32
|
315
|
+
@entry_start = read_int32
|
316
|
+
@config = ResTableConfig.new(@data, current_position)
|
317
|
+
@data_io.seek(@config.size, IO::SEEK_CUR)
|
318
|
+
|
319
|
+
@entries = []
|
320
|
+
@keys = {}
|
321
|
+
@entry_count.times do |i|
|
322
|
+
entry_index = read_int32
|
323
|
+
if entry_index == ResTableEntry::NO_ENTRY
|
324
|
+
@entries << nil
|
325
|
+
else
|
326
|
+
entry = ResTableEntry.read_entry(@data, @offset + @entry_start + entry_index)
|
327
|
+
@entries << entry
|
328
|
+
@keys[@pkg.key(entry.key)] = i
|
329
|
+
end
|
330
|
+
end
|
331
|
+
end
|
332
|
+
private :parse
|
333
|
+
|
334
|
+
|
335
|
+
def inspect
|
336
|
+
"<ResTableType offset:0x#{@offset.to_s(16)}, id:#{@id}, " +
|
337
|
+
"count:#{@entry_count}, start:0x#{@entry_start.to_s(16)}>"
|
338
|
+
end
|
339
|
+
end
|
340
|
+
|
341
|
+
class ResTableConfig < Chunk
|
342
|
+
attr_reader :size, :imei, :locale_lang, :locale_contry, :input
|
343
|
+
attr_reader :screen_input, :version, :screen_config
|
344
|
+
def parse
|
345
|
+
@size = read_int32
|
346
|
+
@imei = read_int32
|
347
|
+
la = @data_io.read(2)
|
348
|
+
@locale_lang = la unless la == "\x00\x00"
|
349
|
+
cn = @data_io.read(2)
|
350
|
+
@locale_contry = cn unless cn == "\x00\x00"
|
351
|
+
@screen_type = read_int32
|
352
|
+
@input = read_int32
|
353
|
+
@screen_input = read_int32
|
354
|
+
@version = read_int32
|
355
|
+
@screen_config = read_int32
|
356
|
+
end
|
357
|
+
def inspect
|
358
|
+
"<ResTableConfig size:#{@size}, imei:#{@imei}, la:'#{@locale_lang}' cn:'#{@locale_contry}'"
|
359
|
+
end
|
360
|
+
end
|
361
|
+
|
362
|
+
class ResTableTypeSpec < ChunkHeader
|
363
|
+
attr_reader :id, :entry_count
|
364
|
+
|
365
|
+
def parse
|
366
|
+
super
|
367
|
+
@id = read_int8
|
368
|
+
res0 = read_int8 # must be 0.(maybe 4byte align)
|
369
|
+
res1 = read_int16 # must be 0.(maybe 4byte align)
|
370
|
+
@entry_count = read_int32
|
371
|
+
end
|
372
|
+
private :parse
|
373
|
+
|
374
|
+
def inspect
|
375
|
+
"<ResTableTypeSpec id:#{@id} entry count:#{@entry_count}>"
|
376
|
+
end
|
377
|
+
end
|
378
|
+
class ResTableEntry < Chunk
|
379
|
+
NO_ENTRY = 0xFFFFFFFF
|
380
|
+
|
381
|
+
# @return [ResTableEntry] if not set FLAG_COMPLEX
|
382
|
+
# @return [ResTableMapEntry] if not set FLAG_COMPLEX
|
383
|
+
def self.read_entry(data, offset)
|
384
|
+
flag = data[offset + 2, 2].unpack('v')[0]
|
385
|
+
if flag & ResTableEntry::FLAG_COMPLEX == 0
|
386
|
+
ResTableEntry.new(data, offset)
|
387
|
+
else
|
388
|
+
ResTableMapEntry.new(data, offset)
|
389
|
+
end
|
390
|
+
end
|
391
|
+
|
392
|
+
# If set, this is a complex entry, holding a set of name/value
|
393
|
+
# mappings. It is followed by an array of ResTable_map structures.
|
394
|
+
FLAG_COMPLEX = 0x01
|
395
|
+
# If set, this resource has been declared public, so libraries
|
396
|
+
# are allowed to reference it.
|
397
|
+
FLAG_PUBLIC = 0x02
|
398
|
+
|
399
|
+
attr_reader :size, :key, :val
|
400
|
+
def parse
|
401
|
+
@size = read_int16
|
402
|
+
@flag = read_int16
|
403
|
+
@key = read_int32 # RefStringPool_key
|
404
|
+
@val = ResValue.new(@data, current_position)
|
405
|
+
end
|
406
|
+
private :parse
|
407
|
+
|
408
|
+
def inspect
|
409
|
+
"<ResTableEntry @size=#{@size}, @key=#{@key} @flag=#{@flag}>"
|
410
|
+
end
|
411
|
+
end
|
412
|
+
class ResTableMapEntry < ResTableEntry
|
413
|
+
attr_reader :parent, :count
|
414
|
+
def parse
|
415
|
+
super
|
416
|
+
# resource identifier of the parent mapping, 0 if there is none.
|
417
|
+
@parent = read_int32
|
418
|
+
# number of name/value pairs that follw for FLAG_COMPLEX
|
419
|
+
@count = read_int32
|
420
|
+
# TODO: implement read ResTableMap object
|
421
|
+
end
|
422
|
+
private :parse
|
423
|
+
end
|
424
|
+
class ResTableMap < Chunk
|
425
|
+
def size
|
426
|
+
@val.size + 4
|
427
|
+
end
|
428
|
+
def parse
|
429
|
+
@name = read_int32
|
430
|
+
@val = ResValue.new(@data, current_position)
|
431
|
+
end
|
432
|
+
end
|
433
|
+
|
434
|
+
class ResValue < Chunk
|
435
|
+
attr_reader :size, :data_type, :data
|
436
|
+
def parse
|
437
|
+
@size = read_int16
|
438
|
+
res0 = read_int8 # Always set 0.
|
439
|
+
@data_type = read_int8
|
440
|
+
@data = read_int32
|
441
|
+
end
|
442
|
+
private :parse
|
443
|
+
end
|
444
|
+
|
445
|
+
######################################################################
|
446
|
+
# @returns [Hash] { name(String) => value(ResTablePackage) }
|
447
|
+
attr_reader :packages
|
448
|
+
|
449
|
+
def initialize(data)
|
450
|
+
data.force_encoding(Encoding::ASCII_8BIT)
|
451
|
+
@data = data
|
452
|
+
parse()
|
453
|
+
end
|
454
|
+
|
455
|
+
|
456
|
+
# @return [Array<String>] all strings defined in arsc.
|
457
|
+
def strings
|
458
|
+
@string_pool.strings
|
459
|
+
end
|
460
|
+
|
461
|
+
# @return [Fixnum] number of packages
|
462
|
+
def package_count
|
463
|
+
@res_table.package_count
|
464
|
+
end
|
465
|
+
|
466
|
+
# This method only support string resource for now.
|
467
|
+
# find resource by resource id
|
468
|
+
# @param [String] res_id (like '@0x7f010001' or '@string/key')
|
469
|
+
# @param [Hash] opts option
|
470
|
+
# @option opts [String] :lang language code like 'ja', 'cn'...
|
471
|
+
# @option opts [String] :contry cantry code like 'jp'...
|
472
|
+
# @raise [ArgumentError] invalid id format
|
473
|
+
# @note
|
474
|
+
# This method only support string resource for now.
|
475
|
+
# @note
|
476
|
+
# Always return nil if assign not string type res id.
|
477
|
+
# @since 0.5.0
|
478
|
+
def find(rsc_id, opt={})
|
479
|
+
first_pkg.find(rsc_id, opt)
|
480
|
+
end
|
481
|
+
|
482
|
+
# @param [String] hex_id hexoctet format resource id('@0x7f010001')
|
483
|
+
# @return [String] readable resource id ('@string/key')
|
484
|
+
# @since 0.5.0
|
485
|
+
def res_readable_id(hex_id)
|
486
|
+
first_pkg.res_readable_id(hex_id)
|
487
|
+
end
|
488
|
+
|
489
|
+
# convert readable resource id to hex id
|
490
|
+
# @param [String] readable_id readable resource id ('@string/key')
|
491
|
+
# @return [String] hexoctet format resource id('@0x7f010001')
|
492
|
+
# @since 0.5.0
|
493
|
+
def res_hex_id(readable_id)
|
494
|
+
first_pkg.res_hex_id(readable_id)
|
495
|
+
end
|
496
|
+
|
497
|
+
def first_pkg
|
498
|
+
@packages.first[1]
|
499
|
+
end
|
500
|
+
private
|
501
|
+
def parse
|
502
|
+
offset = 0
|
503
|
+
|
504
|
+
while offset < @data.size
|
505
|
+
type = @data[offset, 2].unpack('v')[0]
|
506
|
+
#print "[%#08x] " % offset
|
507
|
+
@packages = {}
|
508
|
+
case type
|
509
|
+
when 0x0001 # RES_STRING_POOL_TYPE
|
510
|
+
@string_pool = ResStringPool.new(@data, offset)
|
511
|
+
offset += @string_pool.size
|
512
|
+
#puts "RES_STRING_POOL_TYPE %#x, %#x" % [@string_pool.size, offset]
|
513
|
+
when 0x0002 # RES_TABLE_TYPE
|
514
|
+
#puts "RES_TABLE_TYPE"
|
515
|
+
@res_table = ResTableHeader.new(@data, offset)
|
516
|
+
offset += @res_table.header_size
|
517
|
+
when 0x0200 # RES_TABLE_PACKAGE_TYPE
|
518
|
+
#puts "RES_TABLE_PACKAGE_TYPE"
|
519
|
+
pkg = ResTablePackage.new(@data, offset)
|
520
|
+
pkg.global_string_pool = @string_pool
|
521
|
+
offset += pkg.size
|
522
|
+
@packages[pkg.name] = pkg
|
523
|
+
else
|
524
|
+
raise "chunk type error: type:%#04x" % type
|
525
|
+
end
|
526
|
+
end
|
527
|
+
end
|
528
|
+
end
|
529
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
|
2
|
+
module Android
|
3
|
+
# Utility methods
|
4
|
+
module Utils
|
5
|
+
# path is apk file or not.
|
6
|
+
# @param [String] path target file path
|
7
|
+
# @return [Boolean]
|
8
|
+
def self.apk?(path)
|
9
|
+
begin
|
10
|
+
apk = Apk.new(path)
|
11
|
+
return true
|
12
|
+
rescue => e
|
13
|
+
return false
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# data is elf file or not.
|
18
|
+
# @param [String] data target data
|
19
|
+
# @return [Boolean]
|
20
|
+
def self.elf?(data)
|
21
|
+
data[0..3] == "\x7f\x45\x4c\x46"
|
22
|
+
rescue => e
|
23
|
+
false
|
24
|
+
end
|
25
|
+
|
26
|
+
# data is cert file or not.
|
27
|
+
# @param [String] data target data
|
28
|
+
# @return [Boolean]
|
29
|
+
def self.cert?(data)
|
30
|
+
data[0..1] == "\x30\x82"
|
31
|
+
rescue => e
|
32
|
+
false
|
33
|
+
end
|
34
|
+
|
35
|
+
# data is dex file or not.
|
36
|
+
# @param [String] data target data
|
37
|
+
# @return [Boolean]
|
38
|
+
def self.dex?(data)
|
39
|
+
data[0..7] == "\x64\x65\x78\x0a\x30\x33\x35\x00" # "dex\n035\0"
|
40
|
+
rescue => e
|
41
|
+
false
|
42
|
+
end
|
43
|
+
|
44
|
+
# data is valid dex file or not.
|
45
|
+
# @param [String] data target data
|
46
|
+
# @return [Boolean]
|
47
|
+
def self.valid_dex?(data)
|
48
|
+
Android::Dex.new(data)
|
49
|
+
true
|
50
|
+
rescue => e
|
51
|
+
false
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
data/lib/ruby_apk.rb
ADDED