properly account for combining config section with firmware
prior to this, the concatenate assumed it was concatenating a firmware with a full *storage section*, e.g. the already-padded 8192 bytes, but it's equally valuable now that I'm creating configs to have just a config section + footer, which needs to be padded 8192. now concatenate supports both
This commit is contained in:
parent
85d84144fc
commit
095fac19f1
|
@ -30,10 +30,10 @@ In all cases, online help can be retrieved by providing the `-h` or ``--help`` f
|
|||
### concatenate
|
||||
|
||||
**concatenate** combines a GP2040-CE firmware .bin file (such as from a fresh build) and a GP2040-CE board's storage
|
||||
section .bin, and produces a properly-offset .bin file suitable for flashing to a board. This may be useful to ensure
|
||||
the board is flashed with a particular configuration, for instances such as producing a binary to flash many boards with
|
||||
a particular configuration (specific customizations, etc.), or keeping documented backups of what you're testing with
|
||||
during development.
|
||||
section .bin or config (with footer) .bin, and produces a properly-offset .bin file suitable for flashing to a board.
|
||||
This may be useful to ensure the board is flashed with a particular configuration, for instances such as producing a
|
||||
binary to flash many boards with a particular configuration (specific customizations, etc.), or keeping documented
|
||||
backups of what you're testing with during development.
|
||||
|
||||
Sample usage:
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ import argparse
|
|||
import logging
|
||||
|
||||
from gp2040ce_bintools import core_parser
|
||||
from gp2040ce_bintools.storage import STORAGE_LOCATION
|
||||
from gp2040ce_bintools.storage import STORAGE_LOCATION, pad_config_to_storage_size
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
@ -17,6 +17,18 @@ class FirmwareLengthError(ValueError):
|
|||
"""Exception raised when the firmware is too large to fit the known storage location."""
|
||||
|
||||
|
||||
def combine_firmware_and_config(firmware_binary: bytearray, config_binary: bytearray) -> bytearray:
|
||||
"""Given firmware and config binaries, combine the two to one, with proper offsets for GP2040-CE.
|
||||
|
||||
Args:
|
||||
firmware_binary: binary data of the raw GP2040-CE firmware, probably but not necessarily unpadded
|
||||
config_binary: binary data of board config + footer, possibly padded to be a full storage section
|
||||
Returns:
|
||||
the resulting correctly-offset binary suitable for a GP2040-CE board
|
||||
"""
|
||||
return pad_firmware_up_to_storage(firmware_binary) + pad_config_to_storage_size(config_binary)
|
||||
|
||||
|
||||
def concatenate_firmware_and_storage_files(firmware_filename: str, storage_filename: str, combined_filename: str):
|
||||
"""Open the provided binary files and combine them into one combined GP2040-CE with config file.
|
||||
|
||||
|
@ -26,7 +38,7 @@ def concatenate_firmware_and_storage_files(firmware_filename: str, storage_filen
|
|||
combined_filename: filename of where to write the combine binary
|
||||
"""
|
||||
with open(firmware_filename, 'rb') as firmware, open(storage_filename, 'rb') as storage:
|
||||
new_binary = pad_firmware_up_to_storage(firmware.read()) + bytearray(storage.read())
|
||||
new_binary = combine_firmware_and_config(bytearray(firmware.read()), bytearray(storage.read()))
|
||||
with open(combined_filename, 'wb') as combined:
|
||||
combined.write(new_binary)
|
||||
|
||||
|
@ -35,7 +47,7 @@ def pad_firmware_up_to_storage(firmware: bytes) -> bytearray:
|
|||
"""Provide a copy of the firmware padded with zero bytes up to the provided position.
|
||||
|
||||
Args:
|
||||
firmware: the read-in binary file to process
|
||||
firmware: the firmware binary to process
|
||||
Returns:
|
||||
the resulting padded binary as a bytearray
|
||||
Raises:
|
||||
|
@ -47,7 +59,7 @@ def pad_firmware_up_to_storage(firmware: bytes) -> bytearray:
|
|||
raise FirmwareLengthError(f"provided firmware binary is larger than the start of "
|
||||
f"storage at {STORAGE_LOCATION}!")
|
||||
|
||||
return bytes(bytearray(firmware) + bytearray(b'\x00' * bytes_to_pad))
|
||||
return bytearray(firmware) + bytearray(b'\x00' * bytes_to_pad)
|
||||
|
||||
|
||||
############
|
||||
|
@ -57,13 +69,13 @@ def pad_firmware_up_to_storage(firmware: bytes) -> bytearray:
|
|||
def concatenate():
|
||||
"""Combine a built firmware .bin and a storage .bin."""
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Combine a compiled GP2040-CE firmware-only .bin and an existing storage area .bin into one file "
|
||||
"suitable for flashing onto a board.",
|
||||
description="Combine a compiled GP2040-CE firmware-only .bin and an existing storage area or config .bin "
|
||||
"into one file suitable for flashing onto a board.",
|
||||
parents=[core_parser],
|
||||
)
|
||||
parser.add_argument('firmware_filename', help=".bin file of a GP2040-CE firmware, probably from a build")
|
||||
parser.add_argument('storage_filename', help=".bin file of a GP2040-CE board's storage section")
|
||||
parser.add_argument('config_filename', help=".bin file of a GP2040-CE board's storage section or config w/footer")
|
||||
parser.add_argument('new_binary_filename', help="output .bin file of the resulting firmware + storage")
|
||||
|
||||
args, _ = parser.parse_known_args()
|
||||
concatenate_firmware_and_storage_files(args.firmware_filename, args.storage_filename, args.new_binary_filename)
|
||||
concatenate_firmware_and_storage_files(args.firmware_filename, args.config_filename, args.new_binary_filename)
|
||||
|
|
|
@ -114,6 +114,25 @@ def get_storage_section(content: bytes) -> bytes:
|
|||
logger.debug("returning bytes from %s to %s", hex(STORAGE_LOCATION), hex(STORAGE_LOCATION + STORAGE_SIZE))
|
||||
return content[STORAGE_LOCATION:(STORAGE_LOCATION + STORAGE_SIZE)]
|
||||
|
||||
|
||||
def pad_config_to_storage_size(config: bytes) -> bytearray:
|
||||
"""Provide a copy of the config (with footer) padded with zero bytes to be the proper storage section size.
|
||||
|
||||
Args:
|
||||
firmware: the config section binary to process
|
||||
Returns:
|
||||
the resulting padded binary as a bytearray
|
||||
Raises:
|
||||
FirmwareLengthError: if the is larger than the storage location
|
||||
"""
|
||||
bytes_to_pad = STORAGE_SIZE - len(config)
|
||||
logger.debug("config is length %s, padding %s bytes", len(config), bytes_to_pad)
|
||||
if bytes_to_pad < 0:
|
||||
raise ConfigLengthError(f"provided config binary is larger than the allowed storage of "
|
||||
f"storage at {STORAGE_SIZE} bytes!")
|
||||
|
||||
return bytearray(b'\x00' * bytes_to_pad) + bytearray(config)
|
||||
|
||||
############
|
||||
# COMMANDS #
|
||||
############
|
||||
|
|
|
@ -6,6 +6,16 @@ import pytest
|
|||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def config_binary():
|
||||
"""Read in a test GP2040-CE configuration, Protobuf serialized binary form with footer."""
|
||||
filename = os.path.join(HERE, 'test-files', 'test-config.bin')
|
||||
with open(filename, 'rb') as file:
|
||||
content = file.read()
|
||||
|
||||
yield content
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def firmware_binary():
|
||||
"""Read in a test GP2040-CE firmware binary file."""
|
||||
|
|
Binary file not shown.
|
@ -1,7 +1,8 @@
|
|||
"""Tests for the image builder module."""
|
||||
import pytest
|
||||
|
||||
from gp2040ce_bintools.builder import FirmwareLengthError, pad_firmware_up_to_storage
|
||||
from gp2040ce_bintools.builder import FirmwareLengthError, combine_firmware_and_config, pad_firmware_up_to_storage
|
||||
from gp2040ce_bintools.storage import get_config_footer, get_storage_section
|
||||
|
||||
|
||||
def test_padding_firmware(firmware_binary):
|
||||
|
@ -10,6 +11,24 @@ def test_padding_firmware(firmware_binary):
|
|||
assert len(padded) == 2088960
|
||||
|
||||
|
||||
def test_firmware_plus_storage(firmware_binary, storage_dump):
|
||||
"""Test that combining firmware and storage produces a valid combined binary."""
|
||||
whole_board = combine_firmware_and_config(firmware_binary, storage_dump)
|
||||
# if this is valid, we should be able to find the storage and footer again
|
||||
storage = get_storage_section(whole_board)
|
||||
footer_size, _, _ = get_config_footer(storage)
|
||||
assert footer_size == 2032
|
||||
|
||||
|
||||
def test_firmware_plus_config_binary(firmware_binary, config_binary):
|
||||
"""Test that combining firmware and storage produces a valid combined binary."""
|
||||
whole_board = combine_firmware_and_config(firmware_binary, config_binary)
|
||||
# if this is valid, we should be able to find the storage and footer again
|
||||
storage = get_storage_section(whole_board)
|
||||
footer_size, _, _ = get_config_footer(storage)
|
||||
assert footer_size == 2032
|
||||
|
||||
|
||||
def test_padding_firmware_too_big(firmware_binary):
|
||||
"""Test that firmware is padded to the expected size."""
|
||||
with pytest.raises(FirmwareLengthError):
|
||||
|
|
|
@ -95,3 +95,15 @@ def test_config_from_whole_board_parses(whole_board_dump):
|
|||
config = storage.get_config(storage.get_storage_section(whole_board_dump))
|
||||
assert config.boardVersion == 'v0.7.2'
|
||||
assert config.hotkeyOptions.hotkeyF1Up.dpadMask == 1
|
||||
|
||||
|
||||
def test_pad_config_to_storage(config_binary):
|
||||
"""Test that we can properly pad a config section to the correct storage section size."""
|
||||
storage_section = storage.pad_config_to_storage_size(config_binary)
|
||||
assert len(storage_section) == 8192
|
||||
|
||||
|
||||
def test_pad_config_to_storage_raises(config_binary):
|
||||
"""Test that we raise an exception if the config is bigger than the storage section."""
|
||||
with pytest.raises(storage.ConfigLengthError):
|
||||
_ = storage.pad_config_to_storage_size(config_binary * 5)
|
||||
|
|
Loading…
Reference in New Issue