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