UF2 (USB Flashing Format): PHP parsing library

UF2 is a file format, developed by Microsoft for PXT (also known as Microsoft MakeCode), that is particularly suitable for flashing microcontrollers over MSC (Mass Storage Class; aka removable flash drive).

The list of family IDs is stored in a separate JSON file:

https://github.com/microsoft/uf2/blob/master/utils/uf2families.json

This JSON file is regularly updated. The family_id enum should be kept in sync with it. For simplicity and consistency, it's strongly recommended to auto-generate this enum from uf2families.json directly. This was last done using the following commands:

$ curl -fsSLO https://github.com/microsoft/uf2/raw/90e9741f217f5a40c98ba74d663e408041037578/utils/uf2families.json
$ jq -r '
  .[]
  | "    \(.id | ascii_downcase):\n"
  + "      id: \(.short_name | ascii_downcase | gsub("-"; "_"))\n"
  + "      doc: \(.description | tojson)"
' uf2families.json

Test files, picked to cover as many different shapes as possible:

File extension

uf2

KS implementation details

License: CC0-1.0

References

This page hosts a formal specification of UF2 (USB Flashing Format) using Kaitai Struct. This specification can be automatically translated into a variety of programming languages to get a parsing library.

PHP source code to parse UF2 (USB Flashing Format)

Uf2.php

<?php
// This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild

/**
 * UF2 is a file format, developed by Microsoft for PXT (also known as
 * Microsoft MakeCode), that is particularly suitable for flashing
 * microcontrollers over MSC (Mass Storage Class; aka removable flash drive).
 * 
 * The list of family IDs is stored in a separate JSON file:
 * 
 * <https://github.com/microsoft/uf2/blob/master/utils/uf2families.json>
 * 
 * This JSON file is regularly updated. The `family_id` enum should be kept in
 * sync with it. For simplicity and consistency, it's strongly recommended to
 * auto-generate this enum from `uf2families.json` directly. This was last done
 * using the following commands:
 * 
 * ```bash
 * $ curl -fsSLO https://github.com/microsoft/uf2/raw/90e9741f217f5a40c98ba74d663e408041037578/utils/uf2families.json
 * $ jq -r '
 *   .[]
 *   | "    \(.id | ascii_downcase):\n"
 *   + "      id: \(.short_name | ascii_downcase | gsub("-"; "_"))\n"
 *   + "      doc: \(.description | tojson)"
 * ' uf2families.json
 * ```
 * 
 * Test files, picked to cover as many different shapes as possible:
 * 
 * * <https://micropython.org/download/RPI_PICO/> - a typical case: all blocks
 *   have the same family ID and `num_blocks`.
 * * <https://micropython.org/download/RPI_PICO2/> - these .uf2 files are
 *   actually two UF2 files concatenated. The first UF2 file is the standalone
 *   `family_id::rp2xxx_absolute` block that `picotool` prepends (see the
 *   `is_rp2350_e10_block` value instance in the `block` type).
 * * <https://circuitpython.org/downloads> - the builds for SAMD boards (for
 *   example
 *   [Feather M0 Express](https://circuitpython.org/board/feather_m0_express/))
 *   set no flags at all, so the field at offset 28 is read as `file_size` rather
 *   than `family_id`.
 * * <https://github.com/raspberrypi/pico-sdk-prebuilts/releases> -
 *   [Universal UF2](https://github.com/raspberrypi/pico-examples/blob/c81c855ffdedc825975a40ba357723a71358ddf0/universal/README.md#universal-binary-vs-universal-uf2)
 *   files, which are again two UF2 files concatenated. What's interesting about
 *   these is that the last block of the first file (with the family ID
 *   `family_id::rp2040`) is moved to the end of the second file, which was
 *   intended for `family_id::rp2xxx_absolute` (see
 *   <https://github.com/raspberrypi/pico-examples/blob/c81c855ffdedc825975a40ba357723a71358ddf0/universal/CMakeLists.txt#L159-L169>).
 * * <https://github.com/microsoft/pxt-microsoft-boot-sequence/releases> - some
 *   files contain blocks with the `flags.is_file_container` flag set (for
 *   example
 *   <https://github.com/microsoft/pxt-microsoft-boot-sequence/releases/download/v0.0.4/arcade-p0.uf2>
 *   is a pure file container for
 *   `file_name == "Projects/microsoft-boot-sequence.elf"`). Generated by
 *   MakeCode, the only known producer of file container UF2s.
 * * <https://github.com/umi-eng/uftwo/tree/35bccf75b4f81c43f088696a8c4a9912f1f4104e/uftwo/tests> -
 *   synthetic test files for features that real firmware doesn't seem to use
 *   (e.g. MD5 checksums).
 */

