diff --git a/libresource/Serialize.cpp b/libresource/Serialize.cpp index 5296257f63..7b4369d89b 100644 --- a/libresource/Serialize.cpp +++ b/libresource/Serialize.cpp @@ -70,6 +70,7 @@ namespace { // Just a random thing to make it easy to see (when dumping bytes) if we forgot // to go back and correct a chunk size. constexpr uint32_t FILL_IN_LATER = 0xEEEEEEEE; +constexpr uint16_t FILL_IN_LATER_SHORT = 0xEEEE; void write_long_at_pos(size_t index, uint32_t data, @@ -81,6 +82,14 @@ void write_long_at_pos(size_t index, vec->replaceAt((char)(swapped >> 24), index + 3); } +void write_short_at_pos(size_t index, + uint16_t data, + android::Vector* vec) { + auto swapped = htods(data); + vec->replaceAt((char)swapped, index); + vec->replaceAt((char)(swapped >> 8), index + 1); +} + void encode_string8(const char* string, size_t& len, android::Vector* vec) { @@ -373,13 +382,13 @@ void ResTableTypeBuilder::encode_offsets_as_sparse( } } -void ResTableTypeProjector::serialize_type(android::ResTable_type* type, +bool ResTableTypeProjector::serialize_type(android::ResTable_type* type, size_t last_non_deleted, android::Vector* out) { if (dtohl(type->entryCount) == 0 || dtohs(type->entriesStart) == 0) { // Wonky input data, omit this config. ALOGD("Wonky config for type %d, dropping!", type->id); - return; + return false; } // Check if this config has all of its entries deleted. If a non-default // config has everything deleted, skip emitting data. @@ -397,7 +406,7 @@ void ResTableTypeProjector::serialize_type(android::ResTable_type* type, } if (num_non_deleted_non_empty_entries == 0) { // No meaningful values for this config, don't emit the struct. - return; + return false; } } // Write entry/value data by iterating the existing offset data again, and @@ -474,6 +483,7 @@ void ResTableTypeProjector::serialize_type(android::ResTable_type* type, push_long(offsets[i], out); } push_vec(temp, out); + return true; } void ResTableTypeProjector::serialize(android::Vector* out) { @@ -507,8 +517,9 @@ void ResTableTypeProjector::serialize(android::Vector* out) { push_long(total_size, out); out->push_back(m_type); out->push_back(0); - out->push_back(0); - out->push_back(0); + // Number of types (used to be a reserved field). Will be stamped in later. + auto type_count_pos = out->size(); + push_short(FILL_IN_LATER_SHORT, out); push_long(entries, out); // Copy all existing spec flags for non-deleted entries for (uint16_t i = 0; i < original_entries; i++) { @@ -521,9 +532,13 @@ void ResTableTypeProjector::serialize(android::Vector* out) { } // Write all applicable ResTable_type structures (and their corresponding // entries/values). + uint16_t type_count{0}; for (size_t i = 0; i < m_configs.size(); i++) { - serialize_type(m_configs.at(i), last_non_deleted, out); + if (serialize_type(m_configs.at(i), last_non_deleted, out)) { + type_count++; + } } + write_short_at_pos(type_count_pos, type_count, out); } void ResTableTypeDefiner::serialize(android::Vector* out) { @@ -561,18 +576,21 @@ void ResTableTypeDefiner::serialize(android::Vector* out) { push_long(total_size, out); out->push_back(m_type); out->push_back(0); - out->push_back(0); - out->push_back(0); + // Number of types (used to be a reserved field). Will be stamped in later. + auto type_count_pos = out->size(); + push_short(FILL_IN_LATER_SHORT, out); push_long(entries, out); // Write all given spec flags for (uint16_t i = 0; i < entries; i++) { push_long(dtohl(m_flags.at(i)), out); } // Write the N configs given and all their entries/values + uint16_t type_count{0}; for (auto& config : m_configs) { if (empty_configs.count(config) > 0) { continue; } + type_count++; auto& data = m_data.at(config); // Compute offsets and entry/value data size. CanonicalEntries canonical_entries; @@ -626,6 +644,7 @@ void ResTableTypeDefiner::serialize(android::Vector* out) { } push_vec(entry_data, out); } + write_short_at_pos(type_count_pos, type_count, out); } void ResStringPoolBuilder::add_string(const char* s, size_t len) { diff --git a/libresource/utils/Serialize.h b/libresource/utils/Serialize.h index 231745b787..44ffb77745 100644 --- a/libresource/utils/Serialize.h +++ b/libresource/utils/Serialize.h @@ -324,7 +324,7 @@ class ResTableTypeProjector : public ResTableTypeBuilder { virtual ~ResTableTypeProjector() {} private: - void serialize_type(android::ResTable_type*, + bool serialize_type(android::ResTable_type*, size_t, android::Vector* out); android::ResTable_typeSpec* m_spec; diff --git a/test/unit/ResourceSerializationTest.cpp b/test/unit/ResourceSerializationTest.cpp index 94e993705c..cd0c047a5c 100644 --- a/test/unit/ResourceSerializationTest.cpp +++ b/test/unit/ResourceSerializationTest.cpp @@ -846,16 +846,30 @@ TEST(Configs, TestConfigEquivalence) { } } -TEST(ResTable, TestBuilderRoundTrip) { - auto tmp_dir = redex::make_tmp_dir("ResTable%%%%%%%%"); - auto res_path = tmp_dir.path + "/resources.arsc"; - copy_file(get_env("test_arsc_path"), res_path); - ResourcesArscFile res_table(res_path); - res_table.serialize(); - EXPECT_TRUE(are_files_equal(get_env("test_arsc_path"), res_path)); -} - namespace { +class TypeSpecCollector : public arsc::ResourceTableVisitor { + public: + bool visit_type_spec(android::ResTable_package* package, + android::ResTable_typeSpec* type_spec) override { + type_specs.emplace(type_spec->id, type_spec); + return arsc::ResourceTableVisitor::visit_type_spec(package, type_spec); + } + + // Sets the reserved short to 0, like older tool versions would do. + void clear_reserved_fields() { + for (auto&& [id, ptr] : type_specs) { + ptr->res1 = 0; + } + } + + // Get the repurposed short field in the header. + uint16_t get_reserved_field(uint8_t type_id) { + return type_specs.at(type_id)->res1; + } + + std::map type_specs; +}; + void build_arsc_file_and_validate( const std::function& callback) { @@ -1022,6 +1036,23 @@ std::vector load_types(const RedexMappedFile& arsc_file) { }) } // namespace +TEST(ResTable, TestBuilderRoundTrip) { + auto tmp_dir = redex::make_tmp_dir("ResTable%%%%%%%%"); + auto res_path = tmp_dir.path + "/resources.arsc"; + copy_file(get_env("test_arsc_path"), res_path); + ResourcesArscFile res_table(res_path); + res_table.serialize(); + // To match the old, checked in binary that was made with outdated aapt tool, + // stamp over the reserved fields that got repurposed to set them to be zero. + { + auto f = RedexMappedFile::open(res_path, false); + TypeSpecCollector collector; + collector.visit(f.data(), f.size()); + collector.clear_reserved_fields(); + } + EXPECT_TRUE(are_files_equal(get_env("test_arsc_path"), res_path)); +} + TEST(ResTable, BuildNewTable) { build_arsc_file_and_validate([&](const std::string& /* unused */, const std::string& arsc_path) { @@ -1038,6 +1069,14 @@ TEST(ResTable, BuildNewTable) { ASSERT_ENTRY_VALUES(table_dump, "land", "foo:dimen/first", e0_land); // Separate validation for plurals, styles, etc. ASSERT_MAP_ENTRY_VALUES(table_dump, "xxhdpi", "foo:style/fourth", style); + // Sanity check header values for number of types in each type spec. + auto f = RedexMappedFile::open(arsc_path, false); + TypeSpecCollector collector; + collector.visit(f.data(), f.size()); + ASSERT_EQ(collector.get_reserved_field(0x1), 2) + << "dimen should have two ResTable_type entries"; + ASSERT_EQ(collector.get_reserved_field(0x2), 1) + << "style should have one ResTable_type entry"; }); } @@ -1621,6 +1660,11 @@ TEST(ResTable, BuildDumpAndParseSparseType) { EXPECT_EQ(types.at(1)->header.size, sizeof(android::ResTable_type) + size_of_entry_value_and_offset) << "Expected small type size due to sparse encoding"; + // Additional validation that ResTableTypeProjector filled out headers + // properly. + EXPECT_EQ(parsed_table.m_types.begin()->second->res1, 2) + << "First ResTable_typeSpec should specify two ResTable_type structs " + "to follow"; } }