Skip to content

Packers

Overview

Packers are components that typically mirror unpackers, taking constituent children resources (and sometimes descendants) and reassembling them to produce a new resource.

Packers target specific resource tags against which they can run, and are typically paired with unpackers. For example, consider the ZipPacker:

from io import BytesIO

from ofrak_type.range import Range

from ofrak.resource import Resource
from ofrak.component.packer import Packer

...

class ZipPacker(Packer[None]):
    targets = (ZipArchive,)

    async def pack(self, resource: Resource, config=None):
        zip_view: ZipArchive = await resource.view_as(ZipArchive)
        zip_entries = await zip_view.get_entries()

        result = BytesIO()
        with ZipFile(result, mode="w", compression=ZIP_DEFLATED) as zip_file:
            for zip_entry_view in zip_entries:
                zip_path_in_archive = zip_entry_view.path_in_archive
                zip_entry_data = await zip_entry_view.resource.get_data()
                zip_file.writestr(zip_path_in_archive, zip_entry_data)

        original_zip_size = await zip_view.resource.get_data_length()
        resource.queue_patch(Range(0, original_zip_size), result.getvalue())

This packer targets a ZipArchive and performs the opposite operation of ZipUnpacker.

Discerning OFRAK Users will notice that there are generally more unpackers than packers. The reason for this is that packers are often not needed in OFRAK when a child resource's data is mapped directly into its parent's data. For example, while there is an IElfUnpacker in OFRAK, an ELF packer is not needed: because all of an ELF's children are mapped directly into the ELF file, modifications to them are automatically reflected in the parent object. Note, however, that packers are needed in OFRAK when there are interdependencies between the data in children resources (a common example of this is when modifying the data of a child requires updating a checksum in another part of that resource).

Usage

There are several ways in which a packer can be invoked in OFRAK.

Run Explicitly

OFRAK packers can be run directly against a resource. For example:

from ofrak.resource import Resource
from ofrak.core.zip import ZipPacker

...

resource: Resource
await resource.run(ZipPacker)

Run Automatically

Packers can also be run automatically against resources with valid resource tags. For example, consider the following code:

from ofrak.resource import Resource
from ofrak.core.zip import ZipArchive

...

resource: Resource
assert resource.has_tag(ZipArchive)
await resource.pack()

Since the resource has the ZipArchive tag, OFRAK will run the ZipPacker.

Recursive Packing

It is also possible to chain pack calls together recursively using the Resource.pack_recursively method. See Resource for more details.