namespace {
    class Uf2 extends \Kaitai\Struct\Struct {
        public function __construct(\Kaitai\Struct\Stream $_io, ?\Kaitai\Struct\Struct $_parent = null, ?\Uf2 $_root = null) {
            parent::__construct($_io, $_parent, $_root === null ? $this : $_root);
            $this->_read();
        }

        private function _read() {
            $this->_m_firstBlock = new \Uf2\Block($this->_io, $this, $this->_root);
            $this->_m_blocks = [];
            $n = $this->firstBlock()->numBlocks() - 1;
            for ($i = 0; $i < $n; $i++) {
                $this->_m_blocks[] = new \Uf2\Block($this->_io, $this, $this->_root);
            }
        }
        protected $_m_firstBlock;
        protected $_m_blocks;
        public function firstBlock() { return $this->_m_firstBlock; }
        public function blocks() { return $this->_m_blocks; }
    }
}

namespace Uf2 {
    class Block extends \Kaitai\Struct\Struct {
        public function __construct(\Kaitai\Struct\Stream $_io, ?\Uf2 $_parent = null, ?\Uf2 $_root = null) {
            parent::__construct($_io, $_parent, $_root);
            $this->_read();
        }

        private function _read() {
            $this->_m_magic = $this->_io->readBytes(4);
            if (!($this->_m_magic == "\x55\x46\x32\x0A")) {
                throw new \Kaitai\Struct\Error\ValidationNotEqualError("\x55\x46\x32\x0A", $this->_m_magic, $this->_io, "/types/block/seq/0");
            }
            $this->_m_secondMagic = $this->_io->readBytes(4);
            if (!($this->_m_secondMagic == "\x57\x51\x5D\x9E")) {
                throw new \Kaitai\Struct\Error\ValidationNotEqualError("\x57\x51\x5D\x9E", $this->_m_secondMagic, $this->_io, "/types/block/seq/1");
            }
            $this->_m_flags = new \Uf2\Flags($this->_io, $this, $this->_root);
            $this->_m_targetAddress = $this->_io->readU4le();
            $_ = $this->_m_targetAddress;
            if (!(\Kaitai\Struct\Stream::mod($_, 4) == 0)) {
                throw new \Kaitai\Struct\Error\ValidationExprError($this->_m_targetAddress, $this->_io, "/types/block/seq/3");
            }
            $this->_m_lenPayload = $this->_io->readU4le();
            $_ = $this->_m_lenPayload;
            if (!(\Kaitai\Struct\Stream::mod($_, 4) == 0)) {
                throw new \Kaitai\Struct\Error\ValidationExprError($this->_m_lenPayload, $this->_io, "/types/block/seq/4");
            }
            $this->_m_blockNumber = $this->_io->readU4le();
            $this->_m_numBlocksRaw = $this->_io->readU4le();
            if (!($this->_m_numBlocksRaw >= $this->blockNumber() + 1)) {
                throw new \Kaitai\Struct\Error\ValidationLessThanError($this->blockNumber() + 1, $this->_m_numBlocksRaw, $this->_io, "/types/block/seq/6");
            }
            if (!($this->flags()->hasFamilyId())) {
                $this->_m_fileSize = $this->_io->readU4le();
            }
            if ($this->flags()->hasFamilyId()) {
                $this->_m_familyId = $this->_io->readU4le();
            }
            $this->_m__raw_data = $this->_io->readBytes(476);
            $_io__raw_data = new \Kaitai\Struct\Stream($this->_m__raw_data);
            $this->_m_data = new \Uf2\BlockData($_io__raw_data, $this, $this->_root);
            $this->_m_finalMagic = $this->_io->readBytes(4);
            if (!($this->_m_finalMagic == "\x30\x6F\xB1\x0A")) {
                throw new \Kaitai\Struct\Error\ValidationNotEqualError("\x30\x6F\xB1\x0A", $this->_m_finalMagic, $this->_io, "/types/block/seq/10");
            }
        }
        protected $_m_isRp2350E10Block;

