uf2.py
ofrak.core.uf2
Uf2BlockHeader (ResourceAttributes)
dataclass
Recreates the official spec
Offset Size Value
0 4 First magic number, 0x0A324655 ("UF2
") 4 4 Second magic number, 0x9E5D5157 8 4 Flags 12 4 Address in flash where the data should be written 16 4 Number of bytes used in data (often 256) 20 4 Sequential block number; starts at 0 24 4 Total number of blocks in file 28 4 File size or board family ID or zero 32 476 Data, padded with zeros 508 4 Final magic number, 0x0AB16F30
Uf2File (GenericBinary)
dataclass
A UF2 file
Uf2FileAttributes (ResourceAttributes)
dataclass
Remembers all the information needed to repack that can't be deduced from the contents.
Uf2FilePacker (Packer)
Packages code regions back into USB Flashing Format (UF2) with proper block headers, addresses, and magic numbers for microcontroller programming. Use after modifying firmware code to create flashable .uf2 files for Adafruit boards, Raspberry Pi Pico, or other UF2-compatible development boards. Each UF2 block contains 256 bytes of data with addressing information.
pack(self, resource, config=None)
async
Pack a resource into a UF2 file
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
resource |
Resource |
required | |
config |
None |
Source code in ofrak/core/uf2.py
async def pack(self, resource: Resource, config=None):
"""
Pack a resource into a UF2 file
:param resource:
:param config:
"""
payloads: List[Tuple[int, int, bytes]] = [] # List of target_addr, payload_data
for memory_region_r in await resource.get_children(
r_filter=ResourceFilter(
tags=(CodeRegion,),
)
):
memory_region = await memory_region_r.view_as(CodeRegion)
data = await memory_region_r.get_data()
data_length = await memory_region_r.get_data_length()
data_range = memory_region.vaddr_range()
addr = data_range.start
for i in range(0, data_length, 256):
payloads.append((addr + i, 256, data[i : (i + 256)]))
continue
num_blocks = len(payloads)
block_no = 0
file_attributes = resource.get_attributes(attributes_type=Uf2FileAttributes)
family_id = file_attributes.family_id
repacked_data = b""
for target_addr, payload_size, payload_data in payloads:
repacked_data += struct.pack(
"8I476sI",
UF2_MAGIC_START_ONE,
UF2_MAGIC_START_TWO,
Uf2Flags.FAMILY_ID_PRESENT,
target_addr,
payload_size,
block_no,
num_blocks,
family_id,
payload_data + b"\x00" * (467 - payload_size), # add padding
UF2_MAGIC_END,
)
block_no += 1
resource.queue_patch(Range(0, await resource.get_data_length()), repacked_data)
Uf2Flags (IntEnum)
An enumeration.
Uf2Unpacker (Unpacker)
Extracts code regions from USB Flashing Format (UF2) files, a format designed for easy microcontroller programming via USB mass storage. UF2 files contain binary data with address information and are commonly used for Adafruit boards, Raspberry Pi Pico, and other development boards. The unpacker creates code regions that can be further analyzed or disassembled.
unpack(self, resource, config=None)
async
Unpack a UF2 file.
UF2 files contain blocks of binary data.
Source code in ofrak/core/uf2.py
async def unpack(self, resource: Resource, config=None):
"""
Unpack a UF2 file.
UF2 files contain blocks of binary data.
"""
data_length = await resource.get_data_length()
ranges: List[Tuple[Range, bytes]] = []
# block_no are 0 indexed, to make the check do one fewer check, we start at -1
previous_block_no = -1
family_id = None
file_num_blocks = None
block_no = 0
for i in range(0, data_length, 512):
data = await resource.get_data(Range(i, (i + 512)))
(
magic_start_one,
magic_start_two,
flags,
target_addr,
payload_size,
block_no,
num_blocks,
filesize_familyID,
payload_data,
magic_end,
) = struct.unpack("8I476sI", data)
# basic sanity checks
if magic_start_one != UF2_MAGIC_START_ONE:
raise ValueError("Bad Start Magic")
if magic_start_two != UF2_MAGIC_START_TWO:
raise ValueError("Bad Start Magic")
if magic_end != UF2_MAGIC_END:
raise ValueError("Bad End Magic")
if (previous_block_no - block_no) != -1:
raise ValueError("Skipped a block number")
previous_block_no = block_no
if not file_num_blocks:
file_num_blocks = num_blocks
if family_id is None:
family_id = filesize_familyID
else:
if family_id != filesize_familyID:
raise NotImplementedError("Multiple family IDs in file not supported")
# unpack data
if flags & Uf2Flags.NOT_MAIN_FLASH:
# data not written to main flash
raise NotImplementedError(
"Data not written to main flash is currently not supported"
)
elif flags & Uf2Flags.FILE_CONTAINER:
# file container
raise NotImplementedError("File containers are currently not implemented")
elif flags & Uf2Flags.FAMILY_ID_PRESENT:
data = payload_data[0:payload_size]
if len(ranges) == 0:
ranges.append((Range(target_addr, target_addr + payload_size), data))
else:
last_region_range, last_region_data = ranges[-1]
# if range is adjacent, extend, otherwise start a new one
if target_addr - last_region_range.end == 0:
last_region_range.end = target_addr + payload_size
last_region_data += data
ranges[-1] = (last_region_range, last_region_data)
else:
ranges.append((Range(target_addr, target_addr + payload_size), data))
else:
# unsupported flags
raise ValueError(f"Unsupported flags {flags}")
# count vs 0 indexed (there are 256 blocks from 0-255)
if file_num_blocks != (block_no + 1):
raise ValueError(
f"Incorrect number of blocks. Expected {file_num_blocks}, got {block_no}"
)
if family_id:
file_attributes = Uf2FileAttributes(family_id)
resource.add_attributes(file_attributes)
# print("num: ", last_block_no, "file: ", file_num_blocks)
# assert last_block_no == file_num_blocks, "Did not unpack enough blocks"
for flash_range, flash_data in ranges:
await resource.create_child_from_view(
CodeRegion(flash_range.start, flash_range.end - flash_range.start),
data=flash_data,
)