apktools 0.6.0 → 0.7.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/lib/apktools/apkresources.rb +683 -623
- data/lib/apktools/apkxml.rb +422 -422
- metadata +10 -12
|
@@ -1,14 +1,14 @@
|
|
|
1
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
|
|
5
5
|
# without restriction, including without limitation the rights to use, copy, modify,
|
|
6
6
|
# merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
|
|
7
7
|
# persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
8
|
-
#
|
|
8
|
+
#
|
|
9
9
|
# The above copyright notice and this permission notice shall be included in all copies
|
|
10
10
|
# or substantial portions of the Software.
|
|
11
|
-
#
|
|
11
|
+
#
|
|
12
12
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
|
13
13
|
# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
|
14
14
|
# PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
|
@@ -16,629 +16,689 @@
|
|
|
16
16
|
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
17
17
|
# DEALINGS IN THE SOFTWARE.
|
|
18
18
|
|
|
19
|
-
require 'zip
|
|
19
|
+
require 'zip'
|
|
20
20
|
|
|
21
21
|
##
|
|
22
22
|
# Class to parse an APK's resources.arsc data and retrieve resource
|
|
23
23
|
# data associated with a given R.id value
|
|
24
24
|
class ApkResources
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
26
|
+
DEBUG = false # :nodoc:
|
|
27
|
+
|
|
28
|
+
##
|
|
29
|
+
# Structure defining the type and size of each resource chunk
|
|
30
|
+
#
|
|
31
|
+
# ChunkHeader = Struct.new(:type, :size, :chunk_size)
|
|
32
|
+
ChunkHeader = Struct.new(:type, :size, :chunk_size)
|
|
33
|
+
|
|
34
|
+
##
|
|
35
|
+
# Structure that houses a group of strings
|
|
36
|
+
#
|
|
37
|
+
# StringPool = Struct.new(:header, :string_count, :style_count, :values)
|
|
38
|
+
#
|
|
39
|
+
# * +header+ = ChunkHeader
|
|
40
|
+
# * +string_count+ = Number of normal strings in the pool
|
|
41
|
+
# * +style_count+ = Number of styled strings in the pool
|
|
42
|
+
# * +values+ = Array of the string values
|
|
43
|
+
StringPool = Struct.new(:header, :string_count, :style_count, :values)
|
|
44
|
+
|
|
45
|
+
##
|
|
46
|
+
# Structure defining the data inside of the package chunk
|
|
47
|
+
#
|
|
48
|
+
# PackageHeader = Struct.new(:header, :id, :name, :type_strings, :key_strings)
|
|
49
|
+
#
|
|
50
|
+
# * +header+ = ChunkHeader
|
|
51
|
+
# * +id+ = Package id; usually 0x7F for application resources
|
|
52
|
+
# * +name+ = Package name (e.g. "com.example.application")
|
|
53
|
+
# * +type_strings+ = Array of the type string values present (e.g. "drawable")
|
|
54
|
+
# * +key_strings+ = Array of the key string values present (e.g. "ic_launcher")
|
|
55
|
+
PackageHeader = Struct.new(:header, :id, :name, :type_strings, :key_strings)
|
|
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
|
+
|
|
68
|
+
##
|
|
69
|
+
# Structure defining the flags for a block of common resources
|
|
70
|
+
#
|
|
71
|
+
# ResTypeSpec = Struct.new(:header, :id, :entry_count, :entries, :types)
|
|
72
|
+
#
|
|
73
|
+
# * +header+ = ChunkHeader
|
|
74
|
+
# * +id+ = String value of the referenced type (e.g. "drawable")
|
|
75
|
+
# * +entry_count+ = Number of type entries in this chunk
|
|
76
|
+
# * +entries+ = Array of config flags for each type entry
|
|
77
|
+
# * +types+ = The ResType associated with this spec
|
|
78
|
+
ResTypeSpec = Struct.new(:header, :id, :entry_count, :entries, :types)
|
|
79
|
+
|
|
80
|
+
##
|
|
81
|
+
# Structure that houses all the resources for a given type
|
|
82
|
+
#
|
|
83
|
+
# ResType = Struct.new(:header, :id, :config, :entry_count, :entries)
|
|
84
|
+
#
|
|
85
|
+
# * +header+ = ChunkHeader
|
|
86
|
+
# * +id+ = String value of the referenced type (e.g. "drawable")
|
|
87
|
+
# * +config+ = ResTypeConfig defining the configuration for this type
|
|
88
|
+
# * +entry_count+ = Number of entries in this chunk
|
|
89
|
+
# * +entries+ = Array of Hashes of [ResTypeConfig, ResTypeEntry] in this chunk
|
|
90
|
+
ResType = Struct.new(:header, :id, :config, :entry_count, :entries)
|
|
91
|
+
|
|
92
|
+
##
|
|
93
|
+
# Structure that houses the configuration flags for a given resource.
|
|
94
|
+
#
|
|
95
|
+
# ResTypeConfig = Struct.new(:imsi, :locale, :screen_type, :input, :screen_size, :version, :screen_config, :screen_size_dp)
|
|
96
|
+
#
|
|
97
|
+
# * +imsi+ = Flags marking country code and network code
|
|
98
|
+
# * +locale+ = Flags marking locale requirements (language)
|
|
99
|
+
# * +screen_type+ = Flags/values for screen density
|
|
100
|
+
# * +input+ = Flags marking input types and visibility status
|
|
101
|
+
# * +screen_size+ = Flags marking screen size and length
|
|
102
|
+
# * +version+ = Minimum API version
|
|
103
|
+
# * +screen_config+ = Flags marking screen configuration (like orientation)
|
|
104
|
+
# * +screen_size_dp+ = Flags marking smallest width constraints
|
|
105
|
+
#
|
|
106
|
+
# A default configuration is defined as ResTypeConfig.new(0, 0, 0, 0, 0, 0, 0, 0)
|
|
107
|
+
ResTypeConfig = Struct.new(:imsi, :locale, :screen_type, :input, :screen_size, :version, :screen_config, :screen_size_dp)
|
|
108
|
+
|
|
109
|
+
##
|
|
110
|
+
# Structure that houses the data for a given resource entry
|
|
111
|
+
#
|
|
112
|
+
# ResTypeEntry = Struct.new(:flags, :key, :data_type, :data)
|
|
113
|
+
#
|
|
114
|
+
# * +flags+ = Flags marking if the resource is complex or public
|
|
115
|
+
# * +key+ = Key string for the resource (e.g. "ic_launcher" of R.drawable.ic_launcher")
|
|
116
|
+
# * +data_type+ = Type identifier. The meaning of this value varies with the type of resource
|
|
117
|
+
# * +data+ = Resource value (e.g. "res/drawable/ic_launcher" for R.drawable.ic_launcher")
|
|
118
|
+
#
|
|
119
|
+
# A single resource key can have multiple entries depending on configuration, so these structs
|
|
120
|
+
# are often returned in groups, keyed by a ResTypeConfig
|
|
121
|
+
ResTypeEntry = Struct.new(:flags, :key, :data_type, :data)
|
|
122
|
+
|
|
123
|
+
# PackageHeader containing information about all the type and key strings in the package
|
|
124
|
+
attr_reader :package_header
|
|
125
|
+
# StringPool containing all value strings in the package
|
|
126
|
+
attr_reader :stringpool_main
|
|
127
|
+
# Hash of Package chunks, keyed by package id
|
|
128
|
+
attr_reader :packages
|
|
129
|
+
|
|
130
|
+
##
|
|
131
|
+
# Create a new ApkResources instance from the specified +apk_file+
|
|
132
|
+
#
|
|
133
|
+
# This opens and parses the contents of the APK's resources.arsc file.
|
|
134
|
+
|
|
135
|
+
def initialize(apk_file)
|
|
136
|
+
data = nil
|
|
137
|
+
# Get resources.arsc from the APK file
|
|
138
|
+
Zip::File.foreach(apk_file) do |f|
|
|
139
|
+
if f.name.match(/resources.arsc/)
|
|
140
|
+
data = f.get_input_stream.read
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Parse the Table Chunk
|
|
145
|
+
## Header
|
|
146
|
+
header_type = read_short(data, HEADER_START)
|
|
147
|
+
header_size = read_short(data, HEADER_START+2)
|
|
148
|
+
header_chunk_size = read_word(data, HEADER_START+4)
|
|
149
|
+
header_package_count = read_word(data, HEADER_START+8)
|
|
150
|
+
puts "Resource Package Count = #{header_package_count}" if DEBUG
|
|
151
|
+
|
|
152
|
+
# Parse the StringPool Chunk
|
|
153
|
+
## Header
|
|
154
|
+
startoffset_pool = HEADER_START + header_size
|
|
155
|
+
puts "Parse Main StringPool Chunk" if DEBUG
|
|
156
|
+
@stringpool_main = parse_stringpool(data, startoffset_pool)
|
|
157
|
+
puts "#{@stringpool_main.values.length} strings found" if DEBUG
|
|
158
|
+
|
|
159
|
+
# Parse the Package Chunk
|
|
160
|
+
## Header
|
|
161
|
+
startoffset_package = startoffset_pool + @stringpool_main.header.chunk_size
|
|
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
|
|
169
|
+
|
|
170
|
+
i += 1
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
end #initalize
|
|
174
|
+
|
|
175
|
+
##
|
|
176
|
+
# Return array of all string values in the file
|
|
177
|
+
|
|
178
|
+
def get_all_strings
|
|
179
|
+
return @stringpool_main.values
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
##
|
|
183
|
+
# Return hash of all the type values in the file
|
|
184
|
+
# keyed by package id
|
|
185
|
+
|
|
186
|
+
def get_all_types
|
|
187
|
+
types = Hash.new()
|
|
188
|
+
@packages.each do |key, value|
|
|
189
|
+
types[key] = value.stringpool_typestrings.values
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
return types
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
##
|
|
196
|
+
# Return hash of all the key values in the file
|
|
197
|
+
# keyed by package id
|
|
198
|
+
|
|
199
|
+
def get_all_keys
|
|
200
|
+
keys = Hash.new()
|
|
201
|
+
@packages.each do |key, value|
|
|
202
|
+
keys[key] = value.stringpool_keystrings.values
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
return keys
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
##
|
|
209
|
+
# Obtain the key value for a given resource id
|
|
210
|
+
#
|
|
211
|
+
# res_id: ID value of a resource as a FixNum or String representation (i.e. 0x7F060001)
|
|
212
|
+
# xml_format: Optionally format return string for XML files.
|
|
213
|
+
#
|
|
214
|
+
# If xml_format is true, return value will be @<type>/<key>
|
|
215
|
+
# If xml_format is false or missing, return value will be R.<type>.<key>
|
|
216
|
+
# If the resource id does not exist, return value will be nil
|
|
217
|
+
|
|
218
|
+
def get_resource_key(res_id, xml_format=false)
|
|
219
|
+
if res_id.is_a? String
|
|
220
|
+
res_id = res_id.hex
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
# R.id integers are a concatenation of package_id, type_id, and entry index
|
|
224
|
+
res_package = (res_id >> 24) & 0xFF
|
|
225
|
+
res_type = (res_id >> 16) & 0xFF
|
|
226
|
+
res_index = res_id & 0xFFFF
|
|
227
|
+
|
|
228
|
+
package_element = @packages[res_package]
|
|
229
|
+
if package_element == nil
|
|
230
|
+
# This is not a resource we can parse
|
|
231
|
+
return nil
|
|
232
|
+
end
|
|
233
|
+
|
|
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]
|
|
241
|
+
if entry == nil
|
|
242
|
+
# There is no entry in our table for this resource
|
|
243
|
+
puts "Could not find #{res_spec.types.id} ResType chunk" if DEBUG
|
|
244
|
+
return nil
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
if xml_format
|
|
248
|
+
return "@#{res_spec.id}/#{entry.values[0].key}"
|
|
249
|
+
else
|
|
250
|
+
return "R.#{res_spec.id}.#{entry.values[0].key}"
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
##
|
|
255
|
+
# Obtain the default value for a given resource id
|
|
256
|
+
#
|
|
257
|
+
# res_id: ID values of a resources as a FixNum or String representation (i.e. 0x7F060001)
|
|
258
|
+
#
|
|
259
|
+
# Returns: The default ResTypeEntry to the given id, or nil if no default exists
|
|
260
|
+
|
|
261
|
+
def get_default_resource_value(res_id)
|
|
262
|
+
if res_id.is_a? String
|
|
263
|
+
res_id = res_id.hex
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
entries = get_resource_value(res_id)
|
|
267
|
+
if entries != nil
|
|
268
|
+
default = ResTypeConfig.new(0, 0, 0, 0, 0, 0, 0, 0)
|
|
269
|
+
default_entry = entries[default]
|
|
270
|
+
return default_entry
|
|
271
|
+
else
|
|
272
|
+
return nil
|
|
273
|
+
end
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
##
|
|
277
|
+
# Obtain the value(s) for a given resource id.
|
|
278
|
+
# A default resource is one defined in an unqualified directory.
|
|
279
|
+
#
|
|
280
|
+
# res_id: ID value of a resource as a FixNum or String representation (i.e. 0x7F060001)
|
|
281
|
+
#
|
|
282
|
+
# Returns: Hash of all entries matching this id, keyed by their matching ResTypeConfig
|
|
283
|
+
# or nil if the resource id cannot be found.
|
|
284
|
+
|
|
285
|
+
def get_resource_value(res_id)
|
|
286
|
+
if res_id.is_a? String
|
|
287
|
+
res_id = res_id.hex
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
# R.id integers are a concatenation of package_id, type_id, and entry index
|
|
291
|
+
res_package = (res_id >> 24) & 0xFF
|
|
292
|
+
res_type = (res_id >> 16) & 0xFF
|
|
293
|
+
res_index = res_id & 0xFFFF
|
|
294
|
+
|
|
295
|
+
package_element = @packages[res_package]
|
|
296
|
+
if package_element == nil
|
|
297
|
+
# This is not a resource we can parse
|
|
298
|
+
return nil
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
res_spec = package_element.type_data[res_type-1]
|
|
302
|
+
if res_spec == nil
|
|
303
|
+
puts "Could not find ResTypeSpec for #{res_package} #{res_type}" if DEBUG
|
|
304
|
+
return nil
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
entries = res_spec.types.entries[res_index]
|
|
308
|
+
if entries == nil
|
|
309
|
+
puts "Could not find #{res_spec.types.id} ResType chunk" if DEBUG
|
|
310
|
+
return nil
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
return entries
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
private # Private Helper Methods
|
|
317
|
+
|
|
318
|
+
# Type Constants
|
|
319
|
+
TYPENAME_ARRAY = "array" # :nodoc:
|
|
320
|
+
TYPENAME_ATTRIBUTE = "attr" # :nodoc:
|
|
321
|
+
TYPENAME_BOOLEAN = "bool" # :nodoc:
|
|
322
|
+
TYPENAME_COLOR = "color" # :nodoc:
|
|
323
|
+
TYPENAME_DIMENSION = "dimen" # :nodoc:
|
|
324
|
+
TYPENAME_DRAWABLE = "drawable" # :nodoc:
|
|
325
|
+
TYPENAME_FRACTION = "fraction" # :nodoc:
|
|
326
|
+
TYPENAME_INTEGER = "integer" # :nodoc:
|
|
327
|
+
TYPENAME_LAYOUT = "layout" # :nodoc:
|
|
328
|
+
TYPENAME_PLURALS = "plurals" # :nodoc:
|
|
329
|
+
TYPENAME_STRING = "string" # :nodoc:
|
|
330
|
+
TYPENAME_STYLE = "style" # :nodoc:
|
|
331
|
+
|
|
332
|
+
# Data Type Constants (mirrors ResourceTypes.h)
|
|
333
|
+
TYPE_NULL = 0x0 # :nodoc:
|
|
334
|
+
TYPE_REFERENCE = 0x1 # :nodoc:
|
|
335
|
+
TYPE_ATTRIBUTE = 0x2 # :nodoc:
|
|
336
|
+
TYPE_STRING = 0x3 # :nodoc:
|
|
337
|
+
TYPE_FLOAT = 0x4 # :nodoc:
|
|
338
|
+
TYPE_DIMENSION = 0x5 # :nodoc:
|
|
339
|
+
TYPE_FRACTION = 0x6 # :nodoc:
|
|
340
|
+
TYPE_DYNAMIC_DIMEN = 0x7 # :nodoc:
|
|
341
|
+
TYPE_INT_DEC = 0x10 # :nodoc:
|
|
342
|
+
TYPE_INT_HEX = 0x11 # :nodoc:
|
|
343
|
+
TYPE_BOOL = 0x12 # :nodoc:
|
|
344
|
+
|
|
345
|
+
TYPE_INT_COLOR_ARGB8 = 0x1C # :nodoc:
|
|
346
|
+
TYPE_INT_COLOR_RGB8 = 0x1D # :nodoc:
|
|
347
|
+
TYPE_INT_COLOR_ARGB4 = 0x1E # :nodoc:
|
|
348
|
+
TYPE_INT_COLOR_RGB4 = 0x1F # :nodoc:
|
|
349
|
+
|
|
350
|
+
COMPLEX_UNIT_PX = 0x0 # :nodoc:
|
|
351
|
+
COMPLEX_UNIT_DIP = 0x1 # :nodoc:
|
|
352
|
+
COMPLEX_UNIT_SP = 0x2 # :nodoc:
|
|
353
|
+
COMPLEX_UNIT_PT = 0x3 # :nodoc:
|
|
354
|
+
COMPLEX_UNIT_IN = 0x4 # :nodoc:
|
|
355
|
+
COMPLEX_UNIT_MM = 0x5 # :nodoc:
|
|
356
|
+
|
|
357
|
+
COMPLEX_UNIT_FRACTION = 0x0 # :nodoc:
|
|
358
|
+
COMPLEX_UNIT_FRACTION_PARENT = 0x1 # :nodoc:
|
|
359
|
+
|
|
360
|
+
# Data Constants
|
|
361
|
+
TYPE_BOOL_TRUE = 0xFFFFFFFF # :nodoc:
|
|
362
|
+
TYPE_BOOL_FALSE = 0x00000000 # :nodoc:
|
|
363
|
+
|
|
364
|
+
# Header Constants
|
|
365
|
+
CHUNKTYPE_TYPESPEC = 0x202 # :nodoc:
|
|
366
|
+
CHUNKTYPE_TYPE = 0x201 # :nodoc:
|
|
367
|
+
CHUNKTYPE_PACKAGE = 0x200 # :nodoc:
|
|
368
|
+
|
|
369
|
+
#Flag Constants
|
|
370
|
+
FLAG_UTF8 = 0x100 # :nodoc:
|
|
371
|
+
FLAG_COMPLEX = 0x0001 # :nodoc:
|
|
372
|
+
FLAG_PUBLIC = 0x0002 # :nodoc:
|
|
373
|
+
|
|
374
|
+
OFFSET_NO_ENTRY = 0xFFFFFFFF # :nodoc:
|
|
375
|
+
HEADER_START = 0 # :nodoc:
|
|
376
|
+
|
|
377
|
+
# Read a 32-bit word from a specific location in the data
|
|
378
|
+
def read_word(data, offset)
|
|
379
|
+
out = data[offset,4].unpack('V').first rescue 0
|
|
380
|
+
return out
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
# Read a 16-bit short from a specific location in the data
|
|
384
|
+
def read_short(data, offset)
|
|
385
|
+
out = data[offset,2].unpack('v').first rescue 0
|
|
386
|
+
return out
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
# Read a 8-bit byte from a specific location in the data
|
|
390
|
+
def read_byte(data, offset)
|
|
391
|
+
out = data[offset,1].unpack('C').first rescue 0
|
|
392
|
+
return out
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
# Read in length bytes in as a String
|
|
396
|
+
def read_string(data, offset, length, encoding)
|
|
397
|
+
if "UTF-16".casecmp(encoding) == 0
|
|
398
|
+
out = data[offset, length].unpack('v*').pack('U*')
|
|
399
|
+
else
|
|
400
|
+
out = data[offset, length].unpack('C*').pack('U*')
|
|
401
|
+
end
|
|
402
|
+
return out
|
|
403
|
+
end
|
|
404
|
+
|
|
405
|
+
# Return id as a hex string
|
|
406
|
+
def res_id_to_s(res_id)
|
|
407
|
+
return "0x#{res_id.to_s(16)}"
|
|
408
|
+
end
|
|
409
|
+
|
|
410
|
+
# Parse out a StringPool chunk
|
|
411
|
+
def parse_stringpool(data, offset)
|
|
412
|
+
pool_header = ChunkHeader.new( read_short(data, offset),
|
|
413
|
+
read_short(data, offset+2),
|
|
414
|
+
read_word(data, offset+4) )
|
|
415
|
+
|
|
416
|
+
pool_string_count = read_word(data, offset+8)
|
|
417
|
+
pool_style_count = read_word(data, offset+12)
|
|
418
|
+
pool_flags = read_word(data, offset+16)
|
|
419
|
+
format_utf8 = (pool_flags & FLAG_UTF8) != 0
|
|
420
|
+
puts 'StringPool format is %s' % [format_utf8 ? "UTF-8" : "UTF-16"] if DEBUG
|
|
421
|
+
|
|
422
|
+
pool_string_offset = read_word(data, offset+20)
|
|
423
|
+
pool_style_offset = read_word(data, offset+24)
|
|
424
|
+
|
|
425
|
+
values = Array.new()
|
|
426
|
+
i = 0
|
|
427
|
+
while i < pool_string_count
|
|
428
|
+
# Read the string value
|
|
429
|
+
index = i * 4 + (offset+28)
|
|
430
|
+
offset_addr = pool_string_offset + offset + read_word(data, index)
|
|
431
|
+
if format_utf8
|
|
432
|
+
length = read_byte(data, offset_addr)
|
|
433
|
+
if (length & 0x80) != 0
|
|
434
|
+
length = ((length & 0x7F) << 8) + read_byte(data, offset_addr+1)
|
|
435
|
+
end
|
|
436
|
+
|
|
437
|
+
values << read_string(data, offset_addr + 2, length, "UTF-8")
|
|
438
|
+
else
|
|
439
|
+
length = read_short(data, offset_addr)
|
|
440
|
+
if (length & 0x8000) != 0
|
|
441
|
+
#There is one more length value before the data
|
|
442
|
+
length = ((length & 0x7FFF) << 16) + read_short(data, offset_addr+2)
|
|
443
|
+
values << read_string(data, offset_addr + 4, length * 2, "UTF-16")
|
|
444
|
+
else
|
|
445
|
+
# Read the data
|
|
446
|
+
values << read_string(data, offset_addr + 2, length * 2, "UTF-16")
|
|
447
|
+
end
|
|
448
|
+
end
|
|
449
|
+
|
|
450
|
+
i += 1
|
|
451
|
+
end
|
|
452
|
+
|
|
453
|
+
return StringPool.new(pool_header, pool_string_count, pool_style_count, values)
|
|
454
|
+
end
|
|
455
|
+
|
|
456
|
+
# Parse out a Package Chunk
|
|
457
|
+
def parse_package(data, offset)
|
|
458
|
+
header = ChunkHeader.new( read_short(data, offset),
|
|
459
|
+
read_short(data, offset+2),
|
|
460
|
+
read_word(data, offset+4) )
|
|
461
|
+
|
|
462
|
+
package_id = read_word(data, offset+8)
|
|
463
|
+
package_name = read_string(data, offset+12, 256, "UTF-8")
|
|
464
|
+
package_type_strings = read_word(data, offset+268)
|
|
465
|
+
package_last_type = read_word(data, offset+272)
|
|
466
|
+
package_key_strings = read_word(data, offset+276)
|
|
467
|
+
package_last_key = read_word(data, offset+280)
|
|
468
|
+
|
|
469
|
+
package_header = PackageHeader.new(header, package_id, package_name, package_type_strings, package_key_strings)
|
|
470
|
+
|
|
471
|
+
## typeStrings StringPool
|
|
472
|
+
startoffset_typestrings = offset + package_type_strings
|
|
473
|
+
puts "Parse typeStrings StringPool Chunk" if DEBUG
|
|
474
|
+
stringpool_typestrings = parse_stringpool(data, startoffset_typestrings)
|
|
475
|
+
|
|
476
|
+
## keyStrings StringPool
|
|
477
|
+
startoffset_keystrings = offset + package_key_strings
|
|
478
|
+
puts "Parse keyStrings StringPool Chunk" if DEBUG
|
|
479
|
+
stringpool_keystrings = parse_stringpool(data, startoffset_keystrings)
|
|
480
|
+
|
|
481
|
+
## typeSpec/type Chunks
|
|
482
|
+
type_data = Array.new()
|
|
483
|
+
current_spec = nil
|
|
484
|
+
|
|
485
|
+
current = startoffset_keystrings + stringpool_keystrings.header.chunk_size
|
|
486
|
+
puts "Parse Type/TypeSpec Chunks" if DEBUG
|
|
487
|
+
while current < data.length
|
|
488
|
+
## Parse Header
|
|
489
|
+
header = ChunkHeader.new( read_short(data, current),
|
|
490
|
+
read_short(data, current+2),
|
|
491
|
+
read_word(data, current+4) )
|
|
492
|
+
## Check Type
|
|
493
|
+
if header.type == CHUNKTYPE_TYPESPEC
|
|
494
|
+
typespec_id = read_byte(data, current+8)
|
|
495
|
+
typespec_entrycount = read_word(data, current+12)
|
|
496
|
+
|
|
497
|
+
## Parse the config flags for each entry
|
|
498
|
+
typespec_entries = Array.new()
|
|
499
|
+
i=0
|
|
500
|
+
while i < typespec_entrycount
|
|
501
|
+
offset = i * 4 + (current+16)
|
|
502
|
+
typespec_entries << read_word(data, offset)
|
|
503
|
+
|
|
504
|
+
i += 1
|
|
505
|
+
end
|
|
506
|
+
|
|
507
|
+
typespec_name = stringpool_typestrings.values[typespec_id - 1]
|
|
508
|
+
current_spec = ResTypeSpec.new(header, typespec_name, typespec_entrycount, typespec_entries, nil)
|
|
509
|
+
|
|
510
|
+
type_data << current_spec
|
|
511
|
+
current += header.chunk_size
|
|
512
|
+
elsif header.type == CHUNKTYPE_TYPE
|
|
513
|
+
type_id = read_byte(data, current+8)
|
|
514
|
+
type_entrycount = read_word(data, current+12)
|
|
515
|
+
type_entryoffset = read_word(data, current+16)
|
|
516
|
+
|
|
517
|
+
## The config flags set for this type chunk
|
|
518
|
+
## TODO: Vary the size of the config structure based on size to accomodate for new flags
|
|
519
|
+
config_start = current+20
|
|
520
|
+
config_size = read_word(data, config_start) # Number of bytes in structure
|
|
521
|
+
type_config = ResTypeConfig.new( read_word(data, config_start+4),
|
|
522
|
+
read_word(data, config_start+8),
|
|
523
|
+
read_word(data, config_start+12),
|
|
524
|
+
read_word(data, config_start+16 ),
|
|
525
|
+
read_word(data, config_start+20),
|
|
526
|
+
read_word(data, config_start+24),
|
|
527
|
+
read_word(data, config_start+28),
|
|
528
|
+
read_word(data, config_start+32) )
|
|
529
|
+
## TODO: This config structure is outdated. Update to latest aapt specs.
|
|
530
|
+
|
|
531
|
+
## The end of the config structure marks the offsets table
|
|
532
|
+
offset_table_start = config_start + config_size
|
|
533
|
+
## The remainder of the chunk is a list of the entry values for that type/configuration
|
|
534
|
+
type_name = stringpool_typestrings.values[type_id - 1]
|
|
535
|
+
if current_spec.types == nil
|
|
536
|
+
current_spec.types = ResType.new(header, type_name, type_config, type_entrycount, Array.new())
|
|
537
|
+
end
|
|
538
|
+
|
|
539
|
+
i=0
|
|
540
|
+
while i < type_entrycount
|
|
541
|
+
## Ensure a hash exists for each type
|
|
542
|
+
if current_spec.types.entries[i] == nil
|
|
543
|
+
current_spec.types.entries[i] = Hash.new()
|
|
544
|
+
end
|
|
545
|
+
current_entry = current_spec.types.entries[i]
|
|
546
|
+
|
|
547
|
+
## Get the start of the type from the offsets table
|
|
548
|
+
index_offset = i * 4 + offset_table_start
|
|
549
|
+
start_offset = read_word(data, index_offset)
|
|
550
|
+
if start_offset != OFFSET_NO_ENTRY
|
|
551
|
+
## Set the index_offset to the start of the current entry
|
|
552
|
+
index_offset = current + type_entryoffset + start_offset
|
|
553
|
+
|
|
554
|
+
entry_flags = read_short(data, index_offset+2)
|
|
555
|
+
entry_key = read_word(data, index_offset+4)
|
|
556
|
+
entry_data_type = read_byte(data, index_offset+11)
|
|
557
|
+
entry_data = read_word(data, index_offset+12)
|
|
558
|
+
|
|
559
|
+
# Find the key in our strings index
|
|
560
|
+
key_name = stringpool_keystrings.values[entry_key]
|
|
561
|
+
# Parse the value into a string
|
|
562
|
+
case entry_data_type
|
|
563
|
+
when TYPE_NULL
|
|
564
|
+
data_value = nil
|
|
565
|
+
when TYPE_REFERENCE
|
|
566
|
+
## TODO: Mark these here, and resolve after package is parsed
|
|
567
|
+
data_value = res_id_to_s(entry_data)
|
|
568
|
+
when TYPE_STRING
|
|
569
|
+
data_value = get_resource_string(entry_data_type, entry_data)
|
|
570
|
+
when TYPE_INT_COLOR_ARGB8..TYPE_INT_COLOR_RGB4
|
|
571
|
+
data_value = get_resource_color(entry_data_type, entry_data)
|
|
572
|
+
when TYPE_DIMENSION
|
|
573
|
+
data_value = get_resource_dimension(entry_data_type, entry_data)
|
|
574
|
+
when TYPE_INT_DEC, TYPE_INT_HEX
|
|
575
|
+
data_value = get_resource_integer(entry_data_type, entry_data)
|
|
576
|
+
when TYPE_BOOL
|
|
577
|
+
data_value = get_resource_bool(entry_data_type, entry_data)
|
|
578
|
+
when TYPE_FLOAT
|
|
579
|
+
data_value = get_resource_float(entry_data_type, entry_data)
|
|
580
|
+
when TYPE_FRACTION
|
|
581
|
+
data_value = get_resource_fraction(entry_data_type, entry_data)
|
|
582
|
+
else
|
|
583
|
+
puts "Complex Resource (%s,%d) not yet supported." % [type_name,entry_data_type] if DEBUG
|
|
584
|
+
data_value = entry_data.to_s
|
|
585
|
+
end
|
|
586
|
+
current_entry[type_config] = ResTypeEntry.new(entry_flags, key_name, entry_data_type, data_value)
|
|
587
|
+
end
|
|
588
|
+
i += 1
|
|
589
|
+
end
|
|
590
|
+
|
|
591
|
+
current += header.chunk_size
|
|
592
|
+
elsif header.type == CHUNKTYPE_PACKAGE
|
|
593
|
+
## This is the next package chunk, move along
|
|
594
|
+
puts "Next Package Chunk Found...Ending" if DEBUG
|
|
595
|
+
current = data.length
|
|
596
|
+
else
|
|
597
|
+
puts "Unknown Chunk Found: #{header.type} #{header.size}" if DEBUG
|
|
598
|
+
## End Immediately
|
|
599
|
+
current = data.length
|
|
600
|
+
end
|
|
601
|
+
end
|
|
602
|
+
|
|
603
|
+
return Package.new(package_header, stringpool_typestrings, stringpool_keystrings, type_data)
|
|
604
|
+
end
|
|
605
|
+
|
|
606
|
+
# Obtain string value for resource id
|
|
607
|
+
def get_resource_string(entry_datatype, entry_data)
|
|
608
|
+
result = @stringpool_main.values[entry_data]
|
|
609
|
+
return result
|
|
610
|
+
end
|
|
611
|
+
|
|
612
|
+
# Obtain boolean value for resource id
|
|
613
|
+
def get_resource_bool(entry_datatype, entry_data)
|
|
614
|
+
if entry_data == TYPE_BOOL_TRUE
|
|
615
|
+
return "true"
|
|
616
|
+
elsif entry_data == TYPE_BOOL_FALSE
|
|
617
|
+
return "false"
|
|
618
|
+
else
|
|
619
|
+
return "undefined"
|
|
620
|
+
end
|
|
621
|
+
end
|
|
622
|
+
|
|
623
|
+
# Obtain integer value for resource id
|
|
624
|
+
def get_resource_integer(entry_datatype, entry_data)
|
|
625
|
+
if entry_datatype == TYPE_INT_HEX
|
|
626
|
+
return "0x#{entry_data.to_s(16)}"
|
|
627
|
+
else
|
|
628
|
+
return entry_data.to_s
|
|
629
|
+
end
|
|
630
|
+
end
|
|
631
|
+
|
|
632
|
+
# Obtain color value for resource id
|
|
633
|
+
def get_resource_color(entry_datatype, entry_data)
|
|
634
|
+
case entry_datatype
|
|
635
|
+
when TYPE_INT_COLOR_RGB4
|
|
636
|
+
return "#" + ((entry_data >> 16) & 0xF).to_s(16) + ((entry_data >> 8) & 0xF).to_s(16) + (entry_data & 0xF).to_s(16)
|
|
637
|
+
when TYPE_INT_COLOR_ARGB4
|
|
638
|
+
return "#" + ((entry_data >> 24) & 0xF).to_s(16) + ((entry_data >> 16) & 0xF).to_s(16) + ((entry_data >> 8) & 0xF).to_s(16) + (entry_data & 0xF).to_s(16)
|
|
639
|
+
when TYPE_INT_COLOR_RGB8
|
|
640
|
+
return "#" + ((entry_data >> 16) & 0xFF).to_s(16) + ((entry_data >> 8) & 0xFF).to_s(16) + (entry_data & 0xFF).to_s(16)
|
|
641
|
+
when TYPE_INT_COLOR_ARGB8
|
|
642
|
+
return "#" + ((entry_data >> 24) & 0xFF).to_s(16) + ((entry_data >> 16) & 0xFF).to_s(16) + ((entry_data >> 8) & 0xFF).to_s(16) + (entry_data & 0xFF).to_s(16)
|
|
643
|
+
else
|
|
644
|
+
return "0x#{entry_data.to_s(16)}"
|
|
645
|
+
end
|
|
646
|
+
end
|
|
647
|
+
|
|
648
|
+
|
|
649
|
+
# Obtain a float value for resource id
|
|
650
|
+
def get_resource_float(entry_data_type, entry_data)
|
|
651
|
+
result = [entry_data].pack('I').unpack('F')
|
|
652
|
+
return result[0].to_s
|
|
653
|
+
end
|
|
654
|
+
|
|
655
|
+
# Obtain dimension value for resource id
|
|
656
|
+
def get_resource_dimension(entry_datatype, entry_data)
|
|
657
|
+
unit_type = (entry_data & 0xF)
|
|
658
|
+
case unit_type
|
|
659
|
+
when COMPLEX_UNIT_PX
|
|
660
|
+
unit_name = "px"
|
|
661
|
+
when COMPLEX_UNIT_DIP
|
|
662
|
+
unit_name = "dp"
|
|
663
|
+
when COMPLEX_UNIT_SP
|
|
664
|
+
unit_name = "sp"
|
|
665
|
+
when COMPLEX_UNIT_PT
|
|
666
|
+
unit_name = "pt"
|
|
667
|
+
when COMPLEX_UNIT_IN
|
|
668
|
+
unit_name = "in"
|
|
669
|
+
when COMPLEX_UNIT_MM
|
|
670
|
+
unit_name = "mm"
|
|
671
|
+
else
|
|
672
|
+
unit_name = ""
|
|
673
|
+
end
|
|
674
|
+
|
|
675
|
+
return complex_to_float(entry_data).to_s + unit_name
|
|
676
|
+
#return ((entry_data >> 8) & 0xFFFFFF).to_s + unit_name
|
|
677
|
+
end
|
|
678
|
+
|
|
679
|
+
# Obtain a fraction value for resource id
|
|
680
|
+
def get_resource_fraction(entry_data_type, entry_data)
|
|
681
|
+
unit_type = (entry_data & 0xF)
|
|
682
|
+
case unit_type
|
|
683
|
+
when COMPLEX_UNIT_FRACTION
|
|
684
|
+
unit_name = "%"
|
|
685
|
+
when COMPLEX_UNIT_FRACTION_PARENT
|
|
686
|
+
unit_name = "%p"
|
|
687
|
+
else
|
|
688
|
+
unit_name = ""
|
|
689
|
+
end
|
|
690
|
+
|
|
691
|
+
# Return float as a percentage
|
|
692
|
+
return (complex_to_float(entry_data) * 100).to_s + unit_name
|
|
693
|
+
end
|
|
694
|
+
|
|
695
|
+
def complex_to_float(complex)
|
|
696
|
+
mantissa_mult = 1.0 / (1 << 8)
|
|
697
|
+
multipliers = [1.0*mantissa_mult, 1.0/(1<<7)*mantissa_mult, 1.0/(1<<15)*mantissa_mult, 1.0/(1<<23)*mantissa_mult]
|
|
698
|
+
|
|
699
|
+
mantissa = complex & 0xFFFFFF00
|
|
700
|
+
radix = (complex >> 4) & 0x3
|
|
701
|
+
|
|
702
|
+
return (mantissa * multipliers[radix]).to_f.round(4)
|
|
703
|
+
end
|
|
704
|
+
end
|