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