        /**
         * Determines whether this is a block that `picotool` prepends to RP2350
         * flash images as a workaround for erratum RP2350-E10 (i.e. a hardware
         * bug in the A2 version of the RP2350 boot ROM).
         * 
         * Such a block is always written on its own, but its `num_blocks_raw` is
         * set to 2. If we trusted this value, we would attempt to read one block
         * too many (which would most likely fail). Therefore, we must correct it
         * to 1 before using it.
         * 
         * The conditions for detecting this block come from the
         * [`check_abs_block()`](https://github.com/raspberrypi/picotool/blob/6f6458d792b93685a11423b244a585eaa99eafcf/elf2uf2/elf2uf2.cpp#L147)
         * function in `picotool`. However, there are some differences:
         * 
         * 1. In Kaitai Struct, we cannot easily check whether all 256 payload
         *    bytes are set to `0xef`, so we only check the first and last bytes.
         * 2. There are .uf2 files in the wild where `flags.has_extension_tags`
         *    is true, but there are actually no extension tags (the first and
         *    only tag has a size of 0, which is just a terminator), so our
         *    condition allows for this case. You can download an example of such
         *    a .uf2 file here:
         *    <https://github.com/neednotapply/DC32-cfw/releases/tag/1.69.13.37>
         * 
         * It's worth noting that we cannot require the presence of the
         * `extension_tag_type::rp2_ignore_block`
         * (`UF2_EXTENSION_RP2_IGNORE_BLOCK`) tag because the UF2 files generated
         * by `picotool` prior to
         * <https://github.com/raspberrypi/picotool/commit/78c9bd121b09399823b67ee7ea89003ca0d3315f>
         * don't have it. Therefore, we check whether this tag is present only if
         * `flags.has_extension_tags` is set.
         * 
         * Test .uf2 files with this special block can be downloaded from
         * <https://micropython.org/download/RPI_PICO2/>. Note that all the .uf2
         * files there are actually two UF2 (sub)files concatenated, and this
         * Kaitai Struct implementation parses only one at a time (so in order to
         * parse both, you need something like the helper spec `uf2_files.ksy`
         * from
         * <https://github.com/kaitai-io/kaitai_struct_formats/pull/542#discussion_r3906386820>).
         * v1.24.x releases predate the `extension_tag_type::rp2_ignore_block`
         * tag, while releases v1.25.0 and later include it.
         */
        public function isRp2350E10Block() {
            if ($this->_m_isRp2350E10Block !== null)
                return $this->_m_isRp2350E10Block;
            $this->_m_isRp2350E10Block =  (( (($this->flags()->value() == 8192) || ($this->flags()->value() == 40960)) ) && ($this->familyId() == \Uf2\FamilyId::RP2XXX_ABSOLUTE) && ($this->numBlocksRaw() == 2) && ($this->blockNumber() == 0) && ($this->lenPayload() == 256) && (ord($this->data()->payload()[0]) == 239) && (ord($this->data()->payload()[strlen($this->data()->payload()) - 1]) == 239) && ( ((!($this->flags()->hasExtensionTags())) || ($this->data()->extensionTags()[0]->lenTag() == 0) || ( (($this->data()->extensionTags()[0]->lenTag() == 4) && ($this->data()->extensionTags()[0]->tagType() == \Uf2\ExtensionTagType::RP2_IGNORE_BLOCK)) )) )) ;
            return $this->_m_isRp2350E10Block;
        }
        protected $_m_numBlocks;
        public function numBlocks() {
            if ($this->_m_numBlocks !== null)
                return $this->_m_numBlocks;
            $this->_m_numBlocks = ($this->isRp2350E10Block() ? 1 : $this->numBlocksRaw());
            return $this->_m_numBlocks;
        }
        protected $_m_magic;
        protected $_m_secondMagic;
        protected $_m_flags;
        protected $_m_targetAddress;
        protected $_m_lenPayload;
        protected $_m_blockNumber;
        protected $_m_numBlocksRaw;
        protected $_m_fileSize;
        protected $_m_familyId;
        protected $_m_data;
        protected $_m_finalMagic;
        protected $_m__raw_data;
        public function magic() { return $this->_m_magic; }
        public function secondMagic() { return $this->_m_secondMagic; }
        public function flags() { return $this->_m_flags; }

