Skip to content

Instantly share code, notes, and snippets.

@cielavenir
Created December 23, 2012 19:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cielavenir/4365521 to your computer and use it in GitHub Desktop.
Save cielavenir/4365521 to your computer and use it in GitHub Desktop.
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