libredex/ApkResources.cpp (1,356 lines of code) (raw):

/* * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #include "ApkResources.h" #include <boost/algorithm/string.hpp> #include <boost/algorithm/string/predicate.hpp> #include <boost/filesystem.hpp> #include <boost/filesystem/operations.hpp> #include <boost/iostreams/device/mapped_file.hpp> #include <boost/optional.hpp> #include <boost/regex.hpp> #include <fcntl.h> #include <map> #include "Macros.h" #if IS_WINDOWS #include "CompatWindows.h" #include <io.h> #include <share.h> #endif #include "androidfw/ResourceTypes.h" #include "androidfw/TypeWrappers.h" #include "utils/ByteOrder.h" #include "utils/Errors.h" #include "utils/Log.h" #include "utils/Serialize.h" #include "utils/String16.h" #include "utils/String8.h" #include "utils/TypeHelpers.h" #include "utils/Visitor.h" #include "Debug.h" #include "DexUtil.h" #include "IOUtil.h" #include "ReadMaybeMapped.h" #include "RedexMappedFile.h" #include "RedexResources.h" #include "Trace.h" #include "WorkQueue.h" // Workaround for inclusion order, when compiling on Windows (#defines NO_ERROR // as 0). #ifdef NO_ERROR #undef NO_ERROR #endif namespace apk { std::string get_string_from_pool(const android::ResStringPool& pool, size_t idx) { size_t u16_len; auto wide_chars = pool.stringAt(idx, &u16_len); android::String16 s16(wide_chars, u16_len); android::String8 string8(s16); return std::string(string8.string()); } // The import of AOSP code that Redex has right now predates // https://cs.android.com/android/_/android/platform/frameworks/base/+/d0f116b619feede0cfdb647157ce5ab4d50a1c46 // which properly returns UTF-8 lengths when reading UTF-8 string data. We have // the bugged version which returns UTF-16 lengths for the UTF-8 data! Find the // correct length by walking back from the pointer (being mindful that it might // take two bytes to encode the length for long lengths). size_t read_utf8_length_from_string_pool_data(const char* s) { uint8_t maybe = *((uint8_t*)s - 2); uint8_t len = *((uint8_t*)s - 1); if ((maybe & 0x80) != 0) { return ((maybe & 0x7F) << 8) | len; } return len; } bool TableParser::visit_global_strings(android::ResStringPool_header* pool) { m_global_pool_header = pool; arsc::StringPoolRefVisitor::visit_global_strings(pool); return true; } bool TableParser::visit_package(android::ResTable_package* package) { m_packages.emplace(package); std::vector<android::ResChunk_header*> headers; m_package_unknown_chunks.emplace(package, std::move(headers)); arsc::StringPoolRefVisitor::visit_package(package); return true; } bool TableParser::visit_key_strings(android::ResTable_package* package, android::ResStringPool_header* pool) { m_package_key_string_headers.emplace(package, pool); arsc::StringPoolRefVisitor::visit_key_strings(package, pool); return true; } bool TableParser::visit_type_strings(android::ResTable_package* package, android::ResStringPool_header* pool) { m_package_type_string_headers.emplace(package, pool); arsc::StringPoolRefVisitor::visit_type_strings(package, pool); return true; } bool TableParser::visit_type_spec(android::ResTable_package* package, android::ResTable_typeSpec* type_spec) { arsc::TypeInfo info{type_spec, {}}; auto search = m_package_types.find(package); if (search == m_package_types.end()) { std::vector<arsc::TypeInfo> infos; infos.emplace_back(info); m_package_types.emplace(package, infos); } else { search->second.emplace_back(info); } arsc::StringPoolRefVisitor::visit_type_spec(package, type_spec); return true; } bool TableParser::visit_type(android::ResTable_package* package, android::ResTable_typeSpec* type_spec, android::ResTable_type* type) { auto& infos = m_package_types.at(package); for (auto& info : infos) { if (info.spec->id == type_spec->id) { info.configs.emplace_back(type); } } arsc::StringPoolRefVisitor::visit_type(package, type_spec, type); return true; } bool TableParser::visit_unknown_chunk(android::ResTable_package* package, android::ResChunk_header* header) { auto& chunks = m_package_unknown_chunks.at(package); chunks.emplace_back(header); return true; } bool TableEntryParser::visit_type_spec(android::ResTable_package* package, android::ResTable_typeSpec* type_spec) { TableParser::visit_type_spec(package, type_spec); auto package_type_id = make_package_type_id(package, type_spec->id); m_types.emplace(package_type_id, type_spec); TypeToEntries map; m_types_to_entries.emplace(package, std::move(map)); std::vector<android::ResTable_type*> vec; m_types_to_configs.emplace(package_type_id, std::move(vec)); return true; } void TableEntryParser::put_entry_data(uint32_t res_id, android::ResTable_package* package, android::ResTable_type* type, arsc::EntryValueData& data) { { auto search = m_res_id_to_entries.find(res_id); if (search == m_res_id_to_entries.end()) { ConfigToEntry c; m_res_id_to_entries.emplace(res_id, std::move(c)); } auto& c = m_res_id_to_entries.at(res_id); c.emplace(&type->config, data); } { auto& map = m_types_to_entries.at(package); auto search = map.find(type); if (search == map.end()) { std::vector<arsc::EntryValueData> vec; map.emplace(type, std::move(vec)); } auto& vec = map.at(type); vec.emplace_back(data); } } bool TableEntryParser::visit_type(android::ResTable_package* package, android::ResTable_typeSpec* type_spec, android::ResTable_type* type) { TableParser::visit_type(package, type_spec, type); auto package_type_id = make_package_type_id(package, type_spec->id); auto& types = m_types_to_configs.at(package_type_id); types.emplace_back(type); android::TypeVariant tv(type); uint16_t entry_id = 0; for (auto it = tv.beginEntries(); it != tv.endEntries(); ++it, ++entry_id) { android::ResTable_entry* entry = const_cast<android::ResTable_entry*>(*it); uint32_t res_id = package_type_id << 16 | entry_id; if (entry == nullptr) { arsc::EntryValueData data(nullptr, 0); put_entry_data(res_id, package, type, data); } else { auto size = arsc::compute_entry_value_length(entry); arsc::EntryValueData data((uint8_t*)entry, size); put_entry_data(res_id, package, type, data); } m_res_id_to_flags.emplace(res_id, arsc::get_spec_flags(type_spec, entry_id)); } return true; } } // namespace apk namespace { void ensure_file_contents(const std::string& file_contents, const std::string& filename) { if (file_contents.empty()) { fprintf(stderr, "Unable to read file: %s\n", filename.data()); throw std::runtime_error("Unable to read file: " + filename); } } /* * Reads an entire file into a std::string. Returns an empty string if * anything went wrong (e.g. file not found). */ std::string read_entire_file(const std::string& filename) { std::ifstream in(filename, std::ios::in | std::ios::binary); std::ostringstream sstr; sstr << in.rdbuf(); redex_assert(!in.bad()); return sstr.str(); } size_t write_serialized_data(const android::Vector<char>& cVec, RedexMappedFile f) { size_t vec_size = cVec.size(); size_t f_size = f.size(); always_assert_log(vec_size <= f_size, "Growing file not supported"); if (vec_size > 0) { memcpy(f.data(), &(cVec[0]), vec_size); } f.file.reset(); // Close the map. #if IS_WINDOWS int fd; auto open_res = _sopen_s(&fd, f.filename.c_str(), _O_BINARY | _O_RDWR, _SH_DENYRW, 0); redex_assert(open_res == 0); auto trunc_res = _chsize_s(fd, vec_size); _close(fd); #else auto trunc_res = truncate(f.filename.c_str(), vec_size); #endif redex_assert(trunc_res == 0); return vec_size > 0 ? vec_size : f_size; } /* * Look for <search_tag> within the descendants of the current node in the XML * tree. */ bool find_nested_tag(const android::String16& search_tag, android::ResXMLTree* parser) { size_t depth{1}; while (depth) { auto type = parser->next(); switch (type) { case android::ResXMLParser::START_TAG: { ++depth; size_t len; android::String16 tag(parser->getElementName(&len)); if (tag == search_tag) { return true; } break; } case android::ResXMLParser::END_TAG: { --depth; break; } case android::ResXMLParser::BAD_DOCUMENT: { not_reached(); } default: { break; } } } return false; } /* * Parse AndroidManifest from buffer, return a list of class names that are * referenced */ ManifestClassInfo extract_classes_from_manifest(const char* data, size_t size) { // Tags android::String16 activity("activity"); android::String16 activity_alias("activity-alias"); android::String16 application("application"); android::String16 provider("provider"); android::String16 receiver("receiver"); android::String16 service("service"); android::String16 instrumentation("instrumentation"); android::String16 intent_filter("intent-filter"); // This is not an unordered_map because String16 doesn't define a hash std::map<android::String16, ComponentTag> string_to_tag{ {activity, ComponentTag::Activity}, {activity_alias, ComponentTag::ActivityAlias}, {provider, ComponentTag::Provider}, {receiver, ComponentTag::Receiver}, {service, ComponentTag::Service}, }; // Attributes android::String16 authorities("authorities"); android::String16 exported("exported"); android::String16 protection_level("protectionLevel"); android::String16 permission("permission"); android::String16 name("name"); android::String16 target_activity("targetActivity"); android::String16 app_component_factory("appComponentFactory"); android::ResXMLTree parser; parser.setTo(data, size); ManifestClassInfo manifest_classes; if (parser.getError() != android::NO_ERROR) { return manifest_classes; } android::ResXMLParser::event_code_t type; do { type = parser.next(); if (type == android::ResXMLParser::START_TAG) { size_t len; android::String16 tag(parser.getElementName(&len)); if (tag == application) { std::string classname = get_string_attribute_value(parser, name); // android:name is an optional attribute for <application> if (!classname.empty()) { manifest_classes.application_classes.emplace( java_names::external_to_internal(classname)); } std::string app_factory_cls = get_string_attribute_value(parser, app_component_factory); if (!app_factory_cls.empty()) { manifest_classes.application_classes.emplace( java_names::external_to_internal(app_factory_cls)); } } else if (tag == instrumentation) { std::string classname = get_string_attribute_value(parser, name); always_assert(classname.size()); manifest_classes.instrumentation_classes.emplace( java_names::external_to_internal(classname)); } else if (string_to_tag.count(tag)) { std::string classname = get_string_attribute_value( parser, tag != activity_alias ? name : target_activity); always_assert(classname.size()); bool has_exported_attribute = has_bool_attribute(parser, exported); android::Res_value ignore_output; bool has_permission_attribute = has_raw_attribute_value(parser, permission, ignore_output); bool has_protection_level_attribute = has_raw_attribute_value(parser, protection_level, ignore_output); bool is_exported = get_bool_attribute_value(parser, exported, /* default_value */ false); BooleanXMLAttribute export_attribute; if (has_exported_attribute) { if (is_exported) { export_attribute = BooleanXMLAttribute::True; } else { export_attribute = BooleanXMLAttribute::False; } } else { export_attribute = BooleanXMLAttribute::Undefined; } std::string permission_attribute; std::string protection_level_attribute; if (has_permission_attribute) { permission_attribute = get_string_attribute_value(parser, permission); } if (has_protection_level_attribute) { protection_level_attribute = get_string_attribute_value(parser, protection_level); } ComponentTagInfo tag_info(string_to_tag.at(tag), java_names::external_to_internal(classname), export_attribute, permission_attribute, protection_level_attribute); if (tag == provider) { std::string text = get_string_attribute_value(parser, authorities); parse_authorities(text, &tag_info.authority_classes); } else { tag_info.has_intent_filters = find_nested_tag(intent_filter, &parser); } manifest_classes.component_tags.emplace_back(tag_info); } } } while (type != android::ResXMLParser::BAD_DOCUMENT && type != android::ResXMLParser::END_DOCUMENT); return manifest_classes; } std::string convert_from_string16(const android::String16& string16) { android::String8 string8(string16); std::string converted(string8.string()); return converted; } } // namespace // Returns the attribute with the given name for the current XML element std::string get_string_attribute_value( const android::ResXMLTree& parser, const android::String16& attribute_name) { const size_t attr_count = parser.getAttributeCount(); for (size_t i = 0; i < attr_count; ++i) { size_t len; android::String16 key(parser.getAttributeName(i, &len)); if (key == attribute_name) { const char16_t* p = parser.getAttributeStringValue(i, &len); if (p != nullptr) { android::String16 target(p, len); std::string converted = convert_from_string16(target); return converted; } } } return std::string(""); } bool has_raw_attribute_value(const android::ResXMLTree& parser, const android::String16& attribute_name, android::Res_value& out_value) { const size_t attr_count = parser.getAttributeCount(); for (size_t i = 0; i < attr_count; ++i) { size_t len; android::String16 key(parser.getAttributeName(i, &len)); if (key == attribute_name) { parser.getAttributeValue(i, &out_value); return true; } } return false; } bool has_bool_attribute(const android::ResXMLTree& parser, const android::String16& attribute_name) { android::Res_value raw_value; if (has_raw_attribute_value(parser, attribute_name, raw_value)) { return raw_value.dataType == android::Res_value::TYPE_INT_BOOLEAN; } return false; } bool get_bool_attribute_value(const android::ResXMLTree& parser, const android::String16& attribute_name, bool default_value) { android::Res_value raw_value; if (has_raw_attribute_value(parser, attribute_name, raw_value)) { if (raw_value.dataType == android::Res_value::TYPE_INT_BOOLEAN) { return static_cast<bool>(raw_value.data); } } return default_value; } int get_int_attribute_or_default_value(const android::ResXMLTree& parser, const android::String16& attribute_name, int32_t default_value) { android::Res_value raw_value; if (has_raw_attribute_value(parser, attribute_name, raw_value)) { if (raw_value.dataType == android::Res_value::TYPE_INT_DEC) { return static_cast<int>(raw_value.data); } } return default_value; } std::vector<std::string> ApkResources::find_res_directories() { return {m_directory + "/res"}; } std::vector<std::string> ApkResources::find_lib_directories() { return {m_directory + "/lib"}; } std::string ApkResources::get_base_assets_dir() { return m_directory + "/assets"; } namespace { bool is_binary_xml(const void* data, size_t size) { if (size < sizeof(android::ResChunk_header)) { return false; } return dtohs(((android::ResChunk_header*)data)->type) == android::RES_XML_TYPE; } std::string read_attribute_name_at_idx(const android::ResXMLTree& parser, size_t idx) { size_t len; auto name_chars = parser.getAttributeName8(idx, &len); if (name_chars != nullptr) { return std::string(name_chars); } else { auto wide_chars = parser.getAttributeName(idx, &len); android::String16 s16(wide_chars, len); auto converted = convert_from_string16(s16); return converted; } } void extract_classes_from_layout( const char* data, size_t size, const std::unordered_set<std::string>& attributes_to_read, std::unordered_set<std::string>* out_classes, std::unordered_multimap<std::string, std::string>* out_attributes) { if (!is_binary_xml(data, size)) { return; } android::ResXMLTree parser; parser.setTo(data, size); android::String16 name("name"); android::String16 klazz("class"); android::String16 target_class("targetClass"); if (parser.getError() != android::NO_ERROR) { return; } std::unordered_map<int, std::string> namespace_prefix_map; android::ResXMLParser::event_code_t type; do { type = parser.next(); if (type == android::ResXMLParser::START_TAG) { size_t len; android::String16 tag(parser.getElementName(&len)); std::string classname = convert_from_string16(tag); if (!strcmp(classname.c_str(), "fragment") || !strcmp(classname.c_str(), "view") || !strcmp(classname.c_str(), "dialog") || !strcmp(classname.c_str(), "activity") || !strcmp(classname.c_str(), "intent")) { classname = get_string_attribute_value(parser, klazz); if (classname.empty()) { classname = get_string_attribute_value(parser, name); } if (classname.empty()) { classname = get_string_attribute_value(parser, target_class); } } std::string converted = std::string("L") + classname + std::string(";"); bool is_classname = converted.find('.') != std::string::npos; if (is_classname) { std::replace(converted.begin(), converted.end(), '.', '/'); out_classes->insert(converted); } if (!attributes_to_read.empty()) { for (size_t i = 0; i < parser.getAttributeCount(); i++) { auto ns_id = parser.getAttributeNamespaceID(i); std::string attr_name = read_attribute_name_at_idx(parser, i); std::string fully_qualified; if (ns_id >= 0) { fully_qualified = namespace_prefix_map[ns_id] + ":" + attr_name; } else { fully_qualified = attr_name; } if (attributes_to_read.count(fully_qualified) != 0) { auto val = parser.getAttributeStringValue(i, &len); if (val != nullptr) { android::String16 s16(val, len); out_attributes->emplace(fully_qualified, convert_from_string16(s16)); } } } } } else if (type == android::ResXMLParser::START_NAMESPACE) { auto id = parser.getNamespaceUriID(); size_t len; auto prefix = parser.getNamespacePrefix(&len); namespace_prefix_map.emplace( id, convert_from_string16(android::String16(prefix, len))); } } while (type != android::ResXMLParser::BAD_DOCUMENT && type != android::ResXMLParser::END_DOCUMENT); } } // namespace void ApkResources::collect_layout_classes_and_attributes_for_file( const std::string& file_path, const std::unordered_set<std::string>& attributes_to_read, std::unordered_set<std::string>* out_classes, std::unordered_multimap<std::string, std::string>* out_attributes) { redex::read_file_with_contents(file_path, [&](const char* data, size_t size) { extract_classes_from_layout(data, size, attributes_to_read, out_classes, out_attributes); }); } boost::optional<int32_t> ApkResources::get_min_sdk() { if (!boost::filesystem::exists(m_manifest)) { return boost::none; } auto file = RedexMappedFile::open(m_manifest); if (file.size() == 0) { fprintf(stderr, "WARNING: Cannot find/read the manifest file %s\n", m_manifest.c_str()); return boost::none; } android::ResXMLTree parser; parser.setTo(file.const_data(), file.size()); if (parser.getError() != android::NO_ERROR) { fprintf(stderr, "WARNING: Failed to parse the manifest file %s\n", m_manifest.c_str()); return boost::none; } const android::String16 uses_sdk("uses-sdk"); const android::String16 min_sdk("minSdkVersion"); android::ResXMLParser::event_code_t event_code; do { event_code = parser.next(); if (event_code == android::ResXMLParser::START_TAG) { size_t outLen; auto el_name = android::String16(parser.getElementName(&outLen)); if (el_name == uses_sdk) { android::Res_value raw_value; if (has_raw_attribute_value(parser, min_sdk, raw_value) && (raw_value.dataType & android::Res_value::TYPE_INT_DEC)) { return boost::optional<int32_t>(static_cast<int32_t>(raw_value.data)); } else { return boost::none; } } } } while ((event_code != android::ResXMLParser::END_DOCUMENT) && (event_code != android::ResXMLParser::BAD_DOCUMENT)); return boost::none; } ManifestClassInfo ApkResources::get_manifest_class_info() { std::string manifest = (boost::filesystem::path(m_directory) / "AndroidManifest.xml").string(); ManifestClassInfo classes; if (boost::filesystem::exists(manifest)) { redex::read_file_with_contents(manifest, [&](const char* data, size_t size) { if (size == 0) { fprintf(stderr, "Unable to read manifest file: %s\n", manifest.c_str()); return; } classes = extract_classes_from_manifest(data, size); }); } return classes; } std::unordered_set<uint32_t> ApkResources::get_xml_reference_attributes( const std::string& filename) { std::unordered_set<uint32_t> result; if (is_raw_resource(filename)) { return result; } auto file = RedexMappedFile::open(filename); android::ResXMLTree parser; parser.setTo(file.const_data(), file.size()); if (parser.getError() != android::NO_ERROR) { throw std::runtime_error("Unable to read file: " + filename); } android::ResXMLParser::event_code_t type; do { type = parser.next(); if (type == android::ResXMLParser::START_TAG) { const size_t attr_count = parser.getAttributeCount(); for (size_t i = 0; i < attr_count; ++i) { if (parser.getAttributeDataType(i) == android::Res_value::TYPE_REFERENCE || parser.getAttributeDataType(i) == android::Res_value::TYPE_ATTRIBUTE) { android::Res_value outValue; parser.getAttributeValue(i, &outValue); if (outValue.data > PACKAGE_RESID_START) { result.emplace(outValue.data); } } } } } while (type != android::ResXMLParser::BAD_DOCUMENT && type != android::ResXMLParser::END_DOCUMENT); return result; } namespace { // Insert string data from the given pool at the given index to the builder. void add_existing_string_to_builder(const android::ResStringPool& string_pool, arsc::ResStringPoolBuilder* builder, size_t idx) { size_t length; if (string_pool.isUTF8()) { auto s = string_pool.string8At(idx, &length); size_t actual_length = apk::read_utf8_length_from_string_pool_data(s); builder->add_string(s, actual_length); } else { auto s = string_pool.stringAt(idx, &length); builder->add_string(s, length); } } } // namespace int ApkResources::replace_in_xml_string_pool( const void* data, const size_t len, const std::map<std::string, std::string>& rename_map, android::Vector<char>* out_data, size_t* out_num_renamed) { const auto chunk_size = sizeof(android::ResChunk_header); const auto pool_header_size = (uint16_t)sizeof(android::ResStringPool_header); // Validate the given bytes. if (len < chunk_size + pool_header_size) { return android::NOT_ENOUGH_DATA; } // Layout XMLs will have a ResChunk_header, followed by ResStringPool // representing each XML tag and attribute string. auto chunk = (android::ResChunk_header*)data; LOG_FATAL_IF(dtohl(chunk->size) != len, "Can't read header size"); auto pool_ptr = (android::ResStringPool_header*)((char*)data + chunk_size); if (dtohs(pool_ptr->header.type) != android::RES_STRING_POOL_TYPE) { return android::BAD_TYPE; } size_t num_replaced = 0; android::ResStringPool pool(pool_ptr, dtohl(pool_ptr->header.size)); // Straight copy of everything after the string pool. android::Vector<char> serialized_nodes; auto start = chunk_size + dtohl(pool_ptr->header.size); auto remaining = len - start; serialized_nodes.resize(remaining); void* start_ptr = ((char*)data) + start; memcpy((void*)&serialized_nodes[0], start_ptr, remaining); // Rewrite the strings auto is_utf8 = pool.isUTF8(); auto flags = is_utf8 ? htodl(android::ResStringPool_header::UTF8_FLAG) : (uint32_t)0; arsc::ResStringPoolBuilder pool_builder(flags); for (size_t i = 0; i < dtohl(pool_ptr->stringCount); i++) { auto existing_str = apk::get_string_from_pool(pool, i); auto replacement = rename_map.find(existing_str); if (replacement == rename_map.end()) { add_existing_string_to_builder(pool, &pool_builder, i); } else { pool_builder.add_string(replacement->second); num_replaced++; } } android::Vector<char> serialized_pool; pool_builder.serialize(&serialized_pool); // Assemble arsc::push_short(android::RES_XML_TYPE, out_data); arsc::push_short(chunk_size, out_data); auto total_size = chunk_size + serialized_nodes.size() + serialized_pool.size(); arsc::push_long(total_size, out_data); out_data->appendVector(serialized_pool); out_data->appendVector(serialized_nodes); *out_num_renamed = num_replaced; return android::OK; } bool ApkResources::rename_classes_in_layout( const std::string& file_path, const std::map<std::string, std::string>& rename_map, size_t* out_num_renamed) { RedexMappedFile f = RedexMappedFile::open(file_path, /* read_only= */ false); size_t len = f.size(); android::Vector<char> serialized; auto status = replace_in_xml_string_pool(f.data(), f.size(), rename_map, &serialized, out_num_renamed); if (*out_num_renamed == 0) { return true; } if (status != android::OK) { return false; } write_serialized_data(serialized, std::move(f)); return true; } std::unordered_set<std::string> ApkResources::find_all_xml_files() { std::string manifest_path = m_directory + "/AndroidManifest.xml"; std::unordered_set<std::string> all_xml_files; all_xml_files.emplace(manifest_path); for (const std::string& path : get_xml_files(m_directory + "/res")) { all_xml_files.emplace(path); } return all_xml_files; } namespace { size_t getHashFromValues(const android::Vector<android::Res_value>& values) { size_t hash = 0; for (size_t i = 0; i < values.size(); ++i) { boost::hash_combine(hash, values[i].data); } return hash; } } // namespace size_t ApkResources::remap_xml_reference_attributes( const std::string& filename, const std::map<uint32_t, uint32_t>& kept_to_remapped_ids) { if (is_raw_resource(filename)) { return 0; } std::string file_contents = read_entire_file(filename); ensure_file_contents(file_contents, filename); bool made_change = false; android::ResXMLTree parser; parser.setTo(file_contents.data(), file_contents.size()); if (parser.getError() != android::NO_ERROR) { throw std::runtime_error("Unable to read file: " + filename); } // Update embedded resource ID array size_t resIdCount = 0; uint32_t* resourceIds = parser.getResourceIds(&resIdCount); for (size_t i = 0; i < resIdCount; ++i) { auto id_search = kept_to_remapped_ids.find(resourceIds[i]); if (id_search != kept_to_remapped_ids.end()) { resourceIds[i] = id_search->second; made_change = true; } } android::ResXMLParser::event_code_t type; do { type = parser.next(); if (type == android::ResXMLParser::START_TAG) { const size_t attr_count = parser.getAttributeCount(); for (size_t i = 0; i < attr_count; ++i) { if (parser.getAttributeDataType(i) == android::Res_value::TYPE_REFERENCE || parser.getAttributeDataType(i) == android::Res_value::TYPE_ATTRIBUTE) { android::Res_value outValue; parser.getAttributeValue(i, &outValue); if (outValue.data > PACKAGE_RESID_START && kept_to_remapped_ids.count(outValue.data)) { uint32_t new_value = kept_to_remapped_ids.at(outValue.data); if (new_value != outValue.data) { parser.setAttributeData(i, new_value); made_change = true; } } } } } } while (type != android::ResXMLParser::BAD_DOCUMENT && type != android::ResXMLParser::END_DOCUMENT); if (made_change) { write_string_to_file(filename, file_contents); } return made_change; } std::unique_ptr<ResourceTableFile> ApkResources::load_res_table() { std::string arsc_path = m_directory + std::string("/resources.arsc"); return std::make_unique<ResourcesArscFile>(arsc_path); } std::vector<std::string> ApkResources::find_resources_files() { return {m_directory + std::string("/resources.arsc")}; } ApkResources::~ApkResources() {} void ResourcesArscFile::remap_res_ids_and_serialize( const std::vector<std::string>& /* resource_files */, const std::map<uint32_t, uint32_t>& old_to_new) { remap_ids(old_to_new); serialize(); } namespace { // Parses the global string pool for the resources.arsc file. Stores a lookup // from string to the index in the pool. "Global string pool" in this case will // refer to all string values, file paths, and styles (that is, HTML tags) used // throughout the resource table itself. // // The global string pool however does NOT include: // 1) Names of resource types, i.e. "anim", "color", "drawable", "mipmap" etc. // 2) The keys of resource items, i.e. "app_name" for usage "@string/app_name". // 3) Any string used in an XML document like the manifest or layout. That will // be held in the XML document's own string pool. class GlobalStringPoolReader : public arsc::ResourceTableVisitor { public: ~GlobalStringPoolReader() override {} bool visit_global_strings(android::ResStringPool_header* header) override { always_assert_log( m_global_strings->setTo(header, dtohl(header->header.size)) == android::NO_ERROR, "Failed to parse global strings!"); auto size = m_global_strings->size(); for (uint32_t i = 0; i < size; i++) { auto value = apk::get_string_from_pool(*m_global_strings, i); m_string_to_idx.emplace(value, i); TRACE(RES, 9, "GLOBAL STRING [%u] = %s", i, value.c_str()); } return false; // Don't parse anything else } uint32_t get_string_idx(const std::string& s) { return m_string_to_idx.at(s); } std::shared_ptr<android::ResStringPool> global_strings() { return m_global_strings; } private: std::shared_ptr<android::ResStringPool> m_global_strings = std::make_shared<android::ResStringPool>(); std::unordered_map<std::string, uint32_t> m_string_to_idx; }; class StringPoolRefRemappingVisitor : public arsc::StringPoolRefVisitor { public: ~StringPoolRefRemappingVisitor() override {} explicit StringPoolRefRemappingVisitor( const std::unordered_map<uint32_t, uint32_t>& old_to_new) : m_old_to_new(old_to_new) {} void remap_impl(const uint32_t& idx, const std::function<void(const uint32_t&)>& setter) { auto search = m_old_to_new.find(idx); if (search != m_old_to_new.end()) { auto new_value = search->second; TRACE(RES, 9, "REMAP IDX %u -> %u", idx, new_value); setter(htodl(new_value)); } } bool visit_global_strings_ref(android::Res_value* value) override { TRACE(RES, 9, "visit string Res_value, offset = %ld", get_file_offset(value)); remap_impl(dtohl(value->data), [&value](const uint32_t new_value) { value->data = new_value; }); return true; } private: const std::unordered_map<uint32_t, uint32_t>& m_old_to_new; }; // Collects string references into the global string pool from values and styles // and per package, the entries into the key string pool. class PackageStringRefCollector : public apk::TableParser { public: ~PackageStringRefCollector() override {} bool visit_package(android::ResTable_package* package) override { std::set<android::ResStringPool_ref*> entries; m_package_entries.emplace(package, std::move(entries)); std::shared_ptr<android::ResStringPool> key_strings = std::make_shared<android::ResStringPool>(); m_package_key_strings.emplace(package, std::move(key_strings)); apk::TableParser::visit_package(package); return true; } bool visit_key_strings(android::ResTable_package* package, android::ResStringPool_header* pool) override { auto& key_strings = m_package_key_strings.at(package); always_assert_log(key_strings->getError() == android::NO_INIT, "Key strings re-init!"); always_assert_log(key_strings->setTo(pool, dtohl(pool->header.size)) == android::NO_ERROR, "Failed to parse key strings!"); apk::TableParser::visit_key_strings(package, pool); return true; } bool visit_global_strings_ref(android::Res_value* value) override { m_values.emplace(value); return true; } bool visit_global_strings_ref(android::ResStringPool_ref* value) override { m_span_refs.emplace(value); return true; } bool visit_key_strings_ref(android::ResTable_package* package, android::ResStringPool_ref* value) override { auto& entry_set = m_package_entries.at(package); entry_set.emplace(value); return true; } // Values that are references into the global string pool. std::set<android::Res_value*> m_values; // References into the global string pool from a ResStringPool_span. std::set<android::ResStringPool_ref*> m_span_refs; // References into the key string pool from entries; std::map<android::ResTable_package*, std::set<android::ResStringPool_ref*>> m_package_entries; std::map<android::ResTable_package*, std::shared_ptr<android::ResStringPool>> m_package_key_strings; }; } // namespace void ResourcesArscFile::remap_file_paths_and_serialize( const std::vector<std::string>& /* resource_files */, const std::unordered_map<std::string, std::string>& old_to_new) { TRACE(RES, 9, "BEGIN GlobalStringPoolReader"); GlobalStringPoolReader string_reader; string_reader.visit(m_f.data(), m_arsc_len); std::unordered_map<uint32_t, uint32_t> old_to_new_idx; for (auto& pair : old_to_new) { old_to_new_idx.emplace(string_reader.get_string_idx(pair.first), string_reader.get_string_idx(pair.second)); } TRACE(RES, 9, "BEGIN StringPoolRefRemappingVisitor"); StringPoolRefRemappingVisitor remapper(old_to_new_idx); // Note: file is opened for writing. Visitor will in place change the data // (without altering any data sizes). remapper.visit(m_f.data(), m_arsc_len); } namespace { // Copy an individual index from the pool to the builder. API here is weird due // to this largely being identical for UTF-8 pools and UTF-16 pools, except for // the data type and the API call to get the character pointer. template <typename CharType> void add_string_idx_to_builder( const android::ResStringPool& string_pool, size_t idx, const CharType* s, size_t len, const std::function<void(android::ResStringPool_span*)>& span_remapper, arsc::ResStringPoolBuilder* builder) { if (idx < string_pool.styleCount()) { arsc::SpanVector vec; arsc::collect_spans((android::ResStringPool_span*)string_pool.styleAt(idx), &vec); for (auto& span : vec) { span_remapper(span); } builder->add_style(s, len, vec); } else { builder->add_string(s, len); } } // Copies the string data for the kept indicies from the given pool to the // builder. If needed, a remapper function can be run against the spans required // by a kept index. void rebuild_string_pool( const android::ResStringPool& string_pool, const std::unordered_map<uint32_t, uint32_t>& kept_old_to_new, const std::function<void(android::ResStringPool_span*)>& span_remapper, arsc::ResStringPoolBuilder* builder) { const auto original_string_count = string_pool.size(); const auto is_utf8 = string_pool.isUTF8(); for (size_t idx = 0; idx < original_string_count; idx++) { if (kept_old_to_new.count(idx) == 0) { continue; } size_t length; if (is_utf8) { auto s = string_pool.string8At(idx, &length); size_t actual_length = apk::read_utf8_length_from_string_pool_data(s); add_string_idx_to_builder<char>(string_pool, idx, s, actual_length, span_remapper, builder); } else { auto s = string_pool.stringAt(idx, &length); add_string_idx_to_builder<char16_t>(string_pool, idx, s, length, span_remapper, builder); } } } // Given the kept strings, build the mapping from old -> new in the projected // new string pool. void project_string_mapping( const std::unordered_set<uint32_t>& used_strings, const size_t& string_count, std::unordered_map<uint32_t, uint32_t>* kept_old_to_new) { for (size_t i = 0; i < string_count; i++) { if (used_strings.count(i) > 0) { auto new_index = kept_old_to_new->size(); TRACE(RES, 9, "MAPPING %zu => %zu", i, new_index); kept_old_to_new->emplace(i, new_index); } } } #define POOL_FLAGS(pool) \ (((pool)->isUTF8() ? android::ResStringPool_header::UTF8_FLAG : 0) | \ ((pool)->isSorted() ? android::ResStringPool_header::SORTED_FLAG : 0)) void rebuild_type_strings(const uint32_t& package_id, const android::ResStringPool& string_pool, const std::vector<apk::TypeDefinition>& added_types, arsc::ResStringPoolBuilder* builder) { always_assert_log(string_pool.styleCount() == 0, "type strings should not have styles"); const auto original_string_count = string_pool.size(); for (size_t idx = 0; idx < original_string_count; idx++) { add_existing_string_to_builder(string_pool, builder, idx); } for (auto& type_def : added_types) { if (type_def.package_id != package_id) { continue; } builder->add_string(type_def.name); } } } // namespace void ResourcesArscFile::remove_unreferenced_strings() { // Find the global string pool and read its settings. GlobalStringPoolReader string_reader; string_reader.visit(m_f.data(), m_arsc_len); auto string_pool = string_reader.global_strings(); TRACE(RES, 9, "Global string pool has %zu styles and %zu total strings", string_pool->styleCount(), string_pool->size()); auto is_utf8 = string_pool->isUTF8(); auto is_sorted = string_pool->isSorted(); auto flags = (is_utf8 ? android::ResStringPool_header::UTF8_FLAG : 0) | (is_sorted ? android::ResStringPool_header::SORTED_FLAG : 0); // 1) Collect all referenced global string indicies and key string indicies. PackageStringRefCollector collector; collector.visit(m_f.data(), m_arsc_len); std::unordered_set<uint32_t> used_global_strings; for (const auto& value : collector.m_values) { used_global_strings.emplace(dtohl(value->data)); } for (const auto& value : collector.m_span_refs) { used_global_strings.emplace(dtohl(value->index)); } // 2) Build the compacted map of old -> new indicies for used global strings. std::unordered_map<uint32_t, uint32_t> global_old_to_new; project_string_mapping(used_global_strings, string_pool->size(), &global_old_to_new); // 3) Remap all Res_value structs auto remap_value = [&global_old_to_new](android::Res_value* value) { always_assert_log(value->dataType == android::Res_value::TYPE_STRING, "Wrong data type for string remapping"); auto old = dtohl(value->data); TRACE(RES, 9, "REMAP OLD %u", old); auto remapped_data = global_old_to_new.at(old); value->data = htodl(remapped_data); }; for (const auto& value : collector.m_values) { remap_value(value); } // 4) Actually build the new global ResStringPool. While doing this, remap all // span refs encountered (in case ResStringPool has copied its underlying // data). std::unordered_set<android::ResStringPool_span*> remapped_spans; auto remap_spans = [&global_old_to_new, &remapped_spans](android::ResStringPool_span* span) { // Guard against span offsets that have been "canonicalized" if (remapped_spans.count(span) == 0) { remapped_spans.emplace(span); auto old = dtohl(span->name.index); TRACE(RES, 9, "REMAP OLD %u", old); span->name.index = htodl(global_old_to_new.at(old)); } }; std::shared_ptr<arsc::ResStringPoolBuilder> global_strings_builder = std::make_shared<arsc::ResStringPoolBuilder>(flags); rebuild_string_pool(*string_pool, global_old_to_new, remap_spans, global_strings_builder.get()); // 4) Serialize the ResTable with the modified ResStringPool (which will have // a different size). arsc::ResTableBuilder table_builder; table_builder.set_global_strings(global_strings_builder); for (auto& package_entries : collector.m_package_entries) { // 5) Do a similar remapping as above, but for key strings. // // TODO: also do a similar step for type strings pool, and any empty type // chunks that had all their entries deleted. // auto& package = package_entries.first; std::shared_ptr<arsc::ResPackageBuilder> package_builder = std::make_shared<arsc::ResPackageBuilder>(package); // Build new key string pool indicies. auto refs = package_entries.second; auto key_string_pool = collector.m_package_key_strings.at(package); std::unordered_set<uint32_t> used_key_strings; for (auto& ref : refs) { used_key_strings.emplace(dtohl(ref->index)); } std::unordered_map<uint32_t, uint32_t> key_old_to_new; project_string_mapping(used_key_strings, key_string_pool->size(), &key_old_to_new); // Remap the entries. for (auto& ref : refs) { auto old = dtohl(ref->index); TRACE(RES, 9, "REMAP OLD KEY %u", old); ref->index = htodl(key_old_to_new.at(old)); } // Actually build the key strings pool. std::shared_ptr<arsc::ResStringPoolBuilder> key_strings_builder = std::make_shared<arsc::ResStringPoolBuilder>( POOL_FLAGS(key_string_pool)); rebuild_string_pool( *key_string_pool, key_old_to_new, [](android::ResStringPool_span*) {}, key_strings_builder.get()); package_builder->set_key_strings(key_strings_builder); package_builder->set_type_strings( collector.m_package_type_string_headers.at(package)); // Copy over all existing type data, which has been remapped by the step // above. auto search = collector.m_package_types.find(package); if (search != collector.m_package_types.end()) { auto types = search->second; for (auto& info : types) { package_builder->add_type(info); } } // Finally, preserve any chunks that we are not parsing. auto& unknown_chunks = collector.m_package_unknown_chunks.at(package); for (auto& header : unknown_chunks) { package_builder->add_chunk(header); } table_builder.add_package(package_builder); } android::Vector<char> serialized; table_builder.serialize(&serialized); // 6) Actually write the table to disk so changes take effect. TRACE(RES, 9, "Writing resources.arsc file, total size = %zu", serialized.size()); // NOTE: ResourcesArscFile now has two ways to read/manipulate the underlying // data. This is not good, but a necessary intermediate step while we work on // moving away from the forked methods in ResTable class to stand alone APIs. // Eventually, we should stop constructing ResTable instance automatically in // the constructor so we don't have to worry about it having invalid // underlying data as a result of this call. m_arsc_len = write_serialized_data(serialized, std::move(m_f)); m_file_closed = true; } namespace { const std::array<std::string, 2> KNOWN_RES_DIRS = { std::string(RES_DIRECTORY) + "/", std::string(OBFUSCATED_RES_DIRECTORY) + "/"}; // Checks relative paths that start with known resource file directories // (supporting obfuscation). bool is_resource_file(const std::string& str) { for (const auto& dir : KNOWN_RES_DIRS) { if (boost::algorithm::starts_with(str, dir)) { return true; } } return false; } } // namespace std::vector<std::string> ResourcesArscFile::get_files_by_rid( uint32_t res_id, ResourcePathType /* unused */) { std::vector<std::string> ret; android::Vector<android::Res_value> out_values; res_table.getAllValuesForResource(res_id, out_values); for (size_t i = 0; i < out_values.size(); i++) { auto val = out_values[i]; if (val.dataType == android::Res_value::TYPE_STRING) { // data is an index into string pool. auto file_path = res_table.getString8FromIndex(0, val.data); auto file_chars = file_path.string(); if (file_chars != nullptr) { auto file_str = std::string(file_chars); if (is_resource_file(file_str)) { ret.emplace_back(file_str); } } } } return ret; } void ResourcesArscFile::walk_references_for_resource( uint32_t resID, ResourcePathType path_type, std::unordered_set<uint32_t>* nodes_visited, std::unordered_set<std::string>* potential_file_paths) { if (nodes_visited->find(resID) != nodes_visited->end()) { return; } nodes_visited->emplace(resID); ssize_t pkg_index = res_table.getResourcePackageIndex(resID); android::Vector<android::Res_value> initial_values; res_table.getAllValuesForResource(resID, initial_values); std::stack<android::Res_value> nodes_to_explore; for (size_t index = 0; index < initial_values.size(); ++index) { nodes_to_explore.push(initial_values[index]); } while (!nodes_to_explore.empty()) { android::Res_value r = nodes_to_explore.top(); nodes_to_explore.pop(); if (r.dataType == android::Res_value::TYPE_STRING) { android::String8 str = res_table.getString8FromIndex(pkg_index, r.data); potential_file_paths->insert(std::string(str.string())); continue; } // Skip any non-references or already visited nodes if ((r.dataType != android::Res_value::TYPE_REFERENCE && r.dataType != android::Res_value::TYPE_ATTRIBUTE) || r.data <= PACKAGE_RESID_START || nodes_visited->find(r.data) != nodes_visited->end()) { continue; } nodes_visited->insert(r.data); android::Vector<android::Res_value> inner_values; res_table.getAllValuesForResource(r.data, inner_values); for (size_t index = 0; index < inner_values.size(); ++index) { nodes_to_explore.push(inner_values[index]); } } } void ResourcesArscFile::delete_resource(uint32_t res_id) { m_ids_to_remove.emplace(res_id); } void ResourcesArscFile::collect_resid_values_and_hashes( const std::vector<uint32_t>& ids, std::map<size_t, std::vector<uint32_t>>* res_by_hash) { tmp_id_to_values.clear(); for (uint32_t id : ids) { android::Vector<android::Res_value> row_values; res_table.getAllValuesForResource(id, row_values); (*res_by_hash)[getHashFromValues(row_values)].push_back(id); tmp_id_to_values[id] = row_values; } } bool ResourcesArscFile::resource_value_identical(uint32_t a_id, uint32_t b_id) { if (tmp_id_to_values[a_id].size() != tmp_id_to_values[b_id].size()) { return false; } return res_table.areResourceValuesIdentical(a_id, b_id); } ResourcesArscFile::ResourcesArscFile(const std::string& path) : m_f(RedexMappedFile::open(path, /* read_only= */ false)) { m_path = path; m_arsc_len = m_f.size(); int error = res_table.add(m_f.const_data(), m_f.size(), /* cookie */ -1, /* copyData*/ true); always_assert_log(error == 0, "Reading arsc failed with error code: %d", error); res_table.getResourceIds(&sorted_res_ids); // Build up maps to/from resource ID's and names for (size_t index = 0; index < sorted_res_ids.size(); ++index) { uint32_t id = sorted_res_ids[index]; android::ResTable::resource_name name; res_table.getResourceName(id, true, &name); if (name.name8 != nullptr) { std::string name_string( android::String8(name.name8, name.nameLen).string()); id_to_name.emplace(id, name_string); name_to_ids[name_string].push_back(id); } } } size_t ResourcesArscFile::get_length() const { return m_arsc_len; } namespace { // For a map keyed on ResTable_config instances, find the first key (if any) // that is equivalent to the given config. template <typename ValueType> android::ResTable_config* find_equivalent_config_key( android::ResTable_config* to_find, const std::map<android::ResTable_config*, ValueType>& map) { for (const auto& pair : map) { if (arsc::are_configs_equivalent(to_find, pair.first)) { return pair.first; } } return nullptr; } } // namespace size_t ResourcesArscFile::serialize() { // Serializing will apply pending deletions. This may greatly alter the // ResTable_typeSpec and ResTable_type structures emitted in the resulting // file. To do this, reparse the chunks, forward them on to ResTableBuilder // and let that class make sense of what is to be omitted/retained in the // serialized output. apk::TableEntryParser table_parser; table_parser.visit(m_f.data(), m_arsc_len); // Re-assemble arsc::ResTableBuilder table_builder; table_builder.set_global_strings(table_parser.m_global_pool_header); for (auto& package : table_parser.m_packages) { auto package_id = dtohl(package->id); auto package_builder = std::make_shared<arsc::ResPackageBuilder>(package); package_builder->set_key_strings( table_parser.m_package_key_string_headers.at(package)); // Append names of any new types auto type_strings_header = table_parser.m_package_type_string_headers.at(package); android::ResStringPool type_strings( type_strings_header, dtohl(type_strings_header->header.size)); auto type_strings_builder = std::make_shared<arsc::ResStringPoolBuilder>(POOL_FLAGS(&type_strings)); rebuild_type_strings(package_id, type_strings, m_added_types, type_strings_builder.get()); package_builder->set_type_strings(type_strings_builder); // Copy existing types auto& type_infos = table_parser.m_package_types.at(package); for (auto& type_info : type_infos) { auto type_builder = std::make_shared<arsc::ResTableTypeProjector>( package_id, type_info.spec, type_info.configs); type_builder->remove_ids(m_ids_to_remove); package_builder->add_type(type_builder); } // Append any new types for (auto& type_def : m_added_types) { // Refer to the re-parsed data at initial step to get full details of the // entries, flags and values for the new type. std::vector<uint32_t> flags; for (auto& id : type_def.source_res_ids) { flags.emplace_back(table_parser.m_res_id_to_flags.at(id)); } auto type_definer = std::make_shared<arsc::ResTableTypeDefiner>( package_id, type_def.type_id, type_def.configs, flags); for (auto& id : type_def.source_res_ids) { auto& config_entries = table_parser.m_res_id_to_entries.at(id); for (auto& config : type_def.configs) { auto key = find_equivalent_config_key(config, config_entries); always_assert_log(key != nullptr, "TypeDefinition %d is misconfigured; no equivalent " "config found in table", type_def.type_id); auto& data = config_entries.at(key); type_definer->add(config, data); } } package_builder->add_type(type_definer); } // Copy unknown chunks that we did not parse auto& unknown_chunks = table_parser.m_package_unknown_chunks.at(package); for (auto& header : unknown_chunks) { package_builder->add_chunk(header); } table_builder.add_package(package_builder); } android::Vector<char> out; table_builder.serialize(&out); m_f.file.reset(); // Close the map. std::ofstream fout(m_path, std::ios::out | std::ios::binary | std::ios::trunc); always_assert_log(fout.is_open(), "Could not open path %s for writing", m_path.c_str()); fout.write(out.array(), out.size()); fout.close(); m_arsc_len = out.size(); m_file_closed = true; return m_arsc_len; } namespace { // Given an old -> new ID mapping, change all relevant values in entries/values: // 1) Find all Res_value (whether in complex or non-complex) entries, remap the // data if it's a TYPE_REFERENCE or TYPE_ATTRIBUTE. // 2) For complex entries, remap: // a) ResTable_map_entry's parent // b) For each ResTable_map, the name class EntryRemapper : public arsc::ResourceTableVisitor { public: ~EntryRemapper() override {} explicit EntryRemapper(const std::map<uint32_t, uint32_t>& old_to_new) : m_old_to_new(old_to_new) {} void remap_value_impl(android::Res_value* value) { if (m_seen_values.count(value) > 0) { return; } m_seen_values.emplace(value); if (value->dataType == android::Res_value::TYPE_REFERENCE || value->dataType == android::Res_value::TYPE_ATTRIBUTE) { auto search = m_old_to_new.find(dtohl(value->data)); if (search != m_old_to_new.end()) { value->data = htodl(search->second); } } } void remap_ref_impl(android::ResTable_ref* ref) { if (m_seen_refs.count(ref) > 0) { return; } m_seen_refs.emplace(ref); auto search = m_old_to_new.find(dtohl(ref->ident)); if (search != m_old_to_new.end()) { ref->ident = htodl(search->second); } } bool visit_entry(android::ResTable_package* /* unused */, android::ResTable_typeSpec* /* unused */, android::ResTable_type* /* unused */, android::ResTable_entry* /* unused */, android::Res_value* value) override { remap_value_impl(value); return true; } bool visit_map_entry(android::ResTable_package* /* unused */, android::ResTable_typeSpec* /* unused */, android::ResTable_type* /* unused */, android::ResTable_map_entry* entry) override { remap_ref_impl(&entry->parent); return true; } bool visit_map_value(android::ResTable_package* /* unused */, android::ResTable_typeSpec* /* unused */, android::ResTable_type* /* unused */, android::ResTable_map_entry* /* unused */, android::ResTable_map* value) override { remap_value_impl(&value->value); remap_ref_impl(&value->name); return true; } private: const std::map<uint32_t, uint32_t>& m_old_to_new; // Tolerate a "canonicalized" version of a type, to make sure we don't double // remap. std::unordered_set<android::Res_value*> m_seen_values; std::unordered_set<android::ResTable_ref*> m_seen_refs; }; } // namespace void ResourcesArscFile::remap_ids( const std::map<uint32_t, uint32_t>& old_to_remapped_ids) { EntryRemapper remapper(old_to_remapped_ids); // Note: file is opened for writing. Visitor will in place change the data // (without altering any data sizes). remapper.visit(m_f.data(), m_arsc_len); } std::unordered_set<uint32_t> ResourcesArscFile::get_types_by_name( const std::unordered_set<std::string>& type_names) { android::Vector<android::String8> typeNames; res_table.getTypeNamesForPackage(0, &typeNames); std::unordered_set<uint32_t> type_ids; for (size_t i = 0; i < typeNames.size(); ++i) { std::string typeStr(typeNames[i].string()); if (type_names.count(typeStr) == 1) { type_ids.emplace((i + 1) << TYPE_INDEX_BIT_SHIFT); } } return type_ids; } std::vector<std::string> ResourcesArscFile::get_resource_strings_by_name( const std::string& res_name) { std::vector<std::string> ret; auto it = name_to_ids.find(res_name); if (it != name_to_ids.end()) { ret.reserve(it->second.size()); for (uint32_t id : it->second) { android::Res_value res_value; res_table.getResource(id, &res_value); // just in case there's a reference res_table.resolveReference(&res_value, 0); size_t len = 0; // aapt is using 0, so why not? const char16_t* str = res_table.getTableStringBlock(0)->stringAt(res_value.data, &len); if (str) { ret.push_back(android::String8(str, len).string()); } } } return ret; } ResourcesArscFile::~ResourcesArscFile() {}