        /**
         * Address in flash where `data.payload` should be written, or an offset
         * in the file specified by `data.file_name` if `flags.is_file_container`
         * is set.
         * 
         * The [official
         * spec](https://github.com/microsoft/uf2/blob/90e9741f217f5a40c98ba74d663e408041037578/README.md#payload-sizes)
         * says:
         * 
         * > In any event, payload size and target address should always be
         * > 4-byte aligned.
         */
        public function targetAddress() { return $this->_m_targetAddress; }

        /**
         * The [official
         * spec](https://github.com/microsoft/uf2/blob/90e9741f217f5a40c98ba74d663e408041037578/README.md#payload-sizes)
         * says:
         * 
         * > In any event, payload size and target address should always be
         * > 4-byte aligned.
         */
        public function lenPayload() { return $this->_m_lenPayload; }
        public function blockNumber() { return $this->_m_blockNumber; }

        /**
         * Number of blocks that make up the UF2 file to which this block
         * belongs. Every block of a file has the same value. It is at least 1
         * and every `block_number` in the file must be less than this value
         * (which is validated by this Kaitai Struct implementation).
         */
        public function numBlocksRaw() { return $this->_m_numBlocksRaw; }

        /**
         * Size of the file this block belongs to, but only if
         * `flags.is_file_container` is true. Otherwise, the official spec allows
         * this field to be set to anything - though in practice, it's always
         * zero.
         */
        public function fileSize() { return $this->_m_fileSize; }
        public function familyId() { return $this->_m_familyId; }
        public function data() { return $this->_m_data; }
        public function finalMagic() { return $this->_m_finalMagic; }
        public function _raw_data() { return $this->_m__raw_data; }
    }
}

namespace Uf2 {
    class BlockData extends \Kaitai\Struct\Struct {
        public function __construct(\Kaitai\Struct\Stream $_io, ?\Uf2\Block $_parent = null, ?\Uf2 $_root = null) {
            parent::__construct($_io, $_parent, $_root);
            $this->_read();
        }

        private function _read() {
            $this->_m_payload = $this->_io->readBytes($this->_parent()->lenPayload());
            if ($this->_parent()->flags()->isFileContainer()) {
                $this->_m_fileName = \Kaitai\Struct\Stream::bytesToStr($this->_io->readBytesTerm(0, false, true, true), "UTF-8");
            }
            if ($this->_parent()->flags()->hasExtensionTags()) {
                $this->_m_extensionTags = [];
                $i = 0;
                do {
                    $_ = new \Uf2\ExtensionTag($this->_io, $this, $this->_root);
                    $this->_m_extensionTags[] = $_;
                    $i++;
                } while (!($_->lenTag() == 0));
            }
        }
        protected $_m_md5Checksum;

        /**
         * Describes a region that doesn't need to be flashed again if the
         * checksum matches.
         * 
         * The [official
         * spec](https://github.com/microsoft/uf2/blob/90e9741f217f5a40c98ba74d663e408041037578/README.md#md5-checksum)
         * says that "This is currently only used on ESP32", but no real-world
         * firmware sample has been found (at least none of the .uf2 files from
         * <https://github.com/adafruit/tinyuf2/releases>,
         * [MicroPython](https://micropython.org/download/) or
         * [CircuitPython](https://circuitpython.org/downloads) contain it). It
         * was found only in some synthetic test files, e.g.
         * <https://github.com/umi-eng/uftwo/blob/35bccf75b4f81c43f088696a8c4a9912f1f4104e/uftwo/tests/checksum_256.uf2>.
         */
        public function md5Checksum() {
            if ($this->_m_md5Checksum !== null)
                return $this->_m_md5Checksum;
            if ($this->_parent()->flags()->hasMd5Checksum()) {
                $_pos = $this->_io->pos();
                $this->_io->seek($this->_io()->size() - 24);
                $this->_m_md5Checksum = new \Uf2\Md5Checksum($this->_io, $this, $this->_root);
                $this->_io->seek($_pos);
            }
            return $this->_m_md5Checksum;
        }
        protected $_m_payload;
        protected $_m_fileName;
        protected $_m_extensionTags;

        /**
         * The bytes to be written to `_parent.target_address`, which is either
         * an address in flash, or an offset in the file specified by `file_name`
         * if `_parent.flags.is_file_container` is set.
         */
        public function payload() { return $this->_m_payload; }
        public function fileName() { return $this->_m_fileName; }
        public function extensionTags() { return $this->_m_extensionTags; }
    }
}

