libredex/ApkResources.h (185 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. */ #pragma once #include <boost/optional.hpp> #include <map> #include <string> #include <unordered_map> #include <unordered_set> #include <vector> #include "androidfw/ResourceTypes.h" #include "utils/Serialize.h" #include "utils/Visitor.h" #include "RedexMappedFile.h" #include "RedexResources.h" // Compiled XML reading helper functions. Only applicable to APK input files. std::string get_string_attribute_value(const android::ResXMLTree& parser, const android::String16& attribute_name); bool has_raw_attribute_value(const android::ResXMLTree& parser, const android::String16& attribute_name, android::Res_value& out_value); int get_int_attribute_or_default_value(const android::ResXMLTree& parser, const android::String16& attribute_name, int32_t default_value); bool has_bool_attribute(const android::ResXMLTree& parser, const android::String16& attribute_name); bool get_bool_attribute_value(const android::ResXMLTree& parser, const android::String16& attribute_name, bool default_value); namespace apk { std::string get_string_from_pool(const android::ResStringPool& pool, size_t idx); struct TypeDefinition { uint32_t package_id; uint8_t type_id; std::string name; std::vector<android::ResTable_config*> configs; std::vector<uint32_t> source_res_ids; }; // Read the ResTable data structures and store a convenient organization of the // data pointers and the packages. // NOTE: Visitor super classes simply follow pointers, so all subclasses which // make use of/change data need to be aware of the potential for "canonical" // data offsets (hence the use of sets). class TableParser : public arsc::StringPoolRefVisitor { public: ~TableParser() override {} bool visit_global_strings(android::ResStringPool_header* pool) override; bool visit_package(android::ResTable_package* package) override; bool visit_key_strings(android::ResTable_package* package, android::ResStringPool_header* pool) override; bool visit_type_strings(android::ResTable_package* package, android::ResStringPool_header* pool) override; bool visit_type_spec(android::ResTable_package* package, android::ResTable_typeSpec* type_spec) override; bool visit_type(android::ResTable_package* package, android::ResTable_typeSpec* type_spec, android::ResTable_type* type) override; bool visit_unknown_chunk(android::ResTable_package* package, android::ResChunk_header* header) override; android::ResStringPool_header* m_global_pool_header; std::map<android::ResTable_package*, android::ResStringPool_header*> m_package_key_string_headers; std::map<android::ResTable_package*, android::ResStringPool_header*> m_package_type_string_headers; // Simple organization of each ResTable_typeSpec in the package, and each // spec's types/configs. std::map<android::ResTable_package*, std::vector<arsc::TypeInfo>> m_package_types; std::set<android::ResTable_package*> m_packages; // Chunks belonging to a package that we do not parse/edit. Meant to be // preserved as-is when preparing output file. std::map<android::ResTable_package*, std::vector<android::ResChunk_header*>> m_package_unknown_chunks; }; using TypeToEntries = std::map<android::ResTable_type*, std::vector<arsc::EntryValueData>>; using ConfigToEntry = std::map<android::ResTable_config*, arsc::EntryValueData>; class TableEntryParser : public TableParser { public: ~TableEntryParser() override {} bool visit_type_spec(android::ResTable_package* package, android::ResTable_typeSpec* type_spec) override; bool visit_type(android::ResTable_package* package, android::ResTable_typeSpec* type_spec, android::ResTable_type* type) override; // For a package, the mapping from each type within all type specs to all the // entries/values. std::unordered_map<android::ResTable_package*, TypeToEntries> m_types_to_entries; // Package and type ID to spec std::map<uint16_t, android::ResTable_typeSpec*> m_types; // Package and type ID to all configs in that type std::map<uint16_t, std::vector<android::ResTable_type*>> m_types_to_configs; // Resource ID to the corresponding entries (in all configs). std::map<uint32_t, ConfigToEntry> m_res_id_to_entries; std::map<uint32_t, uint32_t> m_res_id_to_flags; private: // Convenience function to make it easy to uniquely refer to a type. uint16_t make_package_type_id(android::ResTable_package* package, uint8_t type_id) { return ((dtohl(package->id) & 0xFF) << 8) | type_id; } void put_entry_data(uint32_t res_id, android::ResTable_package* package, android::ResTable_type* type, arsc::EntryValueData& data); }; } // namespace apk class ResourcesArscFile : public ResourceTableFile { public: ResourcesArscFile(const ResourcesArscFile&) = delete; ResourcesArscFile& operator=(const ResourcesArscFile&) = delete; android::ResTable res_table; explicit ResourcesArscFile(const std::string& path); std::vector<std::string> get_resource_strings_by_name( const std::string& res_name); void remap_ids(const std::map<uint32_t, uint32_t>& old_to_remapped_ids); size_t serialize(); void collect_resid_values_and_hashes( const std::vector<uint32_t>& ids, std::map<size_t, std::vector<uint32_t>>* res_by_hash) override; bool resource_value_identical(uint32_t a_id, uint32_t b_id) override; std::unordered_set<uint32_t> get_types_by_name( const std::unordered_set<std::string>& type_names) override; void delete_resource(uint32_t res_id) override; void remap_res_ids_and_serialize( const std::vector<std::string>& resource_files, const std::map<uint32_t, uint32_t>& old_to_new) override; void remap_file_paths_and_serialize( const std::vector<std::string>& resource_files, const std::unordered_map<std::string, std::string>& old_to_new) override; void remove_unreferenced_strings() override; std::vector<std::string> get_files_by_rid( uint32_t res_id, ResourcePathType path_type = ResourcePathType::DevicePath) override; void 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) override; // Takes effect during serialization, in which new type spec, type data // structures will be appended to the package, with entry/value data copied // from the given ids. Actual type data in the resulting file will be emitted // in the order as the given configs. void define_type(uint32_t package_id, uint8_t type_id, const std::string& name, const std::vector<android::ResTable_config*>& configs, const std::vector<uint32_t>& source_res_ids) { LOG_ALWAYS_FATAL_IF((package_id & 0xFFFFFF00) != 0, "package_id expected to have low byte set; got 0x%x", package_id); android::String8 name8(name.data()); android::String16 name16(name8); apk::TypeDefinition def{package_id, type_id, name, configs, source_res_ids}; m_added_types.emplace_back(std::move(def)); } ~ResourcesArscFile() override; size_t get_length() const; private: std::string m_path; RedexMappedFile m_f; size_t m_arsc_len; std::map<uint32_t, android::Vector<android::Res_value>> tmp_id_to_values; bool m_file_closed = false; std::unordered_set<uint32_t> m_ids_to_remove; std::vector<apk::TypeDefinition> m_added_types; }; class ApkResources : public AndroidResources { public: explicit ApkResources(const std::string& directory) : AndroidResources(directory), m_manifest(directory + "/AndroidManifest.xml") {} ~ApkResources() override; boost::optional<int32_t> get_min_sdk() override; ManifestClassInfo get_manifest_class_info() override; std::unordered_set<uint32_t> get_xml_reference_attributes( const std::string& filename) override; void 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) override; // Given the bytes of a binary XML file, replace the entries (if any) in the // ResStringPool. Writes result to the given Vector output param. // Returns android::NO_ERROR (0) on success, or one of the corresponding // android:: error codes for failure conditions/bad input data. int 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); size_t remap_xml_reference_attributes( const std::string& filename, const std::map<uint32_t, uint32_t>& kept_to_remapped_ids) override; std::unique_ptr<ResourceTableFile> load_res_table() override; std::unordered_set<std::string> find_all_xml_files() override; std::vector<std::string> find_resources_files() override; std::string get_base_assets_dir() override; protected: std::vector<std::string> find_res_directories() override; std::vector<std::string> find_lib_directories() override; // Replaces all strings in the ResStringPool for the given file with their // replacements. Writes all changes to disk, clobbering the given file. bool rename_classes_in_layout( const std::string& file_path, const std::map<std::string, std::string>& rename_map, size_t* out_num_renamed) override; private: const std::string m_manifest; };