mirror of
https://github.com/armel/uv-k1-k5v3-firmware-custom.git
synced 2026-10-02 03:15:37 +00:00
78 lines
3.3 KiB
Python
78 lines
3.3 KiB
Python
#!/usr/bin/env python3
|
|
# Pack an overlay-app raw binary into a .app blob: 64-byte header + code.
|
|
# Header layout mirrors app_overlay.h : app_header_t (little-endian, packed).
|
|
# The format/version constants (magic, header + ABI version, overlay budget) are
|
|
# read straight from the C headers the firmware itself compiles, so there is a
|
|
# SINGLE source of truth - the packer can never silently drift from the loader.
|
|
# CRC-32 is zlib/PKZIP (init 0xFFFFFFFF, poly 0xEDB88320, final XOR) to match
|
|
# the firmware's mb_ext_image_crc32 / mb_crc32_bytes.
|
|
#
|
|
# ./pack_app.py breakout/breakout.bin breakout/breakout.app --name Breakout --ver 1.0
|
|
import argparse, os, re, struct, zlib, sys
|
|
|
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
|
|
|
def cdefine(header: str, name: str) -> int:
|
|
"""Value of a `#define <name> <int-literal>` in a sibling C header.
|
|
Accepts a decimal or 0x-hex literal with optional u/U/l/L suffixes. Keeps the
|
|
blob format tied to the firmware's own headers (single source of truth)."""
|
|
pat = re.compile(r"^\s*#define\s+" + re.escape(name) +
|
|
r"\s+(0[xX][0-9a-fA-F]+|\d+)[uUlL]*\b")
|
|
with open(os.path.join(HERE, header)) as f:
|
|
for line in f:
|
|
m = pat.match(line)
|
|
if m:
|
|
return int(m.group(1), 0)
|
|
sys.exit(f"{header}: #define {name} not found")
|
|
|
|
# Single source of truth: the C headers the firmware also compiles.
|
|
MAGIC = cdefine("app_overlay.h", "APP_MAGIC").to_bytes(4, "little") # 0x31504146 -> b"FAP1"
|
|
HDR_VERSION = cdefine("app_overlay.h", "APP_HDR_VERSION")
|
|
ABI_VERSION = cdefine("app_api.h", "APP_ABI_VERSION")
|
|
OVERLAY_MAX = cdefine("app_overlay.h", "APP_OVERLAY_MAX") # 4 KiB overlay budget
|
|
FLAG_COMMITTED = cdefine("app_overlay.h", "APP_FLAG_COMMITTED")
|
|
FLAG_SCREEN_SAVER = cdefine("app_overlay.h", "APP_FLAG_SCREEN_SAVER")
|
|
|
|
def field(s: str, n: int) -> bytes:
|
|
b = s.encode("ascii", "strict")[: n - 1]
|
|
return b + b"\x00" * (n - len(b))
|
|
|
|
def main():
|
|
ap = argparse.ArgumentParser()
|
|
ap.add_argument("infile")
|
|
ap.add_argument("outfile")
|
|
ap.add_argument("--name", default="app")
|
|
ap.add_argument("--ver", default="1.0")
|
|
ap.add_argument("--entry", type=lambda x: int(x, 0), default=0)
|
|
ap.add_argument("--vma", type=lambda x: int(x, 0), required=True,
|
|
help="RAM VMA the app was linked at (must match the firmware overlay)")
|
|
ap.add_argument("--screensaver", action="store_true",
|
|
help="allow the resident BLTime screen saver while this app is idle")
|
|
a = ap.parse_args()
|
|
|
|
code = open(a.infile, "rb").read()
|
|
if len(code) == 0:
|
|
sys.exit("empty input")
|
|
if len(code) > OVERLAY_MAX:
|
|
sys.exit(f"code {len(code)} B exceeds overlay budget {OVERLAY_MAX} B")
|
|
|
|
crc = zlib.crc32(code) & 0xFFFFFFFF
|
|
flags = FLAG_COMMITTED | (FLAG_SCREEN_SAVER if a.screensaver else 0)
|
|
header = struct.pack(
|
|
"<4sHHIIHH16s16sI8s",
|
|
MAGIC, HDR_VERSION, ABI_VERSION,
|
|
len(code), crc, a.entry, flags,
|
|
field(a.name, 16), field(a.ver, 16), a.vma, b"\x00" * 8,
|
|
)
|
|
assert len(header) == 64, len(header)
|
|
|
|
with open(a.outfile, "wb") as f:
|
|
f.write(header)
|
|
f.write(code)
|
|
|
|
print(f"{a.outfile}: name={a.name!r} ver={a.ver!r} vma=0x{a.vma:08x} "
|
|
f"code={len(code)} B crc32=0x{crc:08x} -> blob {64 + len(code)} B")
|
|
|
|
if __name__ == "__main__":
|
|
main()
|