namespace Uf2 {
    class ExtensionTag extends \Kaitai\Struct\Struct {
        public function __construct(\Kaitai\Struct\Stream $_io, ?\Uf2\BlockData $_parent = null, ?\Uf2 $_root = null) {
            parent::__construct($_io, $_parent, $_root);
            $this->_read();
        }

        private function _read() {
            $this->_m_lenTag = $this->_io->readU1();
            $_ = $this->_m_lenTag;
            if (!( (($_ == 0) || ($_ >= $this->minLenTag())) )) {
                throw new \Kaitai\Struct\Error\ValidationExprError($this->_m_lenTag, $this->_io, "/types/extension_tag/seq/0");
            }
            $this->_m_tagType = $this->_io->readBitsIntLe(24);
            $this->_io->alignToByte();
            if ($this->lenTag() != 0) {
                $this->_m_value = $this->_io->readBytes($this->lenValue());
            }
            $this->_m_padding = $this->_io->readBytes(\Kaitai\Struct\Stream::mod(-($this->lenTag()), 4));
        }
        protected $_m_lenValue;
        public function lenValue() {
            if ($this->_m_lenValue !== null)
                return $this->_m_lenValue;
            $this->_m_lenValue = ($this->lenTag() >= $this->minLenTag() ? $this->lenTag() - $this->minLenTag() : 0);
            return $this->_m_lenValue;
        }
        protected $_m_minLenTag;
        public function minLenTag() {
            if ($this->_m_minLenTag !== null)
                return $this->_m_minLenTag;
            $this->_m_minLenTag = 1 + 3;
            return $this->_m_minLenTag;
        }
        protected $_m_lenTag;
        protected $_m_tagType;
        protected $_m_value;
        protected $_m_padding;

        /**
         * Total size of the tag in bytes, including this byte and `tag_type`, so
         * at least 4. The exception is the last tag which terminates the list -
         * it specifies a total size of 0.
         */
        public function lenTag() { return $this->_m_lenTag; }
        public function tagType() { return $this->_m_tagType; }
        public function value() { return $this->_m_value; }

        /**
         * Tags are 4-byte aligned, so a tag whose size is not a multiple
         * of 4 is followed by padding.
         */
        public function padding() { return $this->_m_padding; }
    }
}

namespace Uf2 {
    class Flags extends \Kaitai\Struct\Struct {
        public function __construct(\Kaitai\Struct\Stream $_io, ?\Uf2\Block $_parent = null, ?\Uf2 $_root = null) {
            parent::__construct($_io, $_parent, $_root);
            $this->_read();
        }

        private function _read() {
            $this->_m_value = $this->_io->readU4le();
            $_ = $this->_m_value;
            if (!( ((($_ & ~61441) == 0) && (!( (($this->isFileContainer()) && ($this->hasExtensionTags())) ))) )) {
                throw new \Kaitai\Struct\Error\ValidationExprError($this->_m_value, $this->_io, "/types/flags/seq/0");
            }
        }
        protected $_m_hasExtensionTags;

        /**
         * Indicates whether extension tags are present after the payload.
         */
        public function hasExtensionTags() {
            if ($this->_m_hasExtensionTags !== null)
                return $this->_m_hasExtensionTags;
            $this->_m_hasExtensionTags = ($this->value() & 32768) != 0;
            return $this->_m_hasExtensionTags;
        }
        protected $_m_hasFamilyId;

        /**
         * The field at offset 28 in the block is `family_id` instead of
         * `file_size`.
         */
        public function hasFamilyId() {
            if ($this->_m_hasFamilyId !== null)
                return $this->_m_hasFamilyId;
            $this->_m_hasFamilyId = ($this->value() & 8192) != 0;
            return $this->_m_hasFamilyId;
        }
        protected $_m_hasMd5Checksum;

        /**
         * Indicates whether `md5_checksum` is present at the end of `data`.
         */
        public function hasMd5Checksum() {
            if ($this->_m_hasMd5Checksum !== null)
                return $this->_m_hasMd5Checksum;
            $this->_m_hasMd5Checksum = ($this->value() & 16384) != 0;
            return $this->_m_hasMd5Checksum;
        }
        protected $_m_isFileContainer;

