#!/usr/bin/env python # Copyright 2024 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import argparse import stm32_crc import struct import time class ResourcePackTableEntry(object): TABLE_ENTRY_FMT = ' self.table_size): raise Exception("Exceeded max number of resources. Must have %d or " "fewer" % self.table_size) # Assign offsets to each of the entries in reverse order. The reason we do this in reverse # is so that the resource at the end is guaranteed to be at the end of the pack. # # This is required because the firmware looks at the offset + length of the final resource # in the table in order to determine the total size of the pack. If the final resource # is a duplicate of an earlier resource and if we assigned offsets starting at the # beginning of the table, that final resource would end up pointing to an assigned # offset somewhere in the middle of the pack, causing the pack to appear to be truncated. current_offset = sum((len(c) for c in self.contents)) for table_entry in reversed(self.table_entries): if table_entry.offset == -1: # This entry doesn't have an offset in the output file yet, assign one to all # entries that share the same content index current_offset -= table_entry.length for e in self.table_entries: if e.content_index == table_entry.content_index: e.offset = current_offset self.crc = self.get_content_crc() self.finalized = True def serialize(self, f_out): if not self.finalized: self.finalize() f_out.write(self.serialize_manifest(self.crc)) f_out.write(self.serialize_table()) for c in self.serialize_content(): f_out.write(c) return self.crc def add_resource(self, content): if self.finalized: raise Exception("Cannot add additional resource, " + "resource pack has already been finalized") # If resource already is present, add to table only try: # Try to find the index for this content if it already exists. If it doesn't we'll # throw the ValueError. content_index = self.contents.index(content) except ValueError: # This content is completely new, add it to the contents list. self.contents.append(content) content_index = len(self.contents) - 1 crc = stm32_crc.crc32(content) # Use -1 as the offset as we don't assign offsets until serialize_table self.table_entries.append(ResourcePackTableEntry(content_index, -1, len(content), crc)) def dump(self): """ Dump a bunch of information about this pbpack to stdout """ print 'Manifest CRC: 0x%x' % self.crc print 'Calculated CRC: 0x%x' % self.get_content_crc() print 'Num Items: %u' % len(self.table_entries) for i, entry in enumerate(self.table_entries, start=1): print ' %u: Offset %u Length %u CRC 0x%x' % (i, entry.offset, entry.length, entry.crc) def __init__(self, is_system): self.table_size = 512 if is_system else 256 self.content_start = self.MANIFEST_SIZE_BYTES + self.table_size * self.TABLE_ENTRY_SIZE_BYTES # Note that we never actually set the timestamp in newly generated pbpacks. The timestamp # field in the manifest is unused in practice and we'll probably reuse that field for # another purpose in the future. self.timestamp = 0 # CRC of the content. Note that it only covers the content itself and not the table nor # the manifest. Calculated during finalize(). self.crc = 0 # List of binary content, where each entry is the raw processed data for each unique # resource. self.contents = [] # List of resources that are in the pack. Note that this list may be longer than the # self.contents list if there are duplicates, duplicated entries (exact same data) will # not be repeated in self.contents. Each entry is a ResourcePackTableEntry self.table_entries = [] # Indicates that the ResourcePack has been built and no more resources can be added. self.finalized = False if __name__ == '__main__': parser = argparse.ArgumentParser(description='dump pbpack metadata') parser.add_argument('pbpack_path', help='path to pbpack to dump') parser.add_argument('--app', default=False, action='store_true', help='Indicate this pbpack is an app pbpack') args = parser.parse_args() with open(args.pbpack_path, 'rb') as f: pack = ResourcePack.deserialize(f, is_system=not args.app) pack.dump()