1 package org.robolectric.res.android; 2 3 import static org.robolectric.res.android.Errors.NO_ERROR; 4 import static org.robolectric.res.android.Errors.NO_INIT; 5 import static org.robolectric.res.android.ResourceTypes.RES_STRING_POOL_TYPE; 6 import static org.robolectric.res.android.ResourceTypes.RES_TABLE_LIBRARY_TYPE; 7 import static org.robolectric.res.android.ResourceTypes.RES_TABLE_PACKAGE_TYPE; 8 import static org.robolectric.res.android.ResourceTypes.RES_TABLE_STAGED_ALIAS_TYPE; 9 import static org.robolectric.res.android.ResourceTypes.RES_TABLE_TYPE; 10 import static org.robolectric.res.android.ResourceTypes.RES_TABLE_TYPE_SPEC_TYPE; 11 import static org.robolectric.res.android.ResourceTypes.RES_TABLE_TYPE_TYPE; 12 import static org.robolectric.res.android.ResourceTypes.kResTableTypeMinSize; 13 import static org.robolectric.res.android.ResourceUtils.make_resid; 14 import static org.robolectric.res.android.Util.UNLIKELY; 15 import static org.robolectric.res.android.Util.dtohl; 16 import static org.robolectric.res.android.Util.dtohs; 17 import static org.robolectric.res.android.Util.isTruthy; 18 import static org.robolectric.res.android.Util.logError; 19 import static org.robolectric.res.android.Util.logWarning; 20 21 import java.util.ArrayList; 22 import java.util.HashMap; 23 import java.util.HashSet; 24 import java.util.List; 25 import java.util.Map; 26 import java.util.Map.Entry; 27 import java.util.Set; 28 import org.robolectric.res.android.Chunk.Iterator; 29 import org.robolectric.res.android.Idmap.LoadedIdmap; 30 import org.robolectric.res.android.ResourceTypes.IdmapEntry_header; 31 import org.robolectric.res.android.ResourceTypes.ResStringPool_header; 32 import org.robolectric.res.android.ResourceTypes.ResTableStagedAliasEntry; 33 import org.robolectric.res.android.ResourceTypes.ResTableStagedAliasHeader; 34 import org.robolectric.res.android.ResourceTypes.ResTable_entry; 35 import org.robolectric.res.android.ResourceTypes.ResTable_header; 36 import org.robolectric.res.android.ResourceTypes.ResTable_lib_entry; 37 import org.robolectric.res.android.ResourceTypes.ResTable_lib_header; 38 import org.robolectric.res.android.ResourceTypes.ResTable_map; 39 import org.robolectric.res.android.ResourceTypes.ResTable_map_entry; 40 import org.robolectric.res.android.ResourceTypes.ResTable_package; 41 import org.robolectric.res.android.ResourceTypes.ResTable_sparseTypeEntry; 42 import org.robolectric.res.android.ResourceTypes.ResTable_type; 43 import org.robolectric.res.android.ResourceTypes.ResTable_typeSpec; 44 import org.robolectric.res.android.ResourceTypes.Res_value; 45 46 // transliterated from 47 // https://android.googlesource.com/platform/frameworks/base/+/android-9.0.0_r12/libs/androidfw/include/androidfw/LoadedArsc.h 48 // and 49 // https://android.googlesource.com/platform/frameworks/base/+/android-9.0.0_r12/libs/androidfw/LoadedArsc.cpp 50 public class LoadedArsc { 51 52 // #ifndef LOADEDARSC_H_ 53 // #define LOADEDARSC_H_ 54 // 55 // #include <memory> 56 // #include <set> 57 // #include <vector> 58 // 59 // #include "android-base/macros.h" 60 // 61 // #include "androidfw/ByteBucketArray.h" 62 // #include "androidfw/Chunk.h" 63 // #include "androidfw/ResourceTypes.h" 64 // #include "androidfw/Util.h" 65 // 66 // namespace android { 67 // 68 69 private static final int kFrameworkPackageId = 0x01; 70 71 static class DynamicPackageEntry { 72 73 // public: 74 // 75 // DynamicPackageEntry() =default; 76 DynamicPackageEntry(String package_name, int package_id)77 DynamicPackageEntry(String package_name, int package_id) { 78 this.package_name = package_name; 79 this.package_id = package_id; 80 } 81 82 String package_name; 83 int package_id = 0; 84 } 85 86 // TypeSpec is going to be immediately proceeded by 87 // an array of Type structs, all in the same block of memory. 88 static class TypeSpec { 89 90 public static final int SIZEOF = ResTable_typeSpec.SIZEOF + IdmapEntry_header.SIZEOF; 91 92 // Pointer to the mmapped data where flags are kept. 93 // Flags denote whether the resource entry is public 94 // and under which configurations it varies. 95 ResTable_typeSpec type_spec; 96 97 // Pointer to the mmapped data where the IDMAP mappings for this type 98 // exist. May be nullptr if no IDMAP exists. 99 IdmapEntry_header idmap_entries; 100 101 // The number of types that follow this struct. 102 // There is a type for each configuration that entries are defined for. 103 int type_count; 104 105 // Trick to easily access a variable number of Type structs 106 // proceeding this struct, and to ensure their alignment. 107 // ResTable_type* types[0]; 108 ResTable_type[] types; 109 GetFlagsForEntryIndex(int entry_index)110 int GetFlagsForEntryIndex(int entry_index) { 111 if (entry_index >= dtohl(type_spec.entryCount)) { 112 return 0; 113 } 114 115 // uint32_t* flags = reinterpret_cast<uint32_t*>(type_spec + 1); 116 int[] flags = type_spec.getSpecFlags(); 117 return flags[entry_index]; 118 } 119 } 120 121 // Returns the string pool where all string resource values 122 // (Res_value::dataType == Res_value::TYPE_STRING) are indexed. GetStringPool()123 public ResStringPool GetStringPool() { 124 return global_string_pool_; 125 } 126 127 // Returns a vector of LoadedPackage pointers, representing the packages in this LoadedArsc. GetPackages()128 List<LoadedPackage> GetPackages() { 129 return packages_; 130 } 131 132 // Returns true if this is a system provided resource. IsSystem()133 boolean IsSystem() { 134 return system_; 135 } 136 137 // 138 // private: 139 // DISALLOW_COPY_AND_ASSIGN(LoadedArsc); 140 // 141 // LoadedArsc() = default; 142 // bool LoadTable(const Chunk& chunk, const LoadedIdmap* loaded_idmap, bool 143 // load_as_shared_library); 144 // 145 final ResStringPool global_string_pool_ = new ResStringPool(); 146 final List<LoadedPackage> packages_ = new ArrayList<>(); 147 boolean system_ = false; 148 // }; 149 // 150 // } // namespace android 151 // 152 // #endif // LOADEDARSC_H_ 153 154 // #define ATRACE_TAG ATRACE_TAG_RESOURCES 155 // 156 // #include "androidfw/LoadedArsc.h" 157 // 158 // #include <cstddef> 159 // #include <limits> 160 // 161 // #include "android-base/logging.h" 162 // #include "android-base/stringprintf.h" 163 // #include "utils/ByteOrder.h" 164 // #include "utils/Trace.h" 165 // 166 // #ifdef _WIN32 167 // #ifdef ERROR 168 // #undef ERROR 169 // #endif 170 // #endif 171 // 172 // #include "androidfw/ByteBucketArray.h" 173 // #include "androidfw/Chunk.h" 174 // #include "androidfw/ResourceUtils.h" 175 // #include "androidfw/Util.h" 176 // 177 // using android::base::StringPrintf; 178 // 179 // namespace android { 180 181 static final int kAppPackageId = 0x7f; 182 183 // namespace { 184 185 // Builder that helps accumulate Type structs and then create a single 186 // contiguous block of memory to store both the TypeSpec struct and 187 // the Type structs. 188 static class TypeSpecPtrBuilder { 189 // public: TypeSpecPtrBuilder(ResTable_typeSpec header, IdmapEntry_header idmap_header)190 TypeSpecPtrBuilder(ResTable_typeSpec header, IdmapEntry_header idmap_header) { 191 this.header_ = header; 192 this.idmap_header_ = idmap_header; 193 } 194 AddType(ResTable_type type)195 void AddType(ResTable_type type) { 196 types_.add(type); 197 } 198 Build()199 TypeSpec Build() { 200 // Check for overflow. 201 // using ElementType = ResTable_type*; 202 // if ((std.numeric_limits<size_t>.max() - sizeof(TypeSpec)) / sizeof(ElementType) < 203 // types_.size()) { 204 if ((Integer.MAX_VALUE - TypeSpec.SIZEOF) / 4 < types_.size()) { 205 return null; // {} ; 206 } 207 // TypeSpec* type_spec = 208 // (TypeSpec*).malloc(sizeof(TypeSpec) + (types_.size() * sizeof(ElementType))); 209 TypeSpec type_spec = new TypeSpec(); 210 type_spec.types = new ResTable_type[types_.size()]; 211 type_spec.type_spec = header_; 212 type_spec.idmap_entries = idmap_header_; 213 type_spec.type_count = types_.size(); 214 // memcpy(type_spec + 1, types_.data(), types_.size() * sizeof(ElementType)); 215 for (int i = 0; i < type_spec.types.length; i++) { 216 type_spec.types[i] = types_.get(i); 217 } 218 return type_spec; 219 } 220 221 // private: 222 // DISALLOW_COPY_AND_ASSIGN(TypeSpecPtrBuilder); 223 224 ResTable_typeSpec header_; 225 IdmapEntry_header idmap_header_; 226 final List<ResTable_type> types_ = new ArrayList<>(); 227 } 228 ; 229 230 // } // namespace 231 232 // Precondition: The header passed in has already been verified, so reading any fields and 233 // trusting 234 // the ResChunk_header is safe. VerifyResTableType(ResTable_type header)235 static boolean VerifyResTableType(ResTable_type header) { 236 if (header.id == 0) { 237 logError("RES_TABLE_TYPE_TYPE has invalid ID 0."); 238 return false; 239 } 240 241 int entry_count = dtohl(header.entryCount); 242 // if (entry_count > std.numeric_limits<uint16_t>.max()) { 243 if (entry_count > 0xffff) { 244 logError("RES_TABLE_TYPE_TYPE has too many entries (" + entry_count + ")."); 245 return false; 246 } 247 248 // Make sure that there is enough room for the entry offsets. 249 int offsets_offset = dtohs(header.header.headerSize); 250 int entries_offset = dtohl(header.entriesStart); 251 int bytesPerEntry = isTruthy(header.flags & ResTable_type.FLAG_OFFSET16) ? 2 : 4; 252 int offsetsLength = bytesPerEntry * entry_count; 253 if (offsets_offset > entries_offset || entries_offset - offsets_offset < offsetsLength) { 254 logError("RES_TABLE_TYPE_TYPE entry offsets overlap actual entry data."); 255 return false; 256 } 257 258 if (entries_offset > dtohl(header.header.size)) { 259 logError("RES_TABLE_TYPE_TYPE entry offsets extend beyond chunk."); 260 return false; 261 } 262 263 if (isTruthy(entries_offset & 0x03)) { 264 logError("RES_TABLE_TYPE_TYPE entries start at unaligned address."); 265 return false; 266 } 267 return true; 268 } 269 VerifyResTableEntry(ResTable_type type, int entry_offset)270 static boolean VerifyResTableEntry(ResTable_type type, int entry_offset) { 271 // Check that the offset is aligned. 272 if (isTruthy(entry_offset & 0x03)) { 273 logError("Entry at offset " + entry_offset + " is not 4-byte aligned."); 274 return false; 275 } 276 277 // Check that the offset doesn't overflow. 278 // if (entry_offset > std.numeric_limits<int>.max() - dtohl(type.entriesStart)) { 279 if (entry_offset > Integer.MAX_VALUE - dtohl(type.entriesStart)) { 280 // Overflow in offset. 281 logError("Entry at offset " + entry_offset + " is too large."); 282 return false; 283 } 284 285 int chunk_size = dtohl(type.header.size); 286 287 entry_offset += dtohl(type.entriesStart); 288 if (entry_offset > chunk_size - ResTable_entry.SIZEOF) { 289 logError("Entry at offset " + entry_offset + " is too large. No room for ResTable_entry."); 290 return false; 291 } 292 293 // ResTable_entry* entry = reinterpret_cast<ResTable_entry*>( 294 // reinterpret_cast<uint8_t*>(type) + entry_offset); 295 ResTable_entry entry = new ResTable_entry(type.myBuf(), type.myOffset() + entry_offset); 296 297 int entrySize = entry.isCompact() ? ResTable_entry.SIZEOF : dtohs(entry.size); 298 if (entrySize < ResTable_entry.SIZEOF) { 299 logError( 300 "ResTable_entry size " + entrySize + " at offset " + entry_offset + " is too small."); 301 return false; 302 } 303 304 if (entrySize > chunk_size || entry_offset > chunk_size - entrySize) { 305 logError( 306 "ResTable_entry size " + entrySize + " at offset " + entry_offset + " is too large."); 307 return false; 308 } 309 310 // No further validations apply if the entry is compact. 311 if (entry.isCompact()) { 312 return true; 313 } 314 315 if (entrySize < ResTable_map_entry.BASE_SIZEOF) { 316 // There needs to be room for one Res_value struct. 317 if (entry_offset + entrySize > chunk_size - Res_value.SIZEOF) { 318 logError( 319 "No room for Res_value after ResTable_entry at offset " 320 + entry_offset 321 + " for type " 322 + (int) type.id 323 + "."); 324 return false; 325 } 326 327 // Res_value value = 328 // reinterpret_cast<Res_value*>(reinterpret_cast<uint8_t*>(entry) + entry_size); 329 Res_value value = entry.getResValue(); 330 int value_size = dtohs(value.size); 331 if (value_size < Res_value.SIZEOF) { 332 logError("Res_value at offset " + entry_offset + " is too small."); 333 return false; 334 } 335 336 if (value_size > chunk_size || entry_offset + entrySize > chunk_size - value_size) { 337 logError("Res_value size " + value_size + " at offset " + entry_offset + " is too large."); 338 return false; 339 } 340 } else { 341 ResTable_map_entry map = new ResTable_map_entry(entry.myBuf(), entry.myOffset()); 342 int map_entry_count = dtohl(map.count); 343 int mapEntriesStart = entry_offset + entrySize; 344 if (isTruthy(mapEntriesStart & 0x03)) { 345 logError("Map entries at offset " + entry_offset + " start at unaligned offset."); 346 return false; 347 } 348 349 // Each entry is sizeof(ResTable_map) big. 350 if (map_entry_count > ((chunk_size - mapEntriesStart) / ResTable_map.SIZEOF)) { 351 logError("Too many map entries in ResTable_map_entry at offset " + entry_offset + "."); 352 return false; 353 } 354 } 355 return true; 356 } 357 358 static class LoadedPackage { 359 // private: 360 361 // DISALLOW_COPY_AND_ASSIGN(LoadedPackage); 362 363 // LoadedPackage(); 364 365 ResStringPool type_string_pool_ = new ResStringPool(); 366 ResStringPool key_string_pool_ = new ResStringPool(); 367 String package_name_; 368 int package_id_ = -1; 369 int type_id_offset_ = 0; 370 boolean dynamic_ = false; 371 boolean system_ = false; 372 boolean overlay_ = false; 373 374 // final ByteBucketArray<TypeSpec> type_specs_ = new ByteBucketArray<TypeSpec>() { 375 // @Override 376 // TypeSpec newInstance() { 377 // return new TypeSpec(); 378 // } 379 // }; 380 final Map<Integer, TypeSpec> type_specs_ = new HashMap<>(); 381 final List<DynamicPackageEntry> dynamic_package_map_ = new ArrayList<>(); 382 final Map<Integer, Integer> aliasIdMap = new HashMap<>(); 383 GetEntry(ResTable_type type_chunk, short entry_index)384 ResTable_entry GetEntry(ResTable_type type_chunk, short entry_index) { 385 int entry_offset = GetEntryOffset(type_chunk, entry_index); 386 if (entry_offset == ResTable_type.NO_ENTRY) { 387 return null; 388 } 389 return GetEntryFromOffset(type_chunk, entry_offset); 390 } 391 GetEntryOffset(ResTable_type type_chunk, int entry_index)392 static int GetEntryOffset(ResTable_type type_chunk, int entry_index) { 393 // The configuration matches and is better than the previous selection. 394 // Find the entry value if it exists for this configuration. 395 int entry_count = dtohl(type_chunk.entryCount); 396 int offsets_offset = dtohs(type_chunk.header.headerSize); 397 398 // Check if there is the desired entry in this type. 399 400 if (isTruthy(type_chunk.flags & ResTable_type.FLAG_SPARSE)) { 401 // This is encoded as a sparse map, so perform a binary search. 402 // ResTable_sparseTypeEntry sparse_indices = 403 // reinterpret_cast<ResTable_sparseTypeEntry*>( 404 // reinterpret_cast<uint8_t*>(type_chunk) + offsets_offset); 405 // ResTable_sparseTypeEntry* sparse_indices_end = sparse_indices + entry_count; 406 // ResTable_sparseTypeEntry* result = 407 // std.lower_bound(sparse_indices, sparse_indices_end, entry_index, 408 // [](ResTable_sparseTypeEntry& entry, short entry_idx) { 409 // return dtohs(entry.idx) < entry_idx; 410 // }); 411 ResTable_sparseTypeEntry result = null; 412 for (int i = 0; i < entry_count; i++) { 413 ResTable_sparseTypeEntry entry = 414 new ResTable_sparseTypeEntry( 415 type_chunk.myBuf(), 416 type_chunk.myOffset() + offsets_offset + i * ResTable_sparseTypeEntry.SIZEOF); 417 if (entry.idx >= entry_index) { 418 result = entry; 419 break; 420 } 421 } 422 423 if (result == null || dtohs(result.idx) != entry_index) { 424 // No entry found. 425 return ResTable_type.NO_ENTRY; 426 } 427 428 // Extract the offset from the entry. Each offset must be a multiple of 4 so we store it as 429 // the real offset divided by 4. 430 // return int{dtohs(result.offset)} * 4u; 431 return dtohs(result.offset) * 4; 432 } 433 434 // This type is encoded as a dense array. 435 if (entry_index >= entry_count) { 436 // This entry cannot be here. 437 return ResTable_type.NO_ENTRY; 438 } 439 440 // int* entry_offsets = reinterpret_cast<int*>( 441 // reinterpret_cast<uint8_t*>(type_chunk) + offsets_offset); 442 // return dtohl(entry_offsets[entry_index]); 443 return dtohl(type_chunk.entryOffset(entry_index)); 444 } 445 GetEntryFromOffset(ResTable_type type_chunk, int offset)446 static ResTable_entry GetEntryFromOffset(ResTable_type type_chunk, int offset) { 447 if (UNLIKELY(!VerifyResTableEntry(type_chunk, offset))) { 448 return null; 449 } 450 // return reinterpret_cast<ResTable_entry*>(reinterpret_cast<uint8_t*>(type_chunk) + 451 // offset + dtohl(type_chunk.entriesStart)); 452 return new ResTable_entry( 453 type_chunk.myBuf(), type_chunk.myOffset() + offset + dtohl(type_chunk.entriesStart)); 454 } 455 CollectConfigurations(boolean exclude_mipmap, Set<ResTable_config> out_configs)456 void CollectConfigurations(boolean exclude_mipmap, Set<ResTable_config> out_configs) { 457 String kMipMap = "mipmap"; 458 int type_count = type_specs_.size(); 459 for (int i = 0; i < type_count; i++) { 460 TypeSpec type_spec = type_specs_.get(i); 461 if (type_spec != null) { 462 if (exclude_mipmap) { 463 int type_idx = type_spec.type_spec.id - 1; 464 final Ref<Integer> type_name_len = new Ref<>(0); 465 String type_name16 = type_string_pool_.stringAt(type_idx, type_name_len); 466 if (type_name16 != null) { 467 // if (kMipMap.compare(0, std::u16string::npos,type_name16, type_name_len) ==0){ 468 if (kMipMap.equals(type_name16)) { 469 // This is a mipmap type, skip collection. 470 continue; 471 } 472 } 473 String type_name = type_string_pool_.string8At(type_idx, type_name_len); 474 if (type_name != null) { 475 // if (strncmp(type_name, "mipmap", type_name_len) == 0) { 476 if ("mipmap".equals(type_name)) 477 // This is a mipmap type, skip collection. 478 continue; 479 } 480 } 481 } 482 483 for (ResTable_type iter : type_spec.types) { 484 ResTable_config config = ResTable_config.fromDtoH(iter.config); 485 out_configs.add(config); 486 } 487 } 488 } 489 CollectLocales(boolean canonicalize, Set<String> out_locales)490 void CollectLocales(boolean canonicalize, Set<String> out_locales) { 491 // char temp_locale[ RESTABLE_MAX_LOCALE_LEN]; 492 String temp_locale; 493 int type_count = type_specs_.size(); 494 for (int i = 0; i < type_count; i++) { 495 TypeSpec type_spec = type_specs_.get(i); 496 if (type_spec != null) { 497 for (ResTable_type iter : type_spec.types) { 498 ResTable_config configuration = ResTable_config.fromDtoH(iter.config); 499 if (configuration.locale() != 0) { 500 temp_locale = configuration.getBcp47Locale(canonicalize); 501 String locale = temp_locale; 502 out_locales.add(locale); 503 } 504 } 505 } 506 } 507 } 508 509 // Finds the entry with the specified type name and entry name. The names are in UTF-16 because 510 // the underlying ResStringPool API expects this. For now this is acceptable, but since 511 // the default policy in AAPT2 is to build UTF-8 string pools, this needs to change. 512 // Returns a partial resource ID, with the package ID left as 0x00. The caller is responsible 513 // for patching the correct package ID to the resource ID. FindEntryByName(String type_name, String entry_name)514 int FindEntryByName(String type_name, String entry_name) { 515 int type_idx = type_string_pool_.indexOfString(type_name); 516 if (type_idx < 0) { 517 return 0; 518 } 519 520 int key_idx = key_string_pool_.indexOfString(entry_name); 521 if (key_idx < 0) { 522 return 0; 523 } 524 525 TypeSpec type_spec = type_specs_.get(type_idx); 526 if (type_spec == null) { 527 return 0; 528 } 529 530 // for (const auto& type_entry : type_spec->type_entries) { 531 for (ResTable_type iter : type_spec.types) { 532 // const incfs::verified_map_ptr<ResTable_type>& type = type_entry.type; 533 ResTable_type type = iter; 534 // const size_t entry_count = dtohl(type->entryCount); 535 int entry_count = type.entryCount; 536 // const auto entry_offsets = type.offset(dtohs(type->header.headerSize)); 537 538 // for (size_t entry_idx = 0; entry_idx < entry_count; entry_idx++) { 539 for (int entry_idx = 0; entry_idx < entry_count; entry_idx++) { 540 // uint32_t offset; 541 int offset; 542 // uint16_t res_idx; 543 short res_idx; 544 // if (type->flags & ResTable_type::FLAG_SPARSE) { 545 if (isTruthy(type.flags & ResTable_type.FLAG_SPARSE)) { 546 // auto sparse_entry = entry_offsets.convert<ResTable_sparseTypeEntry>() + entry_idx; 547 548 ResTable_sparseTypeEntry sparse_entry = 549 new ResTable_sparseTypeEntry( 550 type.myBuf(), type.myOffset() + entry_idx * ResTable_sparseTypeEntry.SIZEOF); 551 // if (!sparse_entry) { 552 // return base::unexpected(IOError::PAGES_MISSING); 553 // } 554 // TODO: implement above 555 // offset = dtohs(sparse_entry->offset) * 4u; 556 offset = dtohs(sparse_entry.offset) * 4; 557 // res_idx = dtohs(sparse_entry->idx); 558 res_idx = dtohs(sparse_entry.idx); 559 // } else if (type->flags & ResTable_type::FLAG_OFFSET16) { 560 } else if (isTruthy(type.flags & ResTable_type.FLAG_OFFSET16)) { 561 // auto entry = entry_offsets.convert<uint16_t>() + entry_idx; 562 int entry = type.entryOffset(entry_idx); 563 // if (!entry) { 564 // return base::unexpected(IOError::PAGES_MISSING); 565 // } 566 // offset = offset_from16(entry.value()); 567 offset = entry; 568 // res_idx = entry_idx; 569 res_idx = (short) entry_idx; 570 } else { 571 // auto entry = entry_offsets.convert<uint32_t>() + entry_idx; 572 int entry = type.entryOffset(entry_idx); 573 // if (!entry) { 574 // return base::unexpected(IOError::PAGES_MISSING); 575 // } 576 // offset = dtohl(entry.value()); 577 offset = dtohl(entry); 578 res_idx = (short) entry_idx; 579 } 580 581 if (offset != ResTable_type.NO_ENTRY) { 582 // auto entry = type.offset(dtohl(type->entriesStart) + 583 // offset).convert<ResTable_entry>(); 584 ResTable_entry entry = 585 new ResTable_entry( 586 type.myBuf(), type.myOffset() + dtohl(type.entriesStart) + offset); 587 // if (!entry) { 588 // return base::unexpected(IOError::PAGES_MISSING); 589 // } 590 // TODO implement above 591 // if (entry->key() == static_cast<uint32_t>(*key_idx)) { 592 if (dtohl(entry.getKeyIndex()) == key_idx) { 593 // The package ID will be overridden by the caller (due to runtime assignment of 594 // package 595 // IDs for shared libraries). 596 // return make_resid(0x00, *type_idx + type_id_offset_ + 1, res_idx); 597 return make_resid((byte) 0x00, (byte) (type_idx + type_id_offset_ + 1), res_idx); 598 } 599 } 600 } 601 } 602 // return base::unexpected(std::nullopt); 603 return 0; 604 } 605 Load( Chunk chunk, LoadedIdmap loaded_idmap, boolean system, boolean load_as_shared_library)606 static LoadedPackage Load( 607 Chunk chunk, LoadedIdmap loaded_idmap, boolean system, boolean load_as_shared_library) { 608 // ATRACE_NAME("LoadedPackage::Load"); 609 LoadedPackage loaded_package = new LoadedPackage(); 610 611 // typeIdOffset was added at some point, but we still must recognize apps built before this 612 // was added. 613 // constexpr int kMinPackageSize = 614 // sizeof(ResTable_package) - sizeof(ResTable_package.typeIdOffset); 615 final int kMinPackageSize = ResTable_package.SIZEOF - 4; 616 // ResTable_package header = chunk.header<ResTable_package, kMinPackageSize>(); 617 ResTable_package header = chunk.asResTable_package(kMinPackageSize); 618 if (header == null) { 619 logError("RES_TABLE_PACKAGE_TYPE too small."); 620 return emptyBraces(); 621 } 622 623 loaded_package.system_ = system; 624 625 loaded_package.package_id_ = dtohl(header.id); 626 if (loaded_package.package_id_ == 0 627 || (loaded_package.package_id_ == kAppPackageId && load_as_shared_library)) { 628 // Package ID of 0 means this is a shared library. 629 loaded_package.dynamic_ = true; 630 } 631 632 if (loaded_idmap != null) { 633 // This is an overlay and so it needs to pretend to be the target package. 634 loaded_package.package_id_ = loaded_idmap.TargetPackageId(); 635 loaded_package.overlay_ = true; 636 } 637 638 if (header.header.headerSize >= ResTable_package.SIZEOF) { 639 int type_id_offset = dtohl(header.typeIdOffset); 640 // if (type_id_offset > std.numeric_limits<uint8_t>.max()) { 641 if (type_id_offset > 255) { 642 logError("RES_TABLE_PACKAGE_TYPE type ID offset too large."); 643 return emptyBraces(); 644 } 645 loaded_package.type_id_offset_ = type_id_offset; 646 } 647 648 loaded_package.package_name_ = 649 Util.ReadUtf16StringFromDevice(header.name, header.name.length); 650 651 // A map of TypeSpec builders, each associated with an type index. 652 // We use these to accumulate the set of Types available for a TypeSpec, and later build a 653 // single, 654 // contiguous block of memory that holds all the Types together with the TypeSpec. 655 Map<Integer, TypeSpecPtrBuilder> type_builder_map = new HashMap<>(); 656 657 Chunk.Iterator iter = new Iterator(chunk.data_ptr(), chunk.data_size()); 658 while (iter.HasNext()) { 659 Chunk child_chunk = iter.Next(); 660 switch (child_chunk.type()) { 661 case RES_STRING_POOL_TYPE: 662 { 663 // uintptr_t pool_address = 664 // reinterpret_cast<uintptr_t>(child_chunk.header<ResChunk_header>()); 665 // uintptr_t header_address = reinterpret_cast<uintptr_t>(header); 666 int pool_address = child_chunk.myOffset(); 667 int header_address = header.myOffset(); 668 if (pool_address == header_address + dtohl(header.typeStrings)) { 669 // This string pool is the type string pool. 670 int err = 671 loaded_package.type_string_pool_.setTo( 672 child_chunk.myBuf(), child_chunk.myOffset(), child_chunk.size(), false); 673 if (err != NO_ERROR) { 674 logError("RES_STRING_POOL_TYPE for types corrupt."); 675 return emptyBraces(); 676 } 677 } else if (pool_address == header_address + dtohl(header.keyStrings)) { 678 // This string pool is the key string pool. 679 int err = 680 loaded_package.key_string_pool_.setTo( 681 child_chunk.myBuf(), child_chunk.myOffset(), child_chunk.size(), false); 682 if (err != NO_ERROR) { 683 logError("RES_STRING_POOL_TYPE for keys corrupt."); 684 return emptyBraces(); 685 } 686 } else { 687 logWarning("Too many RES_STRING_POOL_TYPEs found in RES_TABLE_PACKAGE_TYPE."); 688 } 689 } 690 break; 691 692 case RES_TABLE_TYPE_SPEC_TYPE: 693 { 694 ResTable_typeSpec type_spec = 695 new ResTable_typeSpec(child_chunk.myBuf(), child_chunk.myOffset()); 696 if (type_spec == null) { 697 logError("RES_TABLE_TYPE_SPEC_TYPE too small."); 698 return emptyBraces(); 699 } 700 701 if (type_spec.id == 0) { 702 logError("RES_TABLE_TYPE_SPEC_TYPE has invalid ID 0."); 703 return emptyBraces(); 704 } 705 706 // if (loaded_package.type_id_offset_ + static_cast<int>(type_spec.id) > 707 // std.numeric_limits<uint8_t>.max()) { 708 if (loaded_package.type_id_offset_ + type_spec.id > 255) { 709 logError("RES_TABLE_TYPE_SPEC_TYPE has out of range ID."); 710 return emptyBraces(); 711 } 712 713 // The data portion of this chunk contains entry_count 32bit entries, 714 // each one representing a set of flags. 715 // Here we only validate that the chunk is well formed. 716 int entry_count = dtohl(type_spec.entryCount); 717 718 // There can only be 2^16 entries in a type, because that is the ID 719 // space for entries (EEEE) in the resource ID 0xPPTTEEEE. 720 // if (entry_count > std.numeric_limits<short>.max()) { 721 if (entry_count > 0xffff) { 722 logError("RES_TABLE_TYPE_SPEC_TYPE has too many entries (" + entry_count + ")."); 723 return emptyBraces(); 724 } 725 726 if (entry_count * 4 /*sizeof(int)*/ > chunk.data_size()) { 727 logError("RES_TABLE_TYPE_SPEC_TYPE too small to hold entries."); 728 return emptyBraces(); 729 } 730 731 // If this is an overlay, associate the mapping of this type to the target type 732 // from the IDMAP. 733 IdmapEntry_header idmap_entry_header = null; 734 if (loaded_idmap != null) { 735 idmap_entry_header = loaded_idmap.GetEntryMapForType(type_spec.id); 736 } 737 738 TypeSpecPtrBuilder builder_ptr = type_builder_map.get(type_spec.id - 1); 739 if (builder_ptr == null) { 740 // builder_ptr = util.make_unique<TypeSpecPtrBuilder>(type_spec, 741 // idmap_entry_header); 742 builder_ptr = new TypeSpecPtrBuilder(type_spec, idmap_entry_header); 743 type_builder_map.put(type_spec.id - 1, builder_ptr); 744 } else { 745 logWarning( 746 String.format( 747 "RES_TABLE_TYPE_SPEC_TYPE already defined for ID %02x", type_spec.id)); 748 } 749 } 750 break; 751 752 case RES_TABLE_TYPE_TYPE: 753 { 754 // ResTable_type type = child_chunk.header<ResTable_type, kResTableTypeMinSize>(); 755 ResTable_type type = child_chunk.asResTable_type(kResTableTypeMinSize); 756 if (type == null) { 757 logError("RES_TABLE_TYPE_TYPE too small."); 758 return emptyBraces(); 759 } 760 761 if (!VerifyResTableType(type)) { 762 return emptyBraces(); 763 } 764 765 // Type chunks must be preceded by their TypeSpec chunks. 766 TypeSpecPtrBuilder builder_ptr = type_builder_map.get(type.id - 1); 767 if (builder_ptr != null) { 768 builder_ptr.AddType(type); 769 } else { 770 logError( 771 String.format( 772 "RES_TABLE_TYPE_TYPE with ID %02x found without preceding" 773 + " RES_TABLE_TYPE_SPEC_TYPE.", 774 type.id)); 775 return emptyBraces(); 776 } 777 } 778 break; 779 780 case RES_TABLE_LIBRARY_TYPE: 781 { 782 ResTable_lib_header lib = child_chunk.asResTable_lib_header(); 783 if (lib == null) { 784 logError("RES_TABLE_LIBRARY_TYPE too small."); 785 return emptyBraces(); 786 } 787 788 if (child_chunk.data_size() / ResTable_lib_entry.SIZEOF < dtohl(lib.count)) { 789 logError("RES_TABLE_LIBRARY_TYPE too small to hold entries."); 790 return emptyBraces(); 791 } 792 793 // loaded_package.dynamic_package_map_.reserve(dtohl(lib.count)); 794 795 // ResTable_lib_entry entryBegin = 796 // reinterpret_cast<ResTable_lib_entry*>(child_chunk.data_ptr()); 797 ResTable_lib_entry entryBegin = child_chunk.asResTable_lib_entry(); 798 // ResTable_lib_entry entry_end = entryBegin + dtohl(lib.count); 799 // for (auto entryIter = entryBegin; entryIter != entry_end; ++entryIter) { 800 for (ResTable_lib_entry entryIter = entryBegin; 801 entryIter.myOffset() != entryBegin.myOffset() + dtohl(lib.count); 802 entryIter = 803 new ResTable_lib_entry( 804 entryIter.myBuf(), entryIter.myOffset() + ResTable_lib_entry.SIZEOF)) { 805 String package_name = 806 Util.ReadUtf16StringFromDevice( 807 entryIter.packageName, entryIter.packageName.length); 808 809 if (dtohl(entryIter.packageId) >= 255) { 810 logError( 811 String.format( 812 "Package ID %02x in RES_TABLE_LIBRARY_TYPE too large for package '%s'.", 813 dtohl(entryIter.packageId), package_name)); 814 return emptyBraces(); 815 } 816 817 // loaded_package.dynamic_package_map_.emplace_back(std.move(package_name), 818 // dtohl(entryIter.packageId)); 819 loaded_package.dynamic_package_map_.add( 820 new DynamicPackageEntry(package_name, dtohl(entryIter.packageId))); 821 } 822 } 823 break; 824 825 case RES_TABLE_STAGED_ALIAS_TYPE: 826 { 827 if (loaded_package.package_id_ != kFrameworkPackageId) { 828 logWarning( 829 String.format( 830 "Alias chunk ignored for non-framework package '%s'", 831 loaded_package.package_name_)); 832 break; 833 } 834 835 ResTableStagedAliasHeader libAlias = child_chunk.asResTableStagedAliasHeader(); 836 if (libAlias == null) { 837 logError("RES_TABLE_STAGED_ALIAS_TYPE is too small."); 838 return emptyBraces(); 839 } 840 if ((child_chunk.data_size() / ResTableStagedAliasEntry.SIZEOF) 841 < dtohl(libAlias.count)) { 842 logError("RES_TABLE_STAGED_ALIAS_TYPE is too small to hold entries."); 843 return emptyBraces(); 844 } 845 846 // const auto entryBegin = 847 // child_chunk.data_ptr().convert<ResTableStagedAliasEntry>(); 848 // const auto entry_end = entryBegin + dtohl(libAlias.count); 849 ResTableStagedAliasEntry entryBegin = child_chunk.asResTableStagedAliasEntry(); 850 int entryEndOffset = 851 entryBegin.myOffset() + dtohl(libAlias.count) * ResTableStagedAliasEntry.SIZEOF; 852 // std::unordered_set<uint32_t> finalizedIds; 853 // finalizedIds.reserve(entry_end - entryBegin); 854 Set<Integer> finalizedIds = new HashSet<>(); 855 for (ResTableStagedAliasEntry entryIter = entryBegin; 856 entryIter.myOffset() != entryEndOffset; 857 entryIter = 858 new ResTableStagedAliasEntry( 859 entryIter.myBuf(), 860 entryIter.myOffset() + ResTableStagedAliasEntry.SIZEOF)) { 861 862 int finalizedId = dtohl(entryIter.finalizedResId); 863 // if (!finalizedIds.insert(finalizedId).second) { 864 if (!finalizedIds.add(finalizedId)) { 865 logError( 866 String.format( 867 "Repeated finalized resource id '%08x' in staged aliases.", finalizedId)); 868 return emptyBraces(); 869 } 870 871 int stagedId = dtohl(entryIter.stagedResId); 872 // auto [_, success] = loaded_package->aliasIdMap.emplace(stagedId, 873 // finalizedId); 874 Integer previousValue = loaded_package.aliasIdMap.put(stagedId, finalizedId); 875 if (previousValue != null) { 876 logError( 877 String.format( 878 "Repeated staged resource id '%08x' in staged aliases.", stagedId)); 879 return emptyBraces(); 880 } 881 } 882 } 883 break; 884 885 default: 886 logWarning(String.format("Unknown chunk type '%02x'.", chunk.type())); 887 break; 888 } 889 } 890 891 if (iter.HadError()) { 892 logError(iter.GetLastError()); 893 if (iter.HadFatalError()) { 894 return emptyBraces(); 895 } 896 } 897 898 // Flatten and construct the TypeSpecs. 899 for (Entry<Integer, TypeSpecPtrBuilder> entry : type_builder_map.entrySet()) { 900 byte type_idx = (byte) entry.getKey().byteValue(); 901 TypeSpec type_spec_ptr = entry.getValue().Build(); 902 if (type_spec_ptr == null) { 903 logError("Too many type configurations, overflow detected."); 904 return emptyBraces(); 905 } 906 907 // We only add the type to the package if there is no IDMAP, or if the type is 908 // overlaying something. 909 if (loaded_idmap == null || type_spec_ptr.idmap_entries != null) { 910 // If this is an overlay, insert it at the target type ID. 911 if (type_spec_ptr.idmap_entries != null) { 912 type_idx = (byte) (dtohs(type_spec_ptr.idmap_entries.target_type_id) - 1); 913 } 914 // loaded_package.type_specs_.editItemAt(type_idx) = std.move(type_spec_ptr); 915 loaded_package.type_specs_.put((int) type_idx, type_spec_ptr); 916 } 917 } 918 919 // return std.move(loaded_package); 920 return loaded_package; 921 } 922 923 // Returns the string pool where type names are stored. GetTypeStringPool()924 ResStringPool GetTypeStringPool() { 925 return type_string_pool_; 926 } 927 928 // Returns the string pool where the names of resource entries are stored. GetKeyStringPool()929 ResStringPool GetKeyStringPool() { 930 return key_string_pool_; 931 } 932 GetPackageName()933 String GetPackageName() { 934 return package_name_; 935 } 936 GetPackageId()937 int GetPackageId() { 938 return package_id_; 939 } 940 941 // Returns true if this package is dynamic (shared library) and needs to have an ID assigned. IsDynamic()942 boolean IsDynamic() { 943 return dynamic_; 944 } 945 946 // Returns true if this package originates from a system provided resource. IsSystem()947 boolean IsSystem() { 948 return system_; 949 } 950 951 // Returns true if this package is from an overlay ApkAssets. IsOverlay()952 boolean IsOverlay() { 953 return overlay_; 954 } 955 956 // Returns the map of package name to package ID used in this LoadedPackage. At runtime, a 957 // package could have been assigned a different package ID than what this LoadedPackage was 958 // compiled with. AssetManager rewrites the package IDs so that they are compatible at runtime. GetDynamicPackageMap()959 List<DynamicPackageEntry> GetDynamicPackageMap() { 960 return dynamic_package_map_; 961 } 962 963 // type_idx is TT - 1 from 0xPPTTEEEE. GetTypeSpecByTypeIndex(int type_index)964 TypeSpec GetTypeSpecByTypeIndex(int type_index) { 965 // If the type IDs are offset in this package, we need to take that into account when 966 // searching 967 // for a type. 968 return type_specs_.get(type_index - type_id_offset_); 969 } 970 971 // template <typename Func> 972 interface TypeSpecFunc { apply(TypeSpec spec, byte index)973 void apply(TypeSpec spec, byte index); 974 } 975 ForEachTypeSpec(TypeSpecFunc f)976 void ForEachTypeSpec(TypeSpecFunc f) { 977 for (Integer i : type_specs_.keySet()) { 978 TypeSpec ptr = type_specs_.get(i); 979 if (ptr != null) { 980 byte type_id = ptr.type_spec.id; 981 if (ptr.idmap_entries != null) { 982 type_id = (byte) ptr.idmap_entries.target_type_id; 983 } 984 f.apply(ptr, (byte) (type_id - 1)); 985 } 986 } 987 } 988 getAliasResourceIdMap()989 Map<Integer, Integer> getAliasResourceIdMap() { 990 return aliasIdMap; 991 } 992 emptyBraces()993 private static LoadedPackage emptyBraces() { 994 return new LoadedPackage(); 995 } 996 } 997 998 // Gets a pointer to the package with the specified package ID, or nullptr if no such package 999 // exists. GetPackageById(int package_id)1000 LoadedPackage GetPackageById(int package_id) { 1001 for (LoadedPackage loaded_package : packages_) { 1002 if (loaded_package.GetPackageId() == package_id) { 1003 return loaded_package; 1004 } 1005 } 1006 return null; 1007 } 1008 LoadTable(Chunk chunk, LoadedIdmap loaded_idmap, boolean load_as_shared_library)1009 boolean LoadTable(Chunk chunk, LoadedIdmap loaded_idmap, boolean load_as_shared_library) { 1010 // ResTable_header header = chunk.header<ResTable_header>(); 1011 ResTable_header header = chunk.asResTable_header(); 1012 if (header == null) { 1013 logError("RES_TABLE_TYPE too small."); 1014 return false; 1015 } 1016 1017 int package_count = dtohl(header.packageCount); 1018 int packages_seen = 0; 1019 1020 // packages_.reserve(package_count); 1021 1022 Chunk.Iterator iter = new Iterator(chunk.data_ptr(), chunk.data_size()); 1023 while (iter.HasNext()) { 1024 Chunk child_chunk = iter.Next(); 1025 switch (child_chunk.type()) { 1026 case RES_STRING_POOL_TYPE: 1027 // Only use the first string pool. Ignore others. 1028 if (global_string_pool_.getError() == NO_INIT) { 1029 ResStringPool_header resStringPool_header = child_chunk.asResStringPool_header(); 1030 int err = 1031 global_string_pool_.setTo( 1032 resStringPool_header.myBuf(), 1033 resStringPool_header.myOffset(), 1034 child_chunk.size(), 1035 false); 1036 if (err != NO_ERROR) { 1037 logError("RES_STRING_POOL_TYPE corrupt."); 1038 return false; 1039 } 1040 } else { 1041 logWarning("Multiple RES_STRING_POOL_TYPEs found in RES_TABLE_TYPE."); 1042 } 1043 break; 1044 1045 case RES_TABLE_PACKAGE_TYPE: 1046 { 1047 if (packages_seen + 1 > package_count) { 1048 logError( 1049 "More package chunks were found than the " 1050 + package_count 1051 + " declared in the header."); 1052 return false; 1053 } 1054 packages_seen++; 1055 1056 LoadedPackage loaded_package = 1057 LoadedPackage.Load(child_chunk, loaded_idmap, system_, load_as_shared_library); 1058 if (!isTruthy(loaded_package)) { 1059 return false; 1060 } 1061 packages_.add(loaded_package); 1062 } 1063 break; 1064 1065 default: 1066 logWarning(String.format("Unknown chunk type '%02x'.", chunk.type())); 1067 break; 1068 } 1069 } 1070 1071 if (iter.HadError()) { 1072 logError(iter.GetLastError()); 1073 if (iter.HadFatalError()) { 1074 return false; 1075 } 1076 } 1077 return true; 1078 } 1079 1080 // Read-only view into a resource table. This class validates all data 1081 // when loading, including offsets and lengths. 1082 // class LoadedArsc { 1083 // public: 1084 // Load a resource table from memory pointed to by `data` of size `len`. 1085 // The lifetime of `data` must out-live the LoadedArsc returned from this method. 1086 // If `system` is set to true, the LoadedArsc is considered as a system provided resource. 1087 // If `load_as_shared_library` is set to true, the application package (0x7f) is treated 1088 // as a shared library (0x00). When loaded into an AssetManager, the package will be assigned an 1089 // ID. Load( StringPiece data, LoadedIdmap loaded_idmap , boolean system , boolean load_as_shared_library )1090 static LoadedArsc Load( 1091 StringPiece data, 1092 LoadedIdmap loaded_idmap /* = null */, 1093 boolean system /* = false */, 1094 boolean load_as_shared_library /* = false */) { 1095 // ATRACE_NAME("LoadedArsc::LoadTable"); 1096 1097 // Not using make_unique because the constructor is private. 1098 LoadedArsc loaded_arsc = new LoadedArsc(); 1099 loaded_arsc.system_ = system; 1100 1101 Chunk.Iterator iter = new Iterator(data, data.size()); 1102 while (iter.HasNext()) { 1103 Chunk chunk = iter.Next(); 1104 switch (chunk.type()) { 1105 case RES_TABLE_TYPE: 1106 if (!loaded_arsc.LoadTable(chunk, loaded_idmap, load_as_shared_library)) { 1107 return emptyBraces(); 1108 } 1109 break; 1110 1111 default: 1112 logWarning(String.format("Unknown chunk type '%02x'.", chunk.type())); 1113 break; 1114 } 1115 } 1116 1117 if (iter.HadError()) { 1118 logError(iter.GetLastError()); 1119 if (iter.HadFatalError()) { 1120 return emptyBraces(); 1121 } 1122 } 1123 1124 // Need to force a move for mingw32. 1125 // return std.move(loaded_arsc); 1126 return loaded_arsc; 1127 } 1128 1129 // Create an empty LoadedArsc. This is used when an APK has no resources.arsc. CreateEmpty()1130 static LoadedArsc CreateEmpty() { 1131 return new LoadedArsc(); 1132 } 1133 1134 // Populates a set of ResTable_config structs, possibly excluding configurations defined for 1135 // the mipmap type. 1136 // void CollectConfigurations(boolean exclude_mipmap, Set<ResTable_config> out_configs); 1137 1138 // Populates a set of strings representing locales. 1139 // If `canonicalize` is set to true, each locale is transformed into its canonical format 1140 // before being inserted into the set. This may cause some equivalent locales to de-dupe. 1141 // void CollectLocales(boolean canonicalize, Set<String> out_locales); 1142 emptyBraces()1143 private static LoadedArsc emptyBraces() { 1144 return new LoadedArsc(); 1145 } 1146 } 1147