        /**
         * When set, the UF2 format is used as a container for regular files
         * (akin to a TAR file, or ZIP archive without compression).
         * 
         * `target_address` is the offset in the file where the payload is to be
         * written, and `file_size` is the size of that file. The name of the
         * destination file is stored in `data.file_name`.
         */
        public function isFileContainer() {
            if ($this->_m_isFileContainer !== null)
                return $this->_m_isFileContainer;
            $this->_m_isFileContainer = ($this->value() & 4096) != 0;
            return $this->_m_isFileContainer;
        }
        protected $_m_notMainFlash;

        /**
         * Indicates that this block should be skipped when writing the device
         * flash. It can be used to store data that does not fit on the device,
         * typically embedded source code or debug info.
         */
        public function notMainFlash() {
            if ($this->_m_notMainFlash !== null)
                return $this->_m_notMainFlash;
            $this->_m_notMainFlash = ($this->value() & 1) != 0;
            return $this->_m_notMainFlash;
        }
        protected $_m_value;

        /**
         * Only the five bits that we cover in value instances below are defined,
         * and no other bit may be set. The [official
         * spec](https://github.com/microsoft/uf2/blob/90e9741f217f5a40c98ba74d663e408041037578/README.md#flags)
         * says "Currently, there are five flags defined". If any other flags are
         * added in the future, this .ksy spec will need to be updated.
         * 
         * The `is_file_container` and `has_extension_tags` flags disagree about
         * what follows the payload in `data` (a file name or a list of extension
         * tags), so this Kaitai Struct implementation treats them as mutually
         * exclusive and will reject a block that sets both. The official spec
         * does not specify how this should be handled, but logically there's a
         * conflict, so we've decided to strictly treat it as an error.
         */
        public function value() { return $this->_m_value; }
    }
}

namespace Uf2 {
    class Md5Checksum extends \Kaitai\Struct\Struct {
        public function __construct(\Kaitai\Struct\Stream $_io, ?\Uf2\BlockData $_parent = null, ?\Uf2 $_root = null) {
            parent::__construct($_io, $_parent, $_root);
            $this->_read();
        }

        private function _read() {
            $this->_m_startAddress = $this->_io->readU4le();
            $this->_m_lenRegion = $this->_io->readU4le();
            $this->_m_md5 = $this->_io->readBytes(16);
        }
        protected $_m_startAddress;
        protected $_m_lenRegion;
        protected $_m_md5;
        public function startAddress() { return $this->_m_startAddress; }
        public function lenRegion() { return $this->_m_lenRegion; }
        public function md5() { return $this->_m_md5; }
    }
}

namespace Uf2 {
    class ExtensionTagType {

        /**
         * According to the official spec, this type should be used in the last tag
         * with `len_tag == 0`, which terminates the list of extension tags.
         */
        const END = 0;

        /**
         * Page size of the target device, as a 32-bit unsigned number
         */
        const PAGE_SIZE = 780791;

        /**
         * Description of the device for which the firmware file is destined
         * (UTF-8)
         */
        const DESCRIPTION = 6622621;

        /**
         * The block is to be ignored. This value doesn't come from the official
         * spec, but from the Raspberry Pi Pico SDK. It's written with no value, so
         * the whole tag is the 4 bytes `04 e3 57 99`. See the
         * `is_rp2350_e10_block` value instance in the `block` type.
         */
        const RP2_IGNORE_BLOCK = 10049507;

        /**
         * Version of the firmware file, as a UTF-8 semver string
         */
        const VERSION = 10471356;

        /**
         * SHA-2 checksum of firmware (can be of various sizes)
         */
        const SHA2_CHECKSUM = 11824560;

        /**
         * Device type identifier, a refinement of `family_id` meant to identify a
         * kind of device rather than only an MCU. 32-bit or 64-bit number. Can be
         * a hash of the `extension_tag_type::description` tag.
         */
        const DEVICE_TYPE_ID = 13149993;

        private const _VALUES = [0 => true, 780791 => true, 6622621 => true, 10049507 => true, 10471356 => true, 11824560 => true, 13149993 => true];

        public static function isDefined(int $v): bool {
            return isset(self::_VALUES[$v]);
        }
    }
}

namespace Uf2 {
    class FamilyId {

        /**
         * ST STM32L4xx
         */
        const STM32L4 = 16738585;

        /**
         * ST STM32L5xx
         */
        const STM32L5 = 69471199;

        /**
         * ST STM32F411xC
         */
        const STM32F411XC = 114362747;

        /**
         * M0SENSE BL702
         */
        const M0SENSE = 299792458;

        /**
         * Microchip (Atmel) ATmega32
         */
        const ATMEGA32 = 374814231;

