All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.robolectric.res.android.LoadedArsc Maven / Gradle / Ivy

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 
  // #include 
  // #include 
  //
  // #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(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 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 packages_ = new ArrayList<>();
  boolean system_ = false;
  // };
  //
  // }  // namespace android
  //
  // #endif // LOADEDARSC_H_

  //  #define ATRACE_TAG ATRACE_TAG_RESOURCES
  //
  //  #include "androidfw/LoadedArsc.h"
  //
  //  #include 
  //  #include 
  //
  //  #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.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 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.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 bytesPerEntry = isTruthy(header.flags & ResTable_type.FLAG_OFFSET16) ? 2 : 4;
    int offsetsLength = bytesPerEntry * entry_count;
    if (offsets_offset > entries_offset || entries_offset - offsets_offset < offsetsLength) {
      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.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(
    //       reinterpret_cast(type) + entry_offset);
    ResTable_entry entry = new ResTable_entry(type.myBuf(), type.myOffset() + entry_offset);

    int entrySize = entry.isCompact() ? ResTable_entry.SIZEOF : dtohs(entry.size);
    if (entrySize < ResTable_entry.SIZEOF) {
      logError(
          "ResTable_entry size " + entrySize + " at offset " + entry_offset + " is too small.");
      return false;
    }

    if (entrySize > chunk_size || entry_offset > chunk_size - entrySize) {
      logError(
          "ResTable_entry size " + entrySize + " at offset " + entry_offset + " is too large.");
      return false;
    }

    // No further validations apply if the entry is compact.
    if (entry.isCompact()) {
      return true;
    }

    if (entrySize < ResTable_map_entry.BASE_SIZEOF) {
      // There needs to be room for one Res_value struct.
      if (entry_offset + entrySize > 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(reinterpret_cast(entry) + entry_size);
      Res_value value = entry.getResValue();
      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 + entrySize > 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 mapEntriesStart = entry_offset + entrySize;
      if (isTruthy(mapEntriesStart & 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 - mapEntriesStart) / 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 type_specs_ = new ByteBucketArray() {
    //   @Override
    //   TypeSpec newInstance() {
    //     return new TypeSpec();
    //   }
    // };
    final Map type_specs_ = new HashMap<>();
    final List dynamic_package_map_ = new ArrayList<>();
    final Map 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(
        //         reinterpret_cast(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(
      //     reinterpret_cast(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(reinterpret_cast(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 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 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 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 (const auto& type_entry : type_spec->type_entries) {
      for (ResTable_type iter : type_spec.types) {
        // const incfs::verified_map_ptr& type = type_entry.type;
        ResTable_type type = iter;
        // const size_t entry_count = dtohl(type->entryCount);
        int entry_count = type.entryCount;
        // const auto entry_offsets = type.offset(dtohs(type->header.headerSize));

        // for (size_t entry_idx = 0; entry_idx < entry_count; entry_idx++) {
        for (int entry_idx = 0; entry_idx < entry_count; entry_idx++) {
          // uint32_t offset;
          int offset;
          // uint16_t res_idx;
          short res_idx;
          // if (type->flags & ResTable_type::FLAG_SPARSE) {
          if (isTruthy(type.flags & ResTable_type.FLAG_SPARSE)) {
            // auto sparse_entry = entry_offsets.convert() + entry_idx;

            ResTable_sparseTypeEntry sparse_entry =
                new ResTable_sparseTypeEntry(
                    type.myBuf(), type.myOffset() + entry_idx * ResTable_sparseTypeEntry.SIZEOF);
            // if (!sparse_entry) {
            //   return base::unexpected(IOError::PAGES_MISSING);
            // }
            // TODO: implement above
            // offset = dtohs(sparse_entry->offset) * 4u;
            offset = dtohs(sparse_entry.offset) * 4;
            // res_idx  = dtohs(sparse_entry->idx);
            res_idx = dtohs(sparse_entry.idx);
            // } else if (type->flags & ResTable_type::FLAG_OFFSET16) {
          } else if (isTruthy(type.flags & ResTable_type.FLAG_OFFSET16)) {
            // auto entry = entry_offsets.convert() + entry_idx;
            int entry = type.entryOffset(entry_idx);
            // if (!entry) {
            //   return base::unexpected(IOError::PAGES_MISSING);
            // }
            // offset = offset_from16(entry.value());
            offset = entry;
            // res_idx = entry_idx;
            res_idx = (short) entry_idx;
          } else {
            // auto entry = entry_offsets.convert() + entry_idx;
            int entry = type.entryOffset(entry_idx);
            // if (!entry) {
            //   return base::unexpected(IOError::PAGES_MISSING);
            // }
            // offset = dtohl(entry.value());
            offset = dtohl(entry);
            res_idx = (short) entry_idx;
          }

          if (offset != ResTable_type.NO_ENTRY) {
            // auto entry = type.offset(dtohl(type->entriesStart) +
            // offset).convert();
            ResTable_entry entry =
                new ResTable_entry(
                    type.myBuf(), type.myOffset() + dtohl(type.entriesStart) + offset);
            // if (!entry) {
            //   return base::unexpected(IOError::PAGES_MISSING);
            // }
            // TODO implement above
            // if (entry->key() == static_cast(*key_idx)) {
            if (dtohl(entry.getKeyIndex()) == key_idx) {
              // The package ID will be overridden by the caller (due to runtime assignment of
              // package
              // IDs for shared libraries).
              // return make_resid(0x00, *type_idx + type_id_offset_ + 1, res_idx);
              return make_resid((byte) 0x00, (byte) (type_idx + type_id_offset_ + 1), res_idx);
            }
          }
        }
      }
      // return base::unexpected(std::nullopt);
      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 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.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 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(child_chunk.header());
            // uintptr_t header_address = reinterpret_cast(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(type_spec.id) >
            //     std.numeric_limits.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.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(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 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(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();
              // 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 finalizedIds;
              // finalizedIds.reserve(entry_end - entryBegin);
              Set 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 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 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 
    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 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 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 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 out_locales);

  private static LoadedArsc emptyBraces() {
    return new LoadedArsc();
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy