KOKINIO - MANAGER
Edit File: firmware_packager.py
#!/usr/bin/python3 # # Copyright 2017 Max Ehrlich maxehr@gmail.com # # SPDX-License-Identifier: LGPL-2.1-or-later # import argparse import contextlib import os import shutil import subprocess import tempfile import time try: from jinja2 import Environment, Template except ImportError: print( "Error: jinja2 is required for this script. Install it with: pip install jinja2" ) exit(1) @contextlib.contextmanager def cd(path): prev_cwd = os.getcwd() os.chdir(path) yield os.chdir(prev_cwd) # Embedded Jinja2 template for firmware.metainfo.xml FIRMWARE_METAINFO_TEMPLATE = """<?xml version="1.0" encoding="UTF-8"?> <component type="firmware"> <id>org.{{ developer_name }}.guid{{ firmware_id }}</id> <name>{{ firmware_name }}</name>{% if name_variant_suffix %} <name_variant_suffix>{{ name_variant_suffix }}</name_variant_suffix>{% endif %} <summary>{{ firmware_summary }}</summary> <description> <p>{{ firmware_description }}</p> </description> <provides> <firmware type="flashed">{{ device_guid }}</firmware> </provides> <url type="homepage">{{ firmware_homepage }}</url> <metadata_license>CC0-1.0</metadata_license> <project_license>proprietary</project_license> <developer_name>{{ developer_name }}</developer_name> <releases> <release urgency="{{ release_urgency }}" version="{{ release_version }}" date="{{ date }}"> <checksum filename="firmware.bin" target="content"/> <description> <p>{{ release_description }}</p> {% for feature in release_features %} <p>{{ feature }}</p> {% endfor %} </description> </release> </releases> <custom> <value key="LVFS::VersionFormat">{{ version_format }}</value> <value key="LVFS::UpdateProtocol">{{ update_protocol }}</value> </custom> <categories> {% for category in categories %} <category>{{ category }}</category> {% endfor %} </categories> </component>""" def load_template(): """Load the Jinja2 template from the embedded string with XML autoescape.""" env = Environment(autoescape=True) return env.from_string(FIRMWARE_METAINFO_TEMPLATE) def make_firmware_metainfo(firmware_info, dst): local_info = vars(firmware_info) local_info["firmware_id"] = local_info["device_guid"][0:8] # Convert name-variant-suffix to name_variant_suffix for template if "name_variant_suffix" not in local_info and hasattr( firmware_info, "name_variant_suffix" ): local_info["name_variant_suffix"] = getattr( firmware_info, "name_variant_suffix", "" ) # Parse release features into a list for template iteration if "release_features" in local_info and local_info["release_features"]: features = local_info["release_features"] # Handle both string (with | delimiter) and list inputs if isinstance(features, str): features = features.split("|") elif isinstance(features, list): # If it's already a list, use it directly pass else: features = [] # Clean up features and filter out empty ones cleaned_features = [] for feature in features: feature = feature.strip() if feature: # Only add non-empty features cleaned_features.append(feature) local_info["release_features"] = cleaned_features else: local_info["release_features"] = [] # Parse firmware categories into a list for template iteration if "firmware_category" in local_info and local_info["firmware_category"]: categories = local_info["firmware_category"] # Handle both string (with | delimiter) and list inputs if isinstance(categories, str): categories = categories.split("|") elif isinstance(categories, list): # Flatten the list in case we have nested lists from argparse append + pipe-separated values flattened_categories = [] for item in categories: if isinstance(item, str) and "|" in item: flattened_categories.extend(item.split("|")) else: flattened_categories.append(item) categories = flattened_categories else: categories = ["X-System"] # Default fallback # Clean up categories and filter out empty ones cleaned_categories = [] for category in categories: category = category.strip() if category: # Only add non-empty categories cleaned_categories.append(category) local_info["categories"] = ( cleaned_categories if cleaned_categories else ["X-System"] ) else: # Default category if none provided local_info["categories"] = ["X-System"] # Load and render the Jinja2 template template = load_template() firmware_metainfo = template.render(**local_info, date=time.strftime("%Y-%m-%d")) with open(os.path.join(dst, "firmware.metainfo.xml"), "w") as f: f.write(firmware_metainfo) def extract_exe(exe, dst): command = ["7z", "x", f"-o{dst}", exe] subprocess.check_call(command, stdout=subprocess.DEVNULL) def get_firmware_bin(root, bin_path, dst): with cd(root): shutil.copy(bin_path, os.path.join(dst, "firmware.bin")) def create_firmware_cab(exe, folder): with cd(folder): if os.name == "nt": directive = os.path.join(folder, "directive") with open(directive, "w") as wfd: wfd.write(".OPTION EXPLICIT\r\n") wfd.write(".Set CabinetNameTemplate=firmware.cab\r\n") wfd.write(".Set DiskDirectory1=.\r\n") wfd.write("firmware.bin\r\n") wfd.write("firmware.metainfo.xml\r\n") command = ["makecab.exe", "/f", directive] else: command = [ "fwupdtool", "build-cabinet", "firmware.cab", "firmware.bin", "firmware.metainfo.xml", ] subprocess.check_call(command) def main(args): with tempfile.TemporaryDirectory() as d: print(f"Using temp directory {d}") if args.exe: print("Extracting firmware exe") extract_exe(args.exe, d) print("Locating firmware bin") get_firmware_bin(d, args.bin, d) print("Creating metainfo") make_firmware_metainfo(args, d) print("Creating cabinet file") create_firmware_cab(args, d) print("Done") shutil.copy(os.path.join(d, "firmware.cab"), args.out) if __name__ == "__main__": parser = argparse.ArgumentParser( description="Create fwupd packaged from windows executables" ) parser.add_argument( "--firmware-name", help="Name of the firmware package can be customized (e.g. DellTBT)", required=True, ) parser.add_argument( "--name-variant-suffix", help="Name variant suffix for the firmware package", default="", ) parser.add_argument( "--firmware-summary", help="One line description of the firmware package" ) parser.add_argument( "--firmware-description", help="Longer description of the firmware package" ) parser.add_argument( "--device-guid", help="GUID of the device this firmware will run on, this *must* match the output of one of the GUIDs in `fwupdmgr get-devices`", required=True, ) parser.add_argument("--firmware-homepage", help="Website for the firmware provider") parser.add_argument( "--developer-name", help="Name of the firmware developer", required=True ) parser.add_argument( "--release-version", help="Version number of the firmware package", required=True, ) parser.add_argument( "--version-format", help="Version format, e.g. quad or triplet", required=True, ) parser.add_argument( "--update-protocol", help="Update protocol, e.g. org.uefi.capsule", required=True, ) parser.add_argument( "--update-message", help="Update message for LVFS compliance", default="This firmware has been tested on target hardware and verified to work correctly", ) parser.add_argument( "--release-description", help="Description of the firmware release" ) parser.add_argument( "--release-features", help="Features in the release feature list. Accept multiple calls or one call splitting them with |", action="append", default=[], ) parser.add_argument( "--release-urgency", help="Release urgency (e.g. low, medium, high, critical)", choices=["low", "medium", "high", "critical"], default="medium", ) parser.add_argument( "--firmware-category", help="Firmware category (e.g. X-System, X-Device, X-EmbeddedController, ...) Split them with | or use multiple --firmware-category arguments", action="append", choices=[ "X-System", "X-Device", "X-EmbeddedController", "X-ManagementEngine", "X-Controller", "X-CorporateManagementEngine", "X-ConsumerManagementEngine", "X-ThunderboltController", "X-PlatformSecurityProcessor", "X-CpuMicrocode", "X-Configuration", "X-Battery", "X-Camera", "X-TPM", "X-Touchpad", "X-Mouse", "X-Keyboard", "X-StorageController", "X-NetworkInterface", "X-VideoDisplay", "X-BaseboardManagementController", "X-UsbReceiver", "X-Drive", "X-FlashDrive", "X-SolidStateDrive", "X-Gpu", "X-Dock", "X-UsbDock", "X-FingerprintReader", "X-GraphicsTablet", "X-InputController", "X-Headphones", "X-Headset", ], default=[], ) parser.add_argument( "--exe", help="(optional) Executable file to extract firmware from" ) parser.add_argument( "--bin", help="Path to the .bin file (Relative if inside the executable; Absolute if outside) to use as the firmware image", required=True, ) parser.add_argument("--out", help="Output cab file path", required=True) args = parser.parse_args() main(args)