#!/usr/bin/env python3 """Wrap the FAT32 boot volume in a hybrid ISO — the flashable danos release image. Mirrors tools/make-fat-image.py in spirit: pure Python 3 standard library, no external tools (no xorriso / mkisofs / isohybrid). The output is one file that boots both ways release media is consumed: * Flashed raw to a USB stick (Etcher, dd): the ISO's system area carries an MBR whose single partition (type 0xEF, "EFI System") points at the FAT32 image embedded in the ISO, so UEFI firmware finds the ESP and runs \\EFI\\BOOT\\BOOTX64.efi off it. * Burned to optical media: an El Torito boot catalog with an EFI platform entry points at the same embedded FAT image. The ISO9660 filesystem itself is minimal but valid — a primary volume descriptor, the El Torito boot record, path tables, and a root directory that lists the boot catalog and the FAT image — so inspection tools can open it. make-iso-image.py make-iso-image.py --verify All timestamp fields are zero ("not specified") so the build is reproducible. """ import struct import sys ISO_SECTOR = 2048 # Fixed layout, in ISO sectors (LBA). Sectors 0-15 are the system area (the # hybrid MBR lives in its first 512 bytes); volume descriptors start at 16. PVD_LBA = 16 # primary volume descriptor BOOT_RECORD_LBA = 17 # El Torito boot record volume descriptor TERMINATOR_LBA = 18 # volume descriptor set terminator PATH_TABLE_L_LBA = 19 PATH_TABLE_M_LBA = 20 ROOT_DIR_LBA = 21 # root directory (one sector holds our four records) CATALOG_LBA = 22 # El Torito boot catalog ESP_LBA = 23 # the embedded FAT32 image starts here MBR_PARTITION_TYPE_ESP = 0xEF def both16(value): """ISO9660 both-byte-order encoding: little-endian then big-endian.""" return struct.pack("H", value) def both32(value): return struct.pack("I", value) def directory_record(identifier, lba, size, flags): length = 33 + len(identifier) if length % 2: length += 1 # records are padded to even length record = bytearray(length) record[0] = length record[2:10] = both32(lba) record[10:18] = both32(size) # record[18:25] is the recording date; zero = unspecified (reproducible). record[25] = flags # 0x02 = directory record[28:32] = both16(1) # volume sequence number record[32] = len(identifier) record[33:33 + len(identifier)] = identifier return bytes(record) def primary_volume_descriptor(total_sectors, path_table_size): sector = bytearray(ISO_SECTOR) sector[0] = 1 # type: primary sector[1:6] = b"CD001" sector[6] = 1 # version sector[8:40] = b"DANOS".ljust(32) # system identifier sector[40:72] = b"DANOS".ljust(32) # volume identifier sector[80:88] = both32(total_sectors) sector[120:124] = both16(1) # volume set size sector[124:128] = both16(1) # volume sequence number sector[128:132] = both16(ISO_SECTOR) # logical block size sector[132:140] = both32(path_table_size) sector[140:144] = struct.pack("I", PATH_TABLE_M_LBA) sector[156:190] = directory_record(b"\x00", ROOT_DIR_LBA, ISO_SECTOR, 0x02) sector[190:318] = b" " * 128 # volume set identifier sector[318:446] = b" " * 128 # publisher sector[446:574] = b" " * 128 # data preparer sector[574:702] = b"DANOS MAKE-ISO-IMAGE".ljust(128) # application sector[702:739] = b" " * 37 # copyright file sector[739:776] = b" " * 37 # abstract file sector[776:813] = b" " * 37 # bibliographic file unspecified_date = b"0" * 16 + b"\x00" for offset in (813, 830, 847, 864): # creation/modification/expiry/effective sector[offset:offset + 17] = unspecified_date sector[881] = 1 # file structure version return bytes(sector) def boot_record_descriptor(): sector = bytearray(ISO_SECTOR) sector[0] = 0 # type: boot record sector[1:6] = b"CD001" sector[6] = 1 sector[7:39] = b"EL TORITO SPECIFICATION".ljust(32, b"\x00") sector[71:75] = struct.pack("") image[PATH_TABLE_M_LBA * ISO_SECTOR:PATH_TABLE_M_LBA * ISO_SECTOR + len(table_m)] = table_m image[ROOT_DIR_LBA * ISO_SECTOR:(ROOT_DIR_LBA + 1) * ISO_SECTOR] = root_directory(len(esp)) image[CATALOG_LBA * ISO_SECTOR:(CATALOG_LBA + 1) * ISO_SECTOR] = boot_catalog(len(esp)) image[ESP_LBA * ISO_SECTOR:] = esp with open(out_path, "wb") as handle: handle.write(image) print(f"make-iso-image: wrote {out_path} " f"({total_sectors * ISO_SECTOR // (1024 * 1024)} MiB hybrid ISO, " f"ESP at LBA {ESP_LBA}, {esp_sectors} sectors)") def verify(path): with open(path, "rb") as handle: data = handle.read() # The hybrid MBR (the Etcher/dd boot path). if data[510] != 0x55 or data[511] != 0xAA: sys.exit("verify: missing MBR 0x55AA signature") status, _, part_type, _, part_start, part_sectors = \ struct.unpack_from(" \n" " make-iso-image.py --verify ") build(argv[1], argv[2]) return 0 if __name__ == "__main__": sys.exit(main(sys.argv))