/* * bcd_patch.c -- build a Windows-boot BCD from BCD-Template. See bcd_patch.h and * docs/appsandbox/BCD-BUILD-SPEC.md (regf + device-element layout). * * Strategy: copy the template, then (a) point {bootmgr}.default at the template's * own OS-loader object (found by Type 0x10200003 — never hardcoded), (b) CREATE * the device/osdevice/displayorder/path elements the template lacks by carving * cells into a freshly appended hbin, (c) fix sequence numbers + XOR checksum. * Every raw pointer is recomputed after any cell_alloc (realloc may move the buf). */ #include "bcd_patch.h" #include #include #include #include typedef struct { uint8_t *b; size_t len, cap; uint32_t abump, aend; } hive_t; static uint16_t rd16(const uint8_t *p){ return (uint16_t)(p[0]|(p[1]<<8)); } static uint32_t rd32(const uint8_t *p){ return p[0]|(p[1]<<8)|((uint32_t)p[2]<<16)|((uint32_t)p[3]<<24); } static void wr16(uint8_t *p, uint16_t v){ p[0]=v; p[1]=v>>8; } static void wr32(uint8_t *p, uint32_t v){ p[0]=v;p[1]=v>>8;p[2]=v>>16;p[3]=v>>24; } static uint32_t ru8(uint32_t x){ return (x+7)&~7u; } #define NIL 0xFFFFFFFFu #define BODY(h,off) ((h)->b + 0x1000 + (off) + 4) /* record body of cell at stored off */ /* ---- read-side navigation ---- */ static void nk_name(hive_t *h, uint32_t nk, char *out, int cap){ uint8_t *b = BODY(h,nk); uint16_t fl=rd16(b+0x02), nl=rd16(b+0x48); uint8_t *nm=b+0x4C; int j=0; if (fl&0x20){ for (int i=0;ib+0x28); size_t nl = h->len + 0x1000; if (nl > h->cap){ h->cap = nl*2; h->b = realloc(h->b, h->cap); } uint8_t *hb = h->b + h->len; memset(hb,0,0x1000); memcpy(hb,"hbin",4); wr32(hb+4, hbins); wr32(hb+8, 0x1000); wr32(h->b+0x28, hbins + 0x1000); h->abump = (uint32_t)(h->len + 0x20); h->aend = (uint32_t)(h->len + 0x1000); h->len = nl; } /* returns stored offset of a new allocated cell holding `payload` bytes (zeroed) */ static uint32_t cell_alloc(hive_t *h, uint32_t payload){ uint32_t need = ru8(4+payload); if (h->aend==0 || h->abump+need > h->aend){ if (h->aend && h->abump < h->aend) wr32(h->b+h->abump, h->aend - h->abump); /* free tail */ hbin_append(h); } uint32_t fo = h->abump; wr32(h->b+fo, (uint32_t)(-(int32_t)need)); memset(h->b+fo+4, 0, need-4); h->abump += need; return fo - 0x1000; } static void sublist_add(hive_t *h, uint32_t parent, uint32_t child, const char *name){ (void)name; uint32_t kids[4096]; int n = collect(h, rd32(BODY(h,parent)+0x1C), kids, 4095); kids[n++] = child; for (int i=1;i=0; j--){ char kj[128]; nk_name(h,kids[j],kj,sizeof kj); if (strcasecmp(kj,ki)>0) kids[j+1]=kids[j]; else break; } kids[j+1]=k; } uint32_t lf = cell_alloc(h, 4 + 8*n); uint8_t *lb = BODY(h,lf); memcpy(lb,"lf",2); wr16(lb+2,(uint16_t)n); for (int i=0;i 0xc0000001 (it * can't enumerate the OS entry, so no boot menu appears). Rebuilds the 'lf' subkey * list keeping only non-'4'-prefixed children (removal preserves sort order). */ static void strip_template_elems(hive_t *h, uint32_t elements){ uint32_t kids[4096]; int n=children(h,elements,kids,4096); uint32_t keep[4096]; int m=0; for (int i=0;i\Element. type: 1=SZ,3=BINARY,7=MULTI_SZ. */ static void set_element(hive_t *h, uint32_t elements, const char *name, uint32_t type, const uint8_t *data, uint32_t dlen){ uint32_t esub = find_subkey(h, elements, name); uint32_t dcell = cell_alloc(h, dlen); memcpy(BODY(h,dcell), data, dlen); if (esub != NIL){ /* update existing Element vk */ uint32_t vk = find_value(h, esub, "Element"); uint8_t *vb = BODY(h,vk); wr32(vb+0x04, dlen); wr32(vb+0x08, dcell); wr32(vb+0x0C, type); return; } uint32_t vk = cell_alloc(h, 0x14+7); { uint8_t *vb=BODY(h,vk); memcpy(vb,"vk",2); wr16(vb+0x02,7); wr32(vb+0x04,dlen); wr32(vb+0x08,dcell); wr32(vb+0x0C,type); wr16(vb+0x10,1); memcpy(vb+0x14,"Element",7); } uint32_t vlist = cell_alloc(h, 4); wr32(BODY(h,vlist), vk); uint32_t sk; { uint8_t *eb=BODY(h,elements); sk=rd32(eb+0x2C); } int nl=(int)strlen(name); uint32_t nk = cell_alloc(h, 0x4C+nl); { uint8_t *nb=BODY(h,nk); memset(nb,0,0x4C); memcpy(nb,"nk",2); wr16(nb+0x02,0x20); wr32(nb+0x10,elements); wr32(nb+0x1C,NIL); wr32(nb+0x24,1); wr32(nb+0x28,vlist); wr32(nb+0x2C,sk); wr32(nb+0x30,NIL); wr16(nb+0x48,(uint16_t)nl); memcpy(nb+0x4C,name,nl); } sublist_add(h, elements, nk, name); } /* Create a brand-new BCD object (a GUID key under Objects) with a Description\Type * value and an empty Elements subkey; returns the Elements cell offset so the * caller can set_element() into it. Used to add {fwbootmgr}, which the WIM's * BCD-Template omits but a UEFI SYSTEM store must have (else sysprep's * Sysprep_Online_Specialize_Bcd rejects the store -> "not system store"). All raw * pointers are recomputed after each cell_alloc (realloc may move the buffer). */ static uint32_t create_object(hive_t *h, uint32_t objects, const char *guid, uint32_t type){ uint32_t sk; { sk = rd32(BODY(h,objects)+0x2C); } /* reuse Objects' security cell */ int gl=(int)strlen(guid); uint32_t obj = cell_alloc(h, 0x4C+gl); /* the {guid} object key */ /* Description\Type = REG_DWORD(type) */ uint32_t dcell = cell_alloc(h, 4); wr32(BODY(h,dcell), type); uint32_t tvk = cell_alloc(h, 0x14+4); { uint8_t *vb=BODY(h,tvk); memcpy(vb,"vk",2); wr16(vb+0x02,4); wr32(vb+0x04,4); wr32(vb+0x08,dcell); wr32(vb+0x0C,4 /*REG_DWORD*/); wr16(vb+0x10,1); memcpy(vb+0x14,"Type",4); } uint32_t tvlist = cell_alloc(h,4); wr32(BODY(h,tvlist), tvk); uint32_t desc = cell_alloc(h, 0x4C+11); { uint8_t *nb=BODY(h,desc); memset(nb,0,0x4C); memcpy(nb,"nk",2); wr16(nb+0x02,0x20); wr32(nb+0x10,obj); wr32(nb+0x1C,NIL); wr32(nb+0x24,1); wr32(nb+0x28,tvlist); wr32(nb+0x2C,sk); wr32(nb+0x30,NIL); wr16(nb+0x48,11); memcpy(nb+0x4C,"Description",11); } uint32_t elem = cell_alloc(h, 0x4C+8); /* empty Elements */ { uint8_t *nb=BODY(h,elem); memset(nb,0,0x4C); memcpy(nb,"nk",2); wr16(nb+0x02,0x20); wr32(nb+0x10,obj); wr32(nb+0x14,0); wr32(nb+0x1C,NIL); wr32(nb+0x24,0); wr32(nb+0x28,NIL); wr32(nb+0x2C,sk); wr32(nb+0x30,NIL); wr16(nb+0x48,8); memcpy(nb+0x4C,"Elements",8); } /* obj's children = [Description, Elements] (sorted) */ uint32_t lf = cell_alloc(h, 4+8*2); { uint8_t *lb=BODY(h,lf); memcpy(lb,"lf",2); wr16(lb+2,2); wr32(lb+4, desc); memcpy(lb+8,"Desc",4); wr32(lb+12, elem); memcpy(lb+16,"Elem",4); } { uint8_t *nb=BODY(h,obj); memset(nb,0,0x4C); memcpy(nb,"nk",2); wr16(nb+0x02,0x20); wr32(nb+0x10,objects); wr32(nb+0x14,2); wr32(nb+0x1C,lf); wr32(nb+0x24,0); wr32(nb+0x28,NIL); wr32(nb+0x2C,sk); wr32(nb+0x30,NIL); wr16(nb+0x48,(uint16_t)gl); memcpy(nb+0x4C,guid,gl); } sublist_add(h, objects, obj, guid); /* link into Objects */ return elem; } /* Add or update a REG_DWORD value `name`=`val` on key `nk` (inline data). Used to * stamp the root\Description with System=1 (+ FirmwareModified=1), which marks a * BCD as the SYSTEM store -- bcdboot/BCD-SYS write this; the WIM's BCD-Template * lacks it, so sysprep's Sysprep_Online_Specialize_Bcd rejects ours as "not system * store" (0xC0000098) -> "could not configure hardware". Pointers recomputed after * every cell_alloc (realloc may move the buffer). */ static void set_dword_value(hive_t *h, uint32_t nk, const char *name, uint32_t val){ uint32_t vk = find_value(h, nk, name); if (vk != NIL){ /* update existing */ uint8_t *vb=BODY(h,vk); wr32(vb+0x04, 4u|0x80000000u); wr32(vb+0x08, val); wr32(vb+0x0C, 4); return; } int nl=(int)strlen(name); uint32_t nvk = cell_alloc(h, 0x14+nl); /* new vk cell */ uint32_t nval = rd32(BODY(h,nk)+0x24); uint32_t oldlist = rd32(BODY(h,nk)+0x28); uint32_t newlist = cell_alloc(h, 4*(nval+1)); /* grown value list */ { uint8_t *vb=BODY(h,nvk); memcpy(vb,"vk",2); wr16(vb+0x02,(uint16_t)nl); wr32(vb+0x04, 4u|0x80000000u); /* size 4, inline-data flag */ wr32(vb+0x08, val); wr32(vb+0x0C, 4 /*REG_DWORD*/); wr16(vb+0x10, 1); memcpy(vb+0x14, name, nl); } { uint8_t *nlb=BODY(h,newlist); if (oldlist!=NIL){ uint8_t *ob=BODY(h,oldlist); for (uint32_t i=0;ib+0x24); uint32_t objects = find_subkey(h, root, "Objects"); if (objects==NIL){ free(h->b); return -2; } /* {bootmgr} (Type 0x10100002) + the INSTALLED-OS loader (Type 0x10200003 with * a \windows systemroot but NOT the \boot.wim ramdisk path of the WinPE loader). */ uint32_t bootmgr = find_object_by_type(h, objects, 0x10100002, NULL, NULL); uint32_t loader = find_object_by_type(h, objects, 0x10200003, "22000002", "42000003"); if (loader==NIL) loader = find_object_by_type(h, objects, 0x10200003, "22000002", NULL); if (loader==NIL) loader = find_object_by_type(h, objects, 0x10200003, NULL, NULL); if (bootmgr==NIL || loader==NIL){ free(h->b); return -3; } char loader_guid[64]; nk_name(h, loader, loader_guid, sizeof loader_guid); uint32_t bm_elems = find_subkey(h, bootmgr, "Elements"); uint32_t ld_elems = find_subkey(h, loader, "Elements"); if (bm_elems==NIL || ld_elems==NIL){ free(h->b); return -4; } uint8_t blob[88], buf[256], qw[8]; uint32_t l; /* Strip the template-class (0x4xxxxxxx) placeholders the template ships in both * objects; bcdboot's live BCD has none. Do this BEFORE adding live elements. */ strip_template_elems(h, bm_elems); strip_template_elems(h, ld_elems); /* {bootmgr}: device -> ESP, default(23000003)=loader, displayorder(24000001)=[loader]. * Keeps the template's 12000004(desc)/12000005(locale)/14000006(inherit->global). */ device_blob(blob, g->esp_part_guid, g->disk_guid); set_element(h, bm_elems, "11000001", 3, blob, 88); l = str_utf16(loader_guid, buf, 0); set_element(h, bm_elems, "23000003", 1, buf, l); l = str_utf16(loader_guid, buf, 1); set_element(h, bm_elems, "24000001", 7, buf, l); l = str_utf16("\\EFI\\Microsoft\\Boot\\bootmgfw.efi", buf, 0); set_element(h, bm_elems, "12000002", 1, buf, l); /* timeout: 30s as an 8-byte LE qword (template ships a 1-byte value; bcdboot * normalizes every INTEGER (format 0x5) element to 8 bytes). */ memset(qw,0,8); qw[0]=30; set_element(h, bm_elems, "25000004", 3, qw, 8); /* loader: device + osdevice -> Windows, winload path, description, inherit. */ device_blob(blob, g->win_part_guid, g->disk_guid); set_element(h, ld_elems, "11000001", 3, blob, 88); set_element(h, ld_elems, "21000001", 3, blob, 88); l = str_utf16("\\Windows\\System32\\winload.efi", buf, 0); set_element(h, ld_elems, "12000002", 1, buf, l); /* description: bootmgr needs 12000004 to render the boot-menu entry. */ l = str_utf16("Windows 11", buf, 0); set_element(h, ld_elems, "12000004", 1, buf, l); /* inherit {bootloadersettings} -> global + hypervisor (bcdboot always writes this). */ l = str_utf16("{6efb52bf-1766-41db-a6b3-0ee5eff72bd7}", buf, 1); set_element(h, ld_elems, "14000006", 7, buf, l); /* nx=OptIn(0) and bootmenupolicy=Standard(1) as 8-byte qwords (template ships * these 1-byte; bcdboot normalizes INTEGER elements to 8 bytes). */ memset(qw,0,8); set_element(h, ld_elems, "25000020", 3, qw, 8); memset(qw,0,8); qw[0]=1; set_element(h, ld_elems, "250000c2", 3, qw, 8); /* Optional serial kernel debugging, baked in at BUILD time (never edit the * FAT ESP post-hoc — that corrupts it -> FatDiskIo timeouts -> 0xc000000d). * This is `bcdedit /debug on` + serial dbgsettings ONLY: NO bootdebug (which * halts winload waiting for a debugger) and NO EMS. Windows boots NORMALLY * to OOBE/desktop while the kernel emits KD/bugcheck output over the SPCR * serial (PL011 on ARM64 -> the QEMU -serial sink). Enable: WIN_DISK_DEBUG_SERIAL=1. */ if (getenv("WIN_DISK_DEBUG_SERIAL")){ uint8_t one=1; set_element(h, ld_elems, "16000010", 3, &one, 1); /* DebuggerEnabled = true */ memset(qw,0,8); set_element(h, ld_elems, "15000011", 3, qw, 8); /* type=SERIAL */ memset(qw,0,8); qw[0]=1; set_element(h, ld_elems, "15000012", 3, qw, 8); /* SerialPort=1 */ memset(qw,0,8); qw[0]=0x00; qw[1]=0xC2; qw[2]=0x01; set_element(h, ld_elems, "15000013", 3, qw, 8); /* Baud=115200 */ } /* ---- clean up to a true bcdboot-equivalent store ---- * (a) Convert the template's resume object (Type 0x10200004) into a live * resume loader: drop its template-class (4xxxxxxx) source elements and * give it real device/osdevice (the Windows volume) + winresume path. It * keeps the template's 12000004/12000005/14000006/16000060/17000077/ * 22000002(\hiberfil.sys)/25000008. (b) Point {bootmgr}\23000006 at it. * (c) Remove the template's orphan WinPE/Setup loader (has 42000003 -> * \boot.wim) that a real bcdboot store never carries. * Win11 ARM64 ships no memtest.efi, so (correctly) NO {memdiag} object. */ uint32_t resume = find_object_by_type(h, objects, 0x10200004, NULL, NULL); if (resume != NIL){ char resume_guid[64]; nk_name(h, resume, resume_guid, sizeof resume_guid); /* "{...}" */ uint32_t rs_elems = find_subkey(h, resume, "Elements"); if (rs_elems != NIL){ strip_template_elems(h, rs_elems); device_blob(blob, g->win_part_guid, g->disk_guid); set_element(h, rs_elems, "11000001", 3, blob, 88); /* device -> Windows volume */ set_element(h, rs_elems, "21000001", 3, blob, 88); /* osdevice -> Windows volume */ l = str_utf16("\\Windows\\system32\\winresume.efi", buf, 0); set_element(h, rs_elems, "12000002", 1, buf, l); /* path */ } char braced[80]; snprintf(braced, sizeof braced, "%s", resume_guid); l = str_utf16(braced, buf, 0); set_element(h, bm_elems, "23000006", 1, buf, l); /* {bootmgr} resume object ref */ } /* drop the orphan WinPE/Setup ramdisk loader (Type 0x10200003 WITH 42000003) */ { uint32_t winpe = find_object_by_type(h, objects, 0x10200003, "42000003", NULL); if (winpe != NIL && winpe != loader) remove_object(h, objects, winpe); } /* {fwbootmgr} (Type 0x10100001): the UEFI firmware boot manager object. The * BCD-Template omits it, but a real UEFI SYSTEM store (bcdboot output) has it, * and sysprep's Sysprep_Online_Specialize_Bcd requires it to accept the store * as the system store -- without it: "File is not system store" (0xC0000098) * -> "Windows Setup could not configure Windows to run on this hardware". * Give it displayorder -> {bootmgr} and a 30s timeout, like bcdboot. */ if (find_object_by_type(h, objects, 0x10100001, NULL, NULL) == NIL){ char bm_guid[64]; nk_name(h, bootmgr, bm_guid, sizeof bm_guid); uint32_t fw_elems = create_object(h, objects, "{a5a30fa2-3d06-4e9f-b5f4-a01df9d1fcba}", 0x10100001); l = str_utf16(bm_guid, buf, 1); set_element(h, fw_elems, "24000001", 7, buf, l); /* displayorder */ memset(qw,0,8); qw[0]=30; set_element(h, fw_elems, "25000004", 3, qw, 8); /* timeout 30s */ } /* Mark this store as the SYSTEM store: root\Description += System=dword:1 * (and FirmwareModified=1). KeyName=BCD00000000 is already in the template. * This is the value sysprep's BCD-specialize reads to accept the store as the * system store (matches bcdboot / BCD-SYS); without it -> "not system store". */ { uint32_t rootdesc = find_subkey(h, root, "Description"); if (rootdesc != NIL){ set_dword_value(h, rootdesc, "System", 1); set_dword_value(h, rootdesc, "FirmwareModified", 1); } } /* finalize: free tail of last appended hbin, equalize seq, recompute checksum */ if (h->aend && h->abump < h->aend) wr32(h->b+h->abump, h->aend - h->abump); wr32(h->b+0x08, rd32(h->b+0x04)); uint32_t s=0; for (int i=0;i<127;i++) s ^= rd32(h->b+4*i); if (s==0xFFFFFFFF) s=0xFFFFFFFE; if (s==0) s=1; wr32(h->b+0x1FC, s); *out = h->b; *out_len = h->len; return 0; }