        /**
         * Microchip (Atmel) SAML21
         */
        const SAML21 = 407992330;

        /**
         * Nordic NRF52
         */
        const NRF52 = 458716255;

        /**
         * ESP32
         */
        const ESP32 = 475996592;

        /**
         * ST STM32L1xx
         */
        const STM32L1 = 505365293;

        /**
         * ST STM32L0xx
         */
        const STM32L0 = 539900561;

        /**
         * ST STM32WLxx
         */
        const STM32WL = 558239728;

        /**
         * Realtek AmebaZ RTL8710B
         */
        const RTL8710B = 585160444;

        /**
         * NXP LPC55xx
         */
        const LPC55 = 716994540;

        /**
         * ESP32-C2
         */
        const ESP32C2 = 730387100;

        /**
         * ST STM32F411xE
         */
        const STM32F411XE = 767756741;

        /**
         * ST STM32G0xx
         */
        const STM32G0 = 806311475;

        /**
         * ESP32-S31
         */
        const ESP32S31 = 822212545;

        /**
         * GD32F350
         */
        const GD32F350 = 835856582;

        /**
         * ESP32-H2
         */
        const ESP32H2 = 858203894;

        /**
         * Realtek AmebaD RTL8720D
         */
        const RTL8720D = 863621090;

        /**
         * ESP32-P4
         */
        const ESP32P4 = 1026592404;

        /**
         * Sipeed MaixPlay-U4(BL618)
         */
        const MAIXPLAY_U4 = 1265126769;

        /**
         * ST STM32G4xx
         */
        const STM32G4 = 1282483210;

        /**
         * ST STM32H5xx
         */
        const STM32H5 = 1318001757;

        /**
         * LISTENAI CSK300x/400x
         */
        const CSK4 = 1332399698;

        /**
         * NXP i.MX RT10XX
         */
        const MIMXRT10XX = 1337120189;

        /**
         * Xradiotech 809
         */
        const XR809 = 1374225320;

        /**
         * ST STM32F7xx
         */
        const STM32F7 = 1404571392;

        /**
         * ESP32-C6
         */
        const ESP32C6 = 1410195298;

        /**
         * Microchip (Atmel) SAMD51
         */
        const SAMD51 = 1427194976;

        /**
         * ST STM32F4xx
         */
        const STM32F4 = 1467308631;

        /**
         * Cypress FX2
         */
        const FX2 = 1511523995;

        /**
         * ST STM32F2xx
         */
        const STM32F2 = 1561987630;

        /**
         * ST STM32F103
         */
        const STM32F1 = 1591873650;

        /**
         * Nordic NRF52833
         */
        const NRF52833 = 1646171002;

        /**
         * ST STM32F0xx
         */
        const STM32F0 = 1685595318;

        /**
         * Beken 7231U/7231T
         */
        const BK7231U = 1733968048;

        /**
         * Microchip (Atmel) SAMD21
         */
        const SAMD21 = 1760373640;

        /**
         * WCH CH32V2xx and CH32V3xx
         */
        const CH32V = 1771791084;

        /**
         * Beken 7251/7252
         */
        const BK7251 = 1786956866;

        /**
         * ST STM32F3xx
         */
        const STM32F3 = 1803837832;

        /**
         * ST STM32F407
         */
        const STM32F407 = 1829315322;

        /**
         * ST STM32H7xx
         */
        const STM32H7 = 1840668802;

        /**
         * LISTENAI CSK60xx
         */
        const CSK6 = 1853049000;

        /**
         * Nordic NRF52832xxAB
         */
        const NRF52832XXAB = 1869948536;

        /**
         * ST STM32WBxx
         */
        const STM32WB = 1892771411;

        /**
         * Nordic NRF52832xxAA
         */
        const NRF52832XXAA = 1920081230;

        /**
         * Analog Devices MAX32690
         */
        const MAX32690 = 1947226634;

        /**
         * ESP32-C61
         */
        const ESP32C61 = 2010665156;

        /**
         * Beken 7231N
         */
        const BK7231N = 2067722800;

        /**
         * Renesas RA4M1
         */
        const RA4M1 = 2078840685;

        /**
         * Quansheng UV-K5 V3 amateur radio based on Puya Semiconductor PY32F071
         */
        const PY32F071_UVK5_V3 = 2105173743;

        /**
         * ESP8266
         */
        const ESP8266 = 2125160941;

