Created
December 23, 2012 19:24
-
-
Save cielavenir/4365521 to your computer and use it in GitHub Desktop.
ruby_apk Android::Resource can relate string pool (partially)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# encoding: utf-8 | |
require 'stringio' | |
require 'csv' | |
require 'ruby_apk' | |
module Android | |
# based on Android OS source code | |
# /frameworks/base/include/utils/ResourceTypes.h | |
class Resource | |
class ChunkHeader | |
attr_reader :type, :header_size, :size | |
def initialize(data, offset) | |
@data = data | |
@offset = offset | |
@data_io = StringIO.new(@data, 'rb') | |
@data_io.seek(offset) | |
parse | |
end | |
private | |
def parse | |
@type = read_int16 | |
@header_size = read_int16 | |
@size = read_int32 | |
end | |
def read_int32 | |
@data_io.read(4).unpack('V')[0] | |
end | |
def read_int16 | |
@data_io.read(2).unpack('v')[0] | |
end | |
end | |
class ResTableHeader < ChunkHeader | |
attr_reader :package_count | |
def parse | |
super | |
@package_count = read_int32 | |
end | |
end | |
class ResStringPool < ChunkHeader | |
SORTED_FLAG = 1 << 0 | |
UTF8_FLAG = 1 << 8 | |
attr_reader :strings | |
private | |
def parse | |
super | |
@string_count = read_int32 | |
@style_count = read_int32 | |
@flags = read_int32 | |
@string_start = read_int32 | |
@style_start = read_int32 | |
@strings = [] | |
@string_count.times do | |
offset = @offset + @string_start + read_int32 | |
if (@flags & UTF8_FLAG != 0) | |
# read length twice(utf16 length and utf8 length) | |
# const uint16_t* ResStringPool::stringAt(size_t idx, size_t* u16len) const | |
u16len, o16 = ResStringPool.utf8_len(@data[offset, 2]) | |
u8len, o8 = ResStringPool.utf8_len(@data[offset+o16, 2]) | |
str = @data[offset+o16+o8, u8len] | |
@strings << str.force_encoding(Encoding::UTF_8) | |
else | |
u16len, o16 = ResStringPool.utf16_len(@data[offset, 4]) | |
str = @data[offset+o16, u16len*2] | |
str.force_encoding(Encoding::UTF_16LE) | |
@strings << str.encode(Encoding::UTF_8) | |
end | |
end | |
end | |
# @note refer to /frameworks/base/libs/androidfw/ResourceTypes.cpp | |
# static inline size_t decodeLength(const uint8_t** str) | |
# @param [String] data parse target | |
# @return[Integer, Integer] string length and parsed length | |
def self.utf8_len(data) | |
first, second = data.unpack('CC') | |
if (first & 0x80) != 0 | |
return (((first & 0x7F) << 8) + second), 2 | |
else | |
return first, 1 | |
end | |
end | |
# @note refer to /frameworks/base/libs/androidfw/ResourceTypes.cpp | |
# static inline size_t decodeLength(const char16_t** str) | |
# @param [String] data parse target | |
# @return[Integer, Integer] string length and parsed length | |
def self.utf16_len(data) | |
first, second = data.unpack('vv') | |
if (first & 0x8000) != 0 | |
return (((first & 0x7FFF) << 16) + second), 4 | |
else | |
return first, 2 | |
end | |
end | |
end | |
class ResTablePackage < ChunkHeader | |
attr_reader :package_name, :typeStrings, :keyStrings | |
def parse | |
super | |
@id = read_int32 | |
str = @data[@offset+12, 256] | |
str.force_encoding(Encoding::UTF_16LE) | |
@package_name = str.encode(Encoding::UTF_8).strip | |
typeStrings_offset=@data[@offset+268,4].unpack('V')[0] | |
typeStrings=ResStringPool.new(@data,@offset+typeStrings_offset) | |
@typeStrings=typeStrings.strings | |
keyStrings_offset=@data[@offset+276,4].unpack('V')[0] | |
keyStrings=ResStringPool.new(@data,@offset+keyStrings_offset) | |
@keyStrings=keyStrings.strings | |
end | |
end | |
class ResTableType < ChunkHeader | |
attr_reader :stringpool_relation | |
def parse | |
super | |
@id = read_int16 | |
@id2 = read_int16 | |
@count = read_int32 | |
@entry_offset = read_int32 | |
read_int32 | |
read_int32 | |
@locale = read_int32 #todo | |
@stringpool_relation={} | |
@count.times{|i| | |
if @data[@offset+@entry_offset+16*i+11,1].ord==3 | |
@stringpool_relation[@data[@offset+@entry_offset+16*i+4,4].unpack('V')[0]]=@data[@offset+@entry_offset+16*i+12,4].unpack('V')[0] | |
end | |
} | |
end | |
end | |
class ResTableTypeSpec < ChunkHeader | |
attr_reader :count | |
def parse | |
super | |
@id = read_int16 | |
@id2 = read_int16 | |
@count = read_int32 | |
end | |
end | |
###################################################################### | |
def initialize(data) | |
data.force_encoding(Encoding::ASCII_8BIT) | |
@data = data | |
@keyStrings_raw = [] | |
@keyStrings = [] | |
@stringpool_relation = [] | |
@string_pool_inside_table_package=false | |
@res_table_type_spec_count = [] | |
@stringpool_relation_ = {} | |
parse() | |
i=0 | |
@res_table_type_spec_count.each{|n| | |
stringpool_pertype = [] | |
keyStrings_pertype = [] | |
n.times{ | |
keyStrings_pertype.push @keyStrings_raw.shift | |
stringpool_pertype.push @stringpool_relation_[i] | |
i+=1 | |
} | |
@keyStrings.push keyStrings_pertype | |
@stringpool_relation.push stringpool_pertype | |
} | |
end | |
def strings | |
@string_pool.strings | |
end | |
def package_count | |
@res_table.package_count | |
end | |
def package_name | |
@res_table_package.package_name | |
end | |
def typeStrings | |
@res_table_package.typeStrings | |
end | |
def keyStrings | |
@keyStrings | |
end | |
def stringpool_relation | |
@stringpool_relation | |
end | |
private | |
def parse | |
offset = 0 | |
while offset < @data.size | |
type = @data[offset, 2].unpack('v')[0] | |
case type | |
when 0x0001 # RES_STRING_POOL_TYPE | |
string_pool = ResStringPool.new(@data, offset) | |
offset += string_pool.size | |
@string_pool=string_pool if !@string_pool_inside_table_package | |
when 0x0002 # RES_TABLE_TYPE | |
@res_table = ResTableHeader.new(@data, offset) | |
offset += @res_table.header_size | |
when 0x0200 # RES_TABLE_PACKAGE_TYPE | |
@res_table_package = ResTablePackage.new(@data, offset) | |
offset += @res_table_package.header_size | |
@keyStrings_raw = @res_table_package.keyStrings | |
@string_pool_inside_table_package=true | |
when 0x0201 # RES_TABLE_TYPE_TYPE | |
res_table_type = ResTableType.new(@data, offset) | |
offset += res_table_type.size | |
res_table_type.stringpool_relation.each{|k,v| | |
@stringpool_relation_[k]=v if !@stringpool_relation_[k] #todo | |
} | |
when 0x0202 # RES_TABLE_TYPE_SPEC_TYPE | |
res_table_type_spec = ResTableTypeSpec.new(@data, offset) | |
offset += res_table_type_spec.size | |
@res_table_type_spec_count.push res_table_type_spec.count | |
else | |
raise "chunk type error: type:%#04x" % type | |
end | |
end | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment