Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
ruby_apk Android::Resource can relate string pool (partially)
# 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
You can’t perform that action at this time.