        /**
         * NXP KL32L2x
         */
        const KL32L2 = 2139350931;

        /**
         * Nordic NRF52820_xxAA
         */
        const NRF52820 = 2181929567;

        /**
         * ST STM32F407VG
         */
        const STM32F407VG = 2410701054;

        /**
         * Analog Devices MAX78002
         */
        const MAX78002 = 2446589208;

        /**
         * Renesas RZ/A1LU (R7S7210xx)
         */
        const RZA1LU = 2501329455;

        /**
         * GigaDevice GD32VF103
         */
        const GD32VF103 = 2599435827;

        /**
         * ESP32-H4
         */
        const ESP32H4 = 2651564682;

        /**
         * Realtek Ameba1 RTL8710A
         */
        const RTL8710A = 2684343619;

        /**
         * ArteryTek AT32F415
         */
        const AT32F415 = 2697558926;

        /**
         * Nordic NRF52840
         */
        const NRF52840 = 2913282112;

        /**
         * ESP32-H21
         */
        const ESP32H21 = 3067936943;

        /**
         * ESP32-S2
         */
        const ESP32S2 = 3218951918;

        /**
         * ESP32-S3
         */
        const ESP32S3 = 3296614247;

        /**
         * ESP32-C3
         */
        const ESP32C3 = 3559628908;

        /**
         * Analog Devices MAX32650/1/2
         */
        const MAX32650 = 3594487346;

        /**
         * Boufallo 602
         */
        const BL602 = 3725750455;

        /**
         * Realtek AmebaZ2 RTL8720C
         */
        const RTL8720C = 3767498084;

        /**
         * Raspberry Pi RP2040
         */
        const RP2040 = 3834380118;

        /**
         * Raspberry Pi Microcontrollers: Absolute (unpartitioned) download
         */
        const RP2XXX_ABSOLUTE = 3834380119;

        /**
         * Raspberry Pi Microcontrollers: Data partition download
         */
        const RP2XXX_DATA = 3834380120;

        /**
         * Raspberry Pi RP2350, Secure Arm image
         */
        const RP2350_ARM_S = 3834380121;

        /**
         * Raspberry Pi RP2350, RISC-V image
         */
        const RP2350_RISCV = 3834380122;

        /**
         * Raspberry Pi RP2350, Non-secure Arm image
         */
        const RP2350_ARM_NS = 3834380123;

        /**
         * Analog Devices MAX32665/6
         */
        const MAX32666 = 4039314801;

        /**
         * ESP32-C5
         */
        const ESP32C5 = 4145808195;

        private const _VALUES = [16738585 => true, 69471199 => true, 114362747 => true, 299792458 => true, 374814231 => true, 407992330 => true, 458716255 => true, 475996592 => true, 505365293 => true, 539900561 => true, 558239728 => true, 585160444 => true, 716994540 => true, 730387100 => true, 767756741 => true, 806311475 => true, 822212545 => true, 835856582 => true, 858203894 => true, 863621090 => true, 1026592404 => true, 1265126769 => true, 1282483210 => true, 1318001757 => true, 1332399698 => true, 1337120189 => true, 1374225320 => true, 1404571392 => true, 1410195298 => true, 1427194976 => true, 1467308631 => true, 1511523995 => true, 1561987630 => true, 1591873650 => true, 1646171002 => true, 1685595318 => true, 1733968048 => true, 1760373640 => true, 1771791084 => true, 1786956866 => true, 1803837832 => true, 1829315322 => true, 1840668802 => true, 1853049000 => true, 1869948536 => true, 1892771411 => true, 1920081230 => true, 1947226634 => true, 2010665156 => true, 2067722800 => true, 2078840685 => true, 2105173743 => true, 2125160941 => true, 2139350931 => true, 2181929567 => true, 2410701054 => true, 2446589208 => true, 2501329455 => true, 2599435827 => true, 2651564682 => true, 2684343619 => true, 2697558926 => true, 2913282112 => true, 3067936943 => true, 3218951918 => true, 3296614247 => true, 3559628908 => true, 3594487346 => true, 3725750455 => true, 3767498084 => true, 3834380118 => true, 3834380119 => true, 3834380120 => true, 3834380121 => true, 3834380122 => true, 3834380123 => true, 4039314801 => true, 4145808195 => true];

        public static function isDefined(int $v): bool {
            return isset(self::_VALUES[$v]);
        }
    }
}