| package org.robolectric.res.android; |
| |
| import static org.robolectric.res.android.Errors.NO_ERROR; |
| import static org.robolectric.res.android.Errors.NO_INIT; |
| import static org.robolectric.res.android.ResourceTypes.RES_STRING_POOL_TYPE; |
| import static org.robolectric.res.android.ResourceTypes.RES_TABLE_LIBRARY_TYPE; |
| import static org.robolectric.res.android.ResourceTypes.RES_TABLE_PACKAGE_TYPE; |
| import static org.robolectric.res.android.ResourceTypes.RES_TABLE_STAGED_ALIAS_TYPE; |
| import static org.robolectric.res.android.ResourceTypes.RES_TABLE_TYPE; |
| import static org.robolectric.res.android.ResourceTypes.RES_TABLE_TYPE_SPEC_TYPE; |
| import static org.robolectric.res.android.ResourceTypes.RES_TABLE_TYPE_TYPE; |
| import static org.robolectric.res.android.ResourceTypes.kResTableTypeMinSize; |
| import static org.robolectric.res.android.ResourceUtils.make_resid; |
| import static org.robolectric.res.android.Util.UNLIKELY; |
| import static org.robolectric.res.android.Util.dtohl; |
| import static org.robolectric.res.android.Util.dtohs; |
| import static org.robolectric.res.android.Util.isTruthy; |
| import static org.robolectric.res.android.Util.logError; |
| import static org.robolectric.res.android.Util.logWarning; |
| |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import java.util.Set; |
| import org.robolectric.res.android.Chunk.Iterator; |
| import org.robolectric.res.android.Idmap.LoadedIdmap; |
| import org.robolectric.res.android.ResourceTypes.IdmapEntry_header; |
| import org.robolectric.res.android.ResourceTypes.ResStringPool_header; |
| import org.robolectric.res.android.ResourceTypes.ResTableStagedAliasEntry; |
| import org.robolectric.res.android.ResourceTypes.ResTableStagedAliasHeader; |
| import org.robolectric.res.android.ResourceTypes.ResTable_entry; |
| import org.robolectric.res.android.ResourceTypes.ResTable_header; |
| import org.robolectric.res.android.ResourceTypes.ResTable_lib_entry; |
| import org.robolectric.res.android.ResourceTypes.ResTable_lib_header; |
| import org.robolectric.res.android.ResourceTypes.ResTable_map; |
| import org.robolectric.res.android.ResourceTypes.ResTable_map_entry; |
| import org.robolectric.res.android.ResourceTypes.ResTable_package; |
| import org.robolectric.res.android.ResourceTypes.ResTable_sparseTypeEntry; |
| import org.robolectric.res.android.ResourceTypes.ResTable_type; |
| import org.robolectric.res.android.ResourceTypes.ResTable_typeSpec; |
| import org.robolectric.res.android.ResourceTypes.Res_value; |
| |
| // transliterated from https://android.googlesource.com/platform/frameworks/base/+/android-9.0.0_r12/libs/androidfw/include/androidfw/LoadedArsc.h |
| // and https://android.googlesource.com/platform/frameworks/base/+/android-9.0.0_r12/libs/androidfw/LoadedArsc.cpp |
| public class LoadedArsc { |
| |
| // #ifndef LOADEDARSC_H_ |
| // #define LOADEDARSC_H_ |
| // |
| // #include <memory> |
| // #include <set> |
| // #include <vector> |
| // |
| // #include "android-base/macros.h" |
| // |
| // #include "androidfw/ByteBucketArray.h" |
| // #include "androidfw/Chunk.h" |
| // #include "androidfw/ResourceTypes.h" |
| // #include "androidfw/Util.h" |
| // |
| // namespace android { |
| // |
| |
| private static final int kFrameworkPackageId = 0x01; |
| |
| static class DynamicPackageEntry { |
| |
| // public: |
| // |
| // DynamicPackageEntry() =default; |
| |
| DynamicPackageEntry(String package_name, int package_id) { |
| this.package_name = package_name; |
| this.package_id = package_id; |
| } |
| |
| String package_name; |
| int package_id = 0; |
| } |
| |
| // TypeSpec is going to be immediately proceeded by |
| // an array of Type structs, all in the same block of memory. |
| static class TypeSpec { |
| |
| public static final int SIZEOF = ResTable_typeSpec.SIZEOF + IdmapEntry_header.SIZEOF; |
| |
| // Pointer to the mmapped data where flags are kept. |
| // Flags denote whether the resource entry is public |
| // and under which configurations it varies. |
| ResTable_typeSpec type_spec; |
| |
| // Pointer to the mmapped data where the IDMAP mappings for this type |
| // exist. May be nullptr if no IDMAP exists. |
| IdmapEntry_header idmap_entries; |
| |
| // The number of types that follow this struct. |
| // There is a type for each configuration that entries are defined for. |
| int type_count; |
| |
| // Trick to easily access a variable number of Type structs |
| // proceeding this struct, and to ensure their alignment. |
| // ResTable_type* types[0]; |
| ResTable_type[] types; |
| |
| int GetFlagsForEntryIndex(int entry_index) { |
| if (entry_index >= dtohl(type_spec.entryCount)) { |
| return 0; |
| } |
| |
| // uint32_t* flags = reinterpret_cast<uint32_t*>(type_spec + 1); |
| int[] flags = type_spec.getSpecFlags(); |
| return flags[entry_index]; |
| } |
| } |
| |
| // Returns the string pool where all string resource values |
| // (Res_value::dataType == Res_value::TYPE_STRING) are indexed. |
| public ResStringPool GetStringPool() { |
| return global_string_pool_; |
| } |
| |
| // Returns a vector of LoadedPackage pointers, representing the packages in this LoadedArsc. |
| List<LoadedPackage> GetPackages() { |
| return packages_; |
| } |
| |
| // Returns true if this is a system provided resource. |
| boolean IsSystem() { |
| return system_; |
| } |
| |
| // |
| // private: |
| // DISALLOW_COPY_AND_ASSIGN(LoadedArsc); |
| // |
| // LoadedArsc() = default; |
| // bool LoadTable(const Chunk& chunk, const LoadedIdmap* loaded_idmap, bool load_as_shared_library); |
| // |
| final ResStringPool global_string_pool_ = new ResStringPool(); |
| final List<LoadedPackage> packages_ = new ArrayList<>(); |
| boolean system_ = false; |
| // }; |
| // |
| // } // namespace android |
| // |
| // #endif // LOADEDARSC_H_ |
| |
| // #define ATRACE_TAG ATRACE_TAG_RESOURCES |
| // |
| // #include "androidfw/LoadedArsc.h" |
| // |
| // #include <cstddef> |
| // #include <limits> |
| // |
| // #include "android-base/logging.h" |
| // #include "android-base/stringprintf.h" |
| // #include "utils/ByteOrder.h" |
| // #include "utils/Trace.h" |
| // |
| // #ifdef _WIN32 |
| // #ifdef ERROR |
| // #undef ERROR |
| // #endif |
| // #endif |
| // |
| // #include "androidfw/ByteBucketArray.h" |
| // #include "androidfw/Chunk.h" |
| // #include "androidfw/ResourceUtils.h" |
| // #include "androidfw/Util.h" |
| // |
| // using android::base::StringPrintf; |
| // |
| // namespace android { |
| |
| static final int kAppPackageId = 0x7f; |
| |
| // namespace { |
| |
| // Builder that helps accumulate Type structs and then create a single |
| // contiguous block of memory to store both the TypeSpec struct and |
| // the Type structs. |
| static class TypeSpecPtrBuilder { |
| // public: |
| TypeSpecPtrBuilder(ResTable_typeSpec header, IdmapEntry_header idmap_header) { |
| this.header_ = header; |
| this.idmap_header_ = idmap_header; |
| } |
| |
| void AddType(ResTable_type type) { |
| types_.add(type); |
| } |
| |
| TypeSpec Build() { |
| // Check for overflow. |
| // using ElementType = ResTable_type*; |
| // if ((std.numeric_limits<size_t>.max() - sizeof(TypeSpec)) / sizeof(ElementType) < |
| // types_.size()) { |
| if ((Integer.MAX_VALUE - TypeSpec.SIZEOF) / 4 < types_.size()) { |
| return null; // {} ; |
| } |
| // TypeSpec* type_spec = |
| // (TypeSpec*).malloc(sizeof(TypeSpec) + (types_.size() * sizeof(ElementType))); |
| TypeSpec type_spec = new TypeSpec(); |
| type_spec.types = new ResTable_type[types_.size()]; |
| type_spec.type_spec = header_; |
| type_spec.idmap_entries = idmap_header_; |
| type_spec.type_count = types_.size(); |
| // memcpy(type_spec + 1, types_.data(), types_.size() * sizeof(ElementType)); |
| for (int i = 0; i < type_spec.types.length; i++) { |
| type_spec.types[i] = types_.get(i); |
| |
| } |
| return type_spec; |
| } |
| |
| // private: |
| // DISALLOW_COPY_AND_ASSIGN(TypeSpecPtrBuilder); |
| |
| ResTable_typeSpec header_; |
| IdmapEntry_header idmap_header_; |
| final List<ResTable_type> types_ = new ArrayList<>(); |
| }; |
| |
| // } // namespace |
| |
| // Precondition: The header passed in has already been verified, so reading any fields and trusting |
| // the ResChunk_header is safe. |
| static boolean VerifyResTableType(ResTable_type header) { |
| if (header.id == 0) { |
| logError("RES_TABLE_TYPE_TYPE has invalid ID 0."); |
| return false; |
| } |
| |
| int entry_count = dtohl(header.entryCount); |
| // if (entry_count > std.numeric_limits<uint16_t>.max()) { |
| if (entry_count > 0xffff) { |
| logError("RES_TABLE_TYPE_TYPE has too many entries (" + entry_count + ")."); |
| return false; |
| } |
| |
| // Make sure that there is enough room for the entry offsets. |
| int offsets_offset = dtohs(header.header.headerSize); |
| int entries_offset = dtohl(header.entriesStart); |
| int offsets_length = 4 * entry_count; |
| |
| if (offsets_offset > entries_offset || entries_offset - offsets_offset < offsets_length) { |
| logError("RES_TABLE_TYPE_TYPE entry offsets overlap actual entry data."); |
| return false; |
| } |
| |
| if (entries_offset > dtohl(header.header.size)) { |
| logError("RES_TABLE_TYPE_TYPE entry offsets extend beyond chunk."); |
| return false; |
| } |
| |
| if (isTruthy(entries_offset & 0x03)) { |
| logError("RES_TABLE_TYPE_TYPE entries start at unaligned address."); |
| return false; |
| } |
| return true; |
| } |
| |
| static boolean VerifyResTableEntry(ResTable_type type, int entry_offset) { |
| // Check that the offset is aligned. |
| if (isTruthy(entry_offset & 0x03)) { |
| logError("Entry at offset " + entry_offset + " is not 4-byte aligned."); |
| return false; |
| } |
| |
| // Check that the offset doesn't overflow. |
| // if (entry_offset > std.numeric_limits<int>.max() - dtohl(type.entriesStart)) { |
| if (entry_offset > Integer.MAX_VALUE - dtohl(type.entriesStart)) { |
| // Overflow in offset. |
| logError("Entry at offset " + entry_offset + " is too large."); |
| return false; |
| } |
| |
| int chunk_size = dtohl(type.header.size); |
| |
| entry_offset += dtohl(type.entriesStart); |
| if (entry_offset > chunk_size - ResTable_entry.SIZEOF) { |
| logError("Entry at offset " + entry_offset |
| + " is too large. No room for ResTable_entry."); |
| return false; |
| } |
| |
| // ResTable_entry* entry = reinterpret_cast<ResTable_entry*>( |
| // reinterpret_cast<uint8_t*>(type) + entry_offset); |
| ResTable_entry entry = new ResTable_entry(type.myBuf(), type.myOffset() + entry_offset); |
| |
| int entry_size = dtohs(entry.size); |
| // if (entry_size < sizeof(*entry)) { |
| if (entry_size < ResTable_entry.SIZEOF) { |
| logError("ResTable_entry size " + entry_size + " at offset " + entry_offset |
| + " is too small."); |
| return false; |
| } |
| |
| if (entry_size > chunk_size || entry_offset > chunk_size - entry_size) { |
| logError("ResTable_entry size " + entry_size + " at offset " + entry_offset |
| + " is too large."); |
| return false; |
| } |
| |
| if (entry_size < ResTable_map_entry.BASE_SIZEOF) { |
| // There needs to be room for one Res_value struct. |
| if (entry_offset + entry_size > chunk_size - Res_value.SIZEOF) { |
| logError("No room for Res_value after ResTable_entry at offset " + entry_offset |
| + " for type " + (int) type.id + "."); |
| return false; |
| } |
| |
| // Res_value value = |
| // reinterpret_cast<Res_value*>(reinterpret_cast<uint8_t*>(entry) + entry_size); |
| Res_value value = |
| new Res_value(entry.myBuf(), entry.myOffset() + ResTable_entry.SIZEOF); |
| int value_size = dtohs(value.size); |
| if (value_size < Res_value.SIZEOF) { |
| logError("Res_value at offset " + entry_offset + " is too small."); |
| return false; |
| } |
| |
| if (value_size > chunk_size || entry_offset + entry_size > chunk_size - value_size) { |
| logError("Res_value size " + value_size + " at offset " + entry_offset |
| + " is too large."); |
| return false; |
| } |
| } else { |
| ResTable_map_entry map = new ResTable_map_entry(entry.myBuf(), entry.myOffset()); |
| int map_entry_count = dtohl(map.count); |
| int map_entries_start = entry_offset + entry_size; |
| if (isTruthy(map_entries_start & 0x03)) { |
| logError("Map entries at offset " + entry_offset + " start at unaligned offset."); |
| return false; |
| } |
| |
| // Each entry is sizeof(ResTable_map) big. |
| if (map_entry_count > ((chunk_size - map_entries_start) / ResTable_map.SIZEOF)) { |
| logError("Too many map entries in ResTable_map_entry at offset " + entry_offset + "."); |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| static class LoadedPackage { |
| // private: |
| |
| // DISALLOW_COPY_AND_ASSIGN(LoadedPackage); |
| |
| // LoadedPackage(); |
| |
| ResStringPool type_string_pool_ = new ResStringPool(); |
| ResStringPool key_string_pool_ = new ResStringPool(); |
| String package_name_; |
| int package_id_ = -1; |
| int type_id_offset_ = 0; |
| boolean dynamic_ = false; |
| boolean system_ = false; |
| boolean overlay_ = false; |
| |
| // final ByteBucketArray<TypeSpec> type_specs_ = new ByteBucketArray<TypeSpec>() { |
| // @Override |
| // TypeSpec newInstance() { |
| // return new TypeSpec(); |
| // } |
| // }; |
| final Map<Integer, TypeSpec> type_specs_ = new HashMap<>(); |
| final List<DynamicPackageEntry> dynamic_package_map_ = new ArrayList<>(); |
| final Map<Integer, Integer> aliasIdMap = new HashMap<>(); |
| |
| ResTable_entry GetEntry(ResTable_type type_chunk, |
| short entry_index) { |
| int entry_offset = GetEntryOffset(type_chunk, entry_index); |
| if (entry_offset == ResTable_type.NO_ENTRY) { |
| return null; |
| } |
| return GetEntryFromOffset(type_chunk, entry_offset); |
| } |
| |
| static int GetEntryOffset(ResTable_type type_chunk, int entry_index) { |
| // The configuration matches and is better than the previous selection. |
| // Find the entry value if it exists for this configuration. |
| int entry_count = dtohl(type_chunk.entryCount); |
| int offsets_offset = dtohs(type_chunk.header.headerSize); |
| |
| // Check if there is the desired entry in this type. |
| |
| if (isTruthy(type_chunk.flags & ResTable_type.FLAG_SPARSE)) { |
| // This is encoded as a sparse map, so perform a binary search. |
| // ResTable_sparseTypeEntry sparse_indices = |
| // reinterpret_cast<ResTable_sparseTypeEntry*>( |
| // reinterpret_cast<uint8_t*>(type_chunk) + offsets_offset); |
| // ResTable_sparseTypeEntry* sparse_indices_end = sparse_indices + entry_count; |
| // ResTable_sparseTypeEntry* result = |
| // std.lower_bound(sparse_indices, sparse_indices_end, entry_index, |
| // [](ResTable_sparseTypeEntry& entry, short entry_idx) { |
| // return dtohs(entry.idx) < entry_idx; |
| // }); |
| ResTable_sparseTypeEntry result = null; |
| for (int i = 0; i < entry_count; i++) { |
| ResTable_sparseTypeEntry entry = |
| new ResTable_sparseTypeEntry( |
| type_chunk.myBuf(), |
| type_chunk.myOffset() + offsets_offset + i * ResTable_sparseTypeEntry.SIZEOF); |
| if (entry.idx >= entry_index) { |
| result = entry; |
| break; |
| } |
| } |
| |
| if (result == null || dtohs(result.idx) != entry_index) { |
| // No entry found. |
| return ResTable_type.NO_ENTRY; |
| } |
| |
| // Extract the offset from the entry. Each offset must be a multiple of 4 so we store it as |
| // the real offset divided by 4. |
| // return int{dtohs(result.offset)} * 4u; |
| return dtohs(result.offset) * 4; |
| } |
| |
| // This type is encoded as a dense array. |
| if (entry_index >= entry_count) { |
| // This entry cannot be here. |
| return ResTable_type.NO_ENTRY; |
| } |
| |
| // int* entry_offsets = reinterpret_cast<int*>( |
| // reinterpret_cast<uint8_t*>(type_chunk) + offsets_offset); |
| // return dtohl(entry_offsets[entry_index]); |
| return dtohl(type_chunk.entryOffset(entry_index)); |
| } |
| |
| static ResTable_entry GetEntryFromOffset(ResTable_type type_chunk, |
| int offset) { |
| if (UNLIKELY(!VerifyResTableEntry(type_chunk, offset))) { |
| return null; |
| } |
| // return reinterpret_cast<ResTable_entry*>(reinterpret_cast<uint8_t*>(type_chunk) + |
| // offset + dtohl(type_chunk.entriesStart)); |
| return new ResTable_entry(type_chunk.myBuf(), |
| type_chunk.myOffset() + offset + dtohl(type_chunk.entriesStart)); |
| } |
| |
| void CollectConfigurations(boolean exclude_mipmap, |
| Set<ResTable_config> out_configs) { |
| String kMipMap = "mipmap"; |
| int type_count = type_specs_.size(); |
| for (int i = 0; i < type_count; i++) { |
| TypeSpec type_spec = type_specs_.get(i); |
| if (type_spec != null) { |
| if (exclude_mipmap) { |
| int type_idx = type_spec.type_spec.id - 1; |
| final Ref<Integer> type_name_len = new Ref<>(0); |
| String type_name16 = type_string_pool_.stringAt(type_idx, type_name_len); |
| if (type_name16 != null) { |
| // if (kMipMap.compare(0, std::u16string::npos,type_name16, type_name_len) ==0){ |
| if (kMipMap.equals(type_name16)) { |
| // This is a mipmap type, skip collection. |
| continue; |
| } |
| } |
| String type_name = type_string_pool_.string8At(type_idx, type_name_len); |
| if (type_name != null) { |
| // if (strncmp(type_name, "mipmap", type_name_len) == 0) { |
| if ("mipmap".equals(type_name)) |
| // This is a mipmap type, skip collection. |
| continue; |
| } |
| } |
| } |
| |
| for (ResTable_type iter : type_spec.types) { |
| ResTable_config config = ResTable_config.fromDtoH(iter.config); |
| out_configs.add(config); |
| } |
| } |
| } |
| |
| void CollectLocales(boolean canonicalize, Set<String> out_locales) { |
| // char temp_locale[ RESTABLE_MAX_LOCALE_LEN]; |
| String temp_locale; |
| int type_count = type_specs_.size(); |
| for (int i = 0; i < type_count; i++) { |
| TypeSpec type_spec = type_specs_.get(i); |
| if (type_spec != null) { |
| for (ResTable_type iter : type_spec.types) { |
| ResTable_config configuration = ResTable_config.fromDtoH(iter.config); |
| if (configuration.locale() != 0) { |
| temp_locale = configuration.getBcp47Locale(canonicalize); |
| String locale = temp_locale; |
| out_locales.add(locale); |
| } |
| } |
| } |
| } |
| } |
| |
| // Finds the entry with the specified type name and entry name. The names are in UTF-16 because |
| // the underlying ResStringPool API expects this. For now this is acceptable, but since |
| // the default policy in AAPT2 is to build UTF-8 string pools, this needs to change. |
| // Returns a partial resource ID, with the package ID left as 0x00. The caller is responsible |
| // for patching the correct package ID to the resource ID. |
| int FindEntryByName(String type_name, String entry_name) { |
| int type_idx = type_string_pool_.indexOfString(type_name); |
| if (type_idx < 0) { |
| return 0; |
| } |
| |
| int key_idx = key_string_pool_.indexOfString(entry_name); |
| if (key_idx < 0) { |
| return 0; |
| } |
| |
| TypeSpec type_spec = type_specs_.get(type_idx); |
| if (type_spec == null) { |
| return 0; |
| } |
| |
| for (ResTable_type iter : type_spec.types) { |
| ResTable_type type = iter; |
| int entry_count = type.entryCount; |
| |
| for (int entry_idx = 0; entry_idx < entry_count; entry_idx++) { |
| // const uint32_t* entry_offsets = reinterpret_cast<const uint32_t*>( |
| // reinterpret_cast<const uint8_t*>(type.type) + dtohs(type.type.header.headerSize)); |
| // ResTable_type entry_offsets = new ResTable_type(type.myBuf(), |
| // type.myOffset() + type.header.headerSize); |
| // int offset = dtohl(entry_offsets[entry_idx]); |
| int offset = dtohl(type.entryOffset(entry_idx)); |
| if (offset != ResTable_type.NO_ENTRY) { |
| // const ResTable_entry* entry = |
| // reinterpret_cast<const ResTable_entry*>(reinterpret_cast<const uint8_t*>(type.type) + |
| // dtohl(type.type.entriesStart) + offset); |
| ResTable_entry entry = |
| new ResTable_entry(type.myBuf(), type.myOffset() + |
| dtohl(type.entriesStart) + offset); |
| if (dtohl(entry.key.index) == key_idx) { |
| // The package ID will be overridden by the caller (due to runtime assignment of package |
| // IDs for shared libraries). |
| return make_resid((byte) 0x00, (byte) (type_idx + type_id_offset_ + 1), (short) entry_idx); |
| } |
| } |
| } |
| } |
| return 0; |
| } |
| |
| static LoadedPackage Load(Chunk chunk, |
| LoadedIdmap loaded_idmap, |
| boolean system, boolean load_as_shared_library) { |
| // ATRACE_NAME("LoadedPackage::Load"); |
| LoadedPackage loaded_package = new LoadedPackage(); |
| |
| // typeIdOffset was added at some point, but we still must recognize apps built before this |
| // was added. |
| // constexpr int kMinPackageSize = |
| // sizeof(ResTable_package) - sizeof(ResTable_package.typeIdOffset); |
| final int kMinPackageSize = ResTable_package.SIZEOF - 4; |
| // ResTable_package header = chunk.header<ResTable_package, kMinPackageSize>(); |
| ResTable_package header = chunk.asResTable_package(kMinPackageSize); |
| if (header == null) { |
| logError("RES_TABLE_PACKAGE_TYPE too small."); |
| return emptyBraces(); |
| } |
| |
| loaded_package.system_ = system; |
| |
| loaded_package.package_id_ = dtohl(header.id); |
| if (loaded_package.package_id_ == 0 || |
| (loaded_package.package_id_ == kAppPackageId && load_as_shared_library)) { |
| // Package ID of 0 means this is a shared library. |
| loaded_package.dynamic_ = true; |
| } |
| |
| if (loaded_idmap != null) { |
| // This is an overlay and so it needs to pretend to be the target package. |
| loaded_package.package_id_ = loaded_idmap.TargetPackageId(); |
| loaded_package.overlay_ = true; |
| } |
| |
| if (header.header.headerSize >= ResTable_package.SIZEOF) { |
| int type_id_offset = dtohl(header.typeIdOffset); |
| // if (type_id_offset > std.numeric_limits<uint8_t>.max()) { |
| if (type_id_offset > 255) { |
| logError("RES_TABLE_PACKAGE_TYPE type ID offset too large."); |
| return emptyBraces(); |
| } |
| loaded_package.type_id_offset_ = type_id_offset; |
| } |
| |
| loaded_package.package_name_ = Util |
| .ReadUtf16StringFromDevice(header.name, header.name.length); |
| |
| // A map of TypeSpec builders, each associated with an type index. |
| // We use these to accumulate the set of Types available for a TypeSpec, and later build a single, |
| // contiguous block of memory that holds all the Types together with the TypeSpec. |
| Map<Integer, TypeSpecPtrBuilder> type_builder_map = new HashMap<>(); |
| |
| Chunk.Iterator iter = new Iterator(chunk.data_ptr(), chunk.data_size()); |
| while (iter.HasNext()) { |
| Chunk child_chunk = iter.Next(); |
| switch (child_chunk.type()) { |
| case RES_STRING_POOL_TYPE: { |
| // uintptr_t pool_address = |
| // reinterpret_cast<uintptr_t>(child_chunk.header<ResChunk_header>()); |
| // uintptr_t header_address = reinterpret_cast<uintptr_t>(header); |
| int pool_address = |
| child_chunk.myOffset(); |
| int header_address = header.myOffset(); |
| if (pool_address == header_address + dtohl(header.typeStrings)) { |
| // This string pool is the type string pool. |
| int err = loaded_package.type_string_pool_.setTo( |
| child_chunk.myBuf(), child_chunk.myOffset(), child_chunk.size(), false); |
| if (err != NO_ERROR) { |
| logError("RES_STRING_POOL_TYPE for types corrupt."); |
| return emptyBraces(); |
| } |
| } else if (pool_address == header_address + dtohl(header.keyStrings)) { |
| // This string pool is the key string pool. |
| int err = loaded_package.key_string_pool_.setTo( |
| child_chunk.myBuf(), child_chunk.myOffset(), child_chunk.size(), false); |
| if (err != NO_ERROR) { |
| logError("RES_STRING_POOL_TYPE for keys corrupt."); |
| return emptyBraces(); |
| } |
| } else { |
| logWarning("Too many RES_STRING_POOL_TYPEs found in RES_TABLE_PACKAGE_TYPE."); |
| } |
| } break; |
| |
| case RES_TABLE_TYPE_SPEC_TYPE: { |
| ResTable_typeSpec type_spec = new ResTable_typeSpec(child_chunk.myBuf(), |
| child_chunk.myOffset()); |
| if (type_spec == null) { |
| logError("RES_TABLE_TYPE_SPEC_TYPE too small."); |
| return emptyBraces(); |
| } |
| |
| if (type_spec.id == 0) { |
| logError("RES_TABLE_TYPE_SPEC_TYPE has invalid ID 0."); |
| return emptyBraces(); |
| } |
| |
| // if (loaded_package.type_id_offset_ + static_cast<int>(type_spec.id) > |
| // std.numeric_limits<uint8_t>.max()) { |
| if (loaded_package.type_id_offset_ + type_spec.id > 255) { |
| logError("RES_TABLE_TYPE_SPEC_TYPE has out of range ID."); |
| return emptyBraces(); |
| } |
| |
| // The data portion of this chunk contains entry_count 32bit entries, |
| // each one representing a set of flags. |
| // Here we only validate that the chunk is well formed. |
| int entry_count = dtohl(type_spec.entryCount); |
| |
| // There can only be 2^16 entries in a type, because that is the ID |
| // space for entries (EEEE) in the resource ID 0xPPTTEEEE. |
| // if (entry_count > std.numeric_limits<short>.max()) { |
| if (entry_count > 0xffff) { |
| logError("RES_TABLE_TYPE_SPEC_TYPE has too many entries (" + entry_count + ")."); |
| return emptyBraces(); |
| } |
| |
| if (entry_count * 4 /*sizeof(int)*/ > chunk.data_size()) { |
| logError("RES_TABLE_TYPE_SPEC_TYPE too small to hold entries."); |
| return emptyBraces(); |
| } |
| |
| // If this is an overlay, associate the mapping of this type to the target type |
| // from the IDMAP. |
| IdmapEntry_header idmap_entry_header = null; |
| if (loaded_idmap != null) { |
| idmap_entry_header = loaded_idmap.GetEntryMapForType(type_spec.id); |
| } |
| |
| TypeSpecPtrBuilder builder_ptr = type_builder_map.get(type_spec.id - 1); |
| if (builder_ptr == null) { |
| // builder_ptr = util.make_unique<TypeSpecPtrBuilder>(type_spec, idmap_entry_header); |
| builder_ptr = new TypeSpecPtrBuilder(type_spec, idmap_entry_header); |
| type_builder_map.put(type_spec.id - 1, builder_ptr); |
| } else { |
| logWarning(String.format("RES_TABLE_TYPE_SPEC_TYPE already defined for ID %02x", |
| type_spec.id)); |
| } |
| } break; |
| |
| case RES_TABLE_TYPE_TYPE: { |
| // ResTable_type type = child_chunk.header<ResTable_type, kResTableTypeMinSize>(); |
| ResTable_type type = child_chunk.asResTable_type(kResTableTypeMinSize); |
| if (type == null) { |
| logError("RES_TABLE_TYPE_TYPE too small."); |
| return emptyBraces(); |
| } |
| |
| if (!VerifyResTableType(type)) { |
| return emptyBraces(); |
| } |
| |
| // Type chunks must be preceded by their TypeSpec chunks. |
| TypeSpecPtrBuilder builder_ptr = type_builder_map.get(type.id - 1); |
| if (builder_ptr != null) { |
| builder_ptr.AddType(type); |
| } else { |
| logError( |
| String.format( |
| "RES_TABLE_TYPE_TYPE with ID %02x found without preceding" |
| + " RES_TABLE_TYPE_SPEC_TYPE.", |
| type.id)); |
| return emptyBraces(); |
| } |
| } break; |
| |
| case RES_TABLE_LIBRARY_TYPE: { |
| ResTable_lib_header lib = child_chunk.asResTable_lib_header(); |
| if (lib == null) { |
| logError("RES_TABLE_LIBRARY_TYPE too small."); |
| return emptyBraces(); |
| } |
| |
| if (child_chunk.data_size() / ResTable_lib_entry.SIZEOF < dtohl(lib.count)) { |
| logError("RES_TABLE_LIBRARY_TYPE too small to hold entries."); |
| return emptyBraces(); |
| } |
| |
| // loaded_package.dynamic_package_map_.reserve(dtohl(lib.count)); |
| |
| // ResTable_lib_entry entryBegin = |
| // reinterpret_cast<ResTable_lib_entry*>(child_chunk.data_ptr()); |
| ResTable_lib_entry entryBegin = |
| child_chunk.asResTable_lib_entry(); |
| // ResTable_lib_entry entry_end = entryBegin + dtohl(lib.count); |
| // for (auto entryIter = entryBegin; entryIter != entry_end; ++entryIter) { |
| for (ResTable_lib_entry entryIter = entryBegin; |
| entryIter.myOffset() != entryBegin.myOffset() + dtohl(lib.count); |
| entryIter = new ResTable_lib_entry(entryIter.myBuf(), entryIter.myOffset() + ResTable_lib_entry.SIZEOF)) { |
| String package_name = |
| Util.ReadUtf16StringFromDevice(entryIter.packageName, |
| entryIter.packageName.length); |
| |
| if (dtohl(entryIter.packageId) >= 255) { |
| logError(String.format( |
| "Package ID %02x in RES_TABLE_LIBRARY_TYPE too large for package '%s'.", |
| dtohl(entryIter.packageId), package_name)); |
| return emptyBraces(); |
| } |
| |
| // loaded_package.dynamic_package_map_.emplace_back(std.move(package_name), |
| // dtohl(entryIter.packageId)); |
| loaded_package.dynamic_package_map_.add(new DynamicPackageEntry(package_name, |
| dtohl(entryIter.packageId))); |
| } |
| |
| } break; |
| |
| case RES_TABLE_STAGED_ALIAS_TYPE: |
| { |
| if (loaded_package.package_id_ != kFrameworkPackageId) { |
| logWarning( |
| String.format( |
| "Alias chunk ignored for non-framework package '%s'", |
| loaded_package.package_name_)); |
| break; |
| } |
| |
| ResTableStagedAliasHeader libAlias = child_chunk.asResTableStagedAliasHeader(); |
| if (libAlias == null) { |
| logError("RES_TABLE_STAGED_ALIAS_TYPE is too small."); |
| return emptyBraces(); |
| } |
| if ((child_chunk.data_size() / ResTableStagedAliasEntry.SIZEOF) |
| < dtohl(libAlias.count)) { |
| logError("RES_TABLE_STAGED_ALIAS_TYPE is too small to hold entries."); |
| return emptyBraces(); |
| } |
| |
| // const auto entryBegin = |
| // child_chunk.data_ptr().convert<ResTableStagedAliasEntry>(); |
| // const auto entry_end = entryBegin + dtohl(libAlias.count); |
| ResTableStagedAliasEntry entryBegin = child_chunk.asResTableStagedAliasEntry(); |
| int entryEndOffset = |
| entryBegin.myOffset() |
| + dtohl(libAlias.count) * ResTableStagedAliasEntry.SIZEOF; |
| // std::unordered_set<uint32_t> finalizedIds; |
| // finalizedIds.reserve(entry_end - entryBegin); |
| Set<Integer> finalizedIds = new HashSet<>(); |
| for (ResTableStagedAliasEntry entryIter = entryBegin; |
| entryIter.myOffset() != entryEndOffset; |
| entryIter = |
| new ResTableStagedAliasEntry( |
| entryIter.myBuf(), |
| entryIter.myOffset() + ResTableStagedAliasEntry.SIZEOF)) { |
| |
| int finalizedId = dtohl(entryIter.finalizedResId); |
| // if (!finalizedIds.insert(finalizedId).second) { |
| if (!finalizedIds.add(finalizedId)) { |
| logError( |
| String.format( |
| "Repeated finalized resource id '%08x' in staged aliases.", |
| finalizedId)); |
| return emptyBraces(); |
| } |
| |
| int stagedId = dtohl(entryIter.stagedResId); |
| // auto [_, success] = loaded_package->aliasIdMap.emplace(stagedId, |
| // finalizedId); |
| Integer previousValue = loaded_package.aliasIdMap.put(stagedId, finalizedId); |
| if (previousValue != null) { |
| logError( |
| String.format( |
| "Repeated staged resource id '%08x' in staged aliases.", stagedId)); |
| return emptyBraces(); |
| } |
| } |
| } |
| break; |
| |
| default: |
| logWarning(String.format("Unknown chunk type '%02x'.", chunk.type())); |
| break; |
| } |
| } |
| |
| if (iter.HadError()) { |
| logError(iter.GetLastError()); |
| if (iter.HadFatalError()) { |
| return emptyBraces(); |
| } |
| } |
| |
| // Flatten and construct the TypeSpecs. |
| for (Entry<Integer, TypeSpecPtrBuilder> entry : type_builder_map.entrySet()) { |
| byte type_idx = (byte) entry.getKey().byteValue(); |
| TypeSpec type_spec_ptr = entry.getValue().Build(); |
| if (type_spec_ptr == null) { |
| logError("Too many type configurations, overflow detected."); |
| return emptyBraces(); |
| } |
| |
| // We only add the type to the package if there is no IDMAP, or if the type is |
| // overlaying something. |
| if (loaded_idmap == null || type_spec_ptr.idmap_entries != null) { |
| // If this is an overlay, insert it at the target type ID. |
| if (type_spec_ptr.idmap_entries != null) { |
| type_idx = (byte) (dtohs(type_spec_ptr.idmap_entries.target_type_id) - 1); |
| } |
| // loaded_package.type_specs_.editItemAt(type_idx) = std.move(type_spec_ptr); |
| loaded_package.type_specs_.put((int) type_idx, type_spec_ptr); |
| } |
| } |
| |
| // return std.move(loaded_package); |
| return loaded_package; |
| } |
| |
| // Returns the string pool where type names are stored. |
| ResStringPool GetTypeStringPool() { |
| return type_string_pool_; |
| } |
| |
| // Returns the string pool where the names of resource entries are stored. |
| ResStringPool GetKeyStringPool() { |
| return key_string_pool_; |
| } |
| |
| String GetPackageName() { |
| return package_name_; |
| } |
| |
| int GetPackageId() { |
| return package_id_; |
| } |
| |
| // Returns true if this package is dynamic (shared library) and needs to have an ID assigned. |
| boolean IsDynamic() { |
| return dynamic_; |
| } |
| |
| // Returns true if this package originates from a system provided resource. |
| boolean IsSystem() { |
| return system_; |
| } |
| |
| // Returns true if this package is from an overlay ApkAssets. |
| boolean IsOverlay() { |
| return overlay_; |
| } |
| |
| // Returns the map of package name to package ID used in this LoadedPackage. At runtime, a |
| // package could have been assigned a different package ID than what this LoadedPackage was |
| // compiled with. AssetManager rewrites the package IDs so that they are compatible at runtime. |
| List<DynamicPackageEntry> GetDynamicPackageMap() { |
| return dynamic_package_map_; |
| } |
| |
| // type_idx is TT - 1 from 0xPPTTEEEE. |
| TypeSpec GetTypeSpecByTypeIndex(int type_index) { |
| // If the type IDs are offset in this package, we need to take that into account when searching |
| // for a type. |
| return type_specs_.get(type_index - type_id_offset_); |
| } |
| |
| // template <typename Func> |
| interface TypeSpecFunc { |
| void apply(TypeSpec spec, byte index); |
| } |
| |
| void ForEachTypeSpec(TypeSpecFunc f) { |
| for (Integer i : type_specs_.keySet()) { |
| TypeSpec ptr = type_specs_.get(i); |
| if (ptr != null) { |
| byte type_id = ptr.type_spec.id; |
| if (ptr.idmap_entries != null) { |
| type_id = (byte) ptr.idmap_entries.target_type_id; |
| } |
| f.apply(ptr, (byte) (type_id - 1)); |
| } |
| } |
| } |
| |
| Map<Integer, Integer> getAliasResourceIdMap() { |
| return aliasIdMap; |
| } |
| |
| private static LoadedPackage emptyBraces() { |
| return new LoadedPackage(); |
| } |
| } |
| |
| // Gets a pointer to the package with the specified package ID, or nullptr if no such package |
| // exists. |
| LoadedPackage GetPackageById(int package_id) { |
| for (LoadedPackage loaded_package : packages_) { |
| if (loaded_package.GetPackageId() == package_id) { |
| return loaded_package; |
| } |
| } |
| return null; |
| } |
| |
| boolean LoadTable(Chunk chunk, LoadedIdmap loaded_idmap, |
| boolean load_as_shared_library) { |
| // ResTable_header header = chunk.header<ResTable_header>(); |
| ResTable_header header = chunk.asResTable_header(); |
| if (header == null) { |
| logError("RES_TABLE_TYPE too small."); |
| return false; |
| } |
| |
| int package_count = dtohl(header.packageCount); |
| int packages_seen = 0; |
| |
| // packages_.reserve(package_count); |
| |
| Chunk.Iterator iter = new Iterator(chunk.data_ptr(), chunk.data_size()); |
| while (iter.HasNext()) { |
| Chunk child_chunk = iter.Next(); |
| switch (child_chunk.type()) { |
| case RES_STRING_POOL_TYPE: |
| // Only use the first string pool. Ignore others. |
| if (global_string_pool_.getError() == NO_INIT) { |
| ResStringPool_header resStringPool_header = child_chunk.asResStringPool_header(); |
| int err = global_string_pool_.setTo(resStringPool_header.myBuf(), |
| resStringPool_header.myOffset(), |
| child_chunk.size(), false); |
| if (err != NO_ERROR) { |
| logError("RES_STRING_POOL_TYPE corrupt."); |
| return false; |
| } |
| } else { |
| logWarning("Multiple RES_STRING_POOL_TYPEs found in RES_TABLE_TYPE."); |
| } |
| break; |
| |
| case RES_TABLE_PACKAGE_TYPE: { |
| if (packages_seen + 1 > package_count) { |
| logError("More package chunks were found than the " + package_count |
| + " declared in the header."); |
| return false; |
| } |
| packages_seen++; |
| |
| LoadedPackage loaded_package = |
| LoadedPackage.Load(child_chunk, loaded_idmap, system_, load_as_shared_library); |
| if (!isTruthy(loaded_package)) { |
| return false; |
| } |
| packages_.add(loaded_package); |
| } break; |
| |
| default: |
| logWarning(String.format("Unknown chunk type '%02x'.", chunk.type())); |
| break; |
| } |
| } |
| |
| if (iter.HadError()) { |
| logError(iter.GetLastError()); |
| if (iter.HadFatalError()) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| // Read-only view into a resource table. This class validates all data |
| // when loading, including offsets and lengths. |
| //class LoadedArsc { |
| // public: |
| // Load a resource table from memory pointed to by `data` of size `len`. |
| // The lifetime of `data` must out-live the LoadedArsc returned from this method. |
| // If `system` is set to true, the LoadedArsc is considered as a system provided resource. |
| // If `load_as_shared_library` is set to true, the application package (0x7f) is treated |
| // as a shared library (0x00). When loaded into an AssetManager, the package will be assigned an |
| // ID. |
| static LoadedArsc Load(StringPiece data, |
| LoadedIdmap loaded_idmap /* = null */, boolean system /* = false */, |
| boolean load_as_shared_library /* = false */) { |
| // ATRACE_NAME("LoadedArsc::LoadTable"); |
| |
| // Not using make_unique because the constructor is private. |
| LoadedArsc loaded_arsc = new LoadedArsc(); |
| loaded_arsc.system_ = system; |
| |
| Chunk.Iterator iter = new Iterator(data, data.size()); |
| while (iter.HasNext()) { |
| Chunk chunk = iter.Next(); |
| switch (chunk.type()) { |
| case RES_TABLE_TYPE: |
| if (!loaded_arsc.LoadTable(chunk, loaded_idmap, load_as_shared_library)) { |
| return emptyBraces(); |
| } |
| break; |
| |
| default: |
| logWarning(String.format("Unknown chunk type '%02x'.", chunk.type())); |
| break; |
| } |
| } |
| |
| if (iter.HadError()) { |
| logError(iter.GetLastError()); |
| if (iter.HadFatalError()) { |
| return emptyBraces(); |
| } |
| } |
| |
| // Need to force a move for mingw32. |
| // return std.move(loaded_arsc); |
| return loaded_arsc; |
| } |
| |
| // Create an empty LoadedArsc. This is used when an APK has no resources.arsc. |
| static LoadedArsc CreateEmpty() { |
| return new LoadedArsc(); |
| } |
| |
| // Populates a set of ResTable_config structs, possibly excluding configurations defined for |
| // the mipmap type. |
| // void CollectConfigurations(boolean exclude_mipmap, Set<ResTable_config> out_configs); |
| |
| // Populates a set of strings representing locales. |
| // If `canonicalize` is set to true, each locale is transformed into its canonical format |
| // before being inserted into the set. This may cause some equivalent locales to de-dupe. |
| // void CollectLocales(boolean canonicalize, Set<String> out_locales); |
| |
| private static LoadedArsc emptyBraces() { |
| return new LoadedArsc(); |
| } |
| |
| } |