ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/pbcdedit/pbcdedit
Revision: 1.4
Committed: Wed Aug 14 22:03:36 2019 UTC (4 years, 9 months ago) by root
Branch: MAIN
Changes since 1.3: +69 -3 lines
Log Message:
*** empty log message ***

File Contents

# User Rev Content
1 root 1.1 #!/opt/bin/perl
2    
3     #
4     # PBCDEDIT - Copyright 2019 Marc A. Lehmann <pbcbedit@schmorp.de>
5     #
6     # SPDX-License-Identifier: GPL-3.0-or-later
7     #
8     # This program is free software: you can redistribute it and/or modify
9     # it under the terms of the GNU General Public License as published by
10     # the Free Software Foundation, either version 3 of the License, or
11     # (at your option) any later version.
12     #
13     # This program is distributed in the hope that it will be useful,
14     # but WITHOUT ANY WARRANTY; without even the implied warranty of
15     # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16     # GNU General Public License for more details.
17     #
18     # You should have received a copy of the GNU General Public License
19     # along with this program. If not, see <https://www.gnu.org/licenses/>.
20     #
21    
22     use 5.014; # numerous features
23    
24     our $VERSION = '1.0';
25     our $JSON_VERSION = 1; # the versiobn of the json objects generated by this program
26    
27     =head1 NAME
28    
29     pbcdedit - portable boot configuration data (BCD) store editor
30    
31     =head1 SYNOPSIS
32    
33     pbcdedit help # output manual page
34     pbcdedit export path/to/BCD # output BCD hive as JSON
35     pbcdedit import path/to/bcd # convert standard input to BCD hive
36     pbcdedit edit path/to/BCD edit-instructions...
37    
38     pbcdedit objects # list all supported object aliases and types
39     pbcdedit elements # list all supported bcd element aliases
40    
41     =head1 DESCRIPTION
42    
43     This program allows you to create, read and modify Boot Configuration Data
44     (BCD) stores used by Windows Vista and newer versions of Windows.
45    
46     Compared to other BCD editing programs it offers the following unique
47     features:
48    
49     =over
50    
51     =item Can create BCD hives from scratch
52    
53     Practically all other BCD editing programs force you to copy existing BCD
54     stores, which might or might not be copyrighted by Microsoft.
55    
56     =item Does not rely on Windows
57    
58     As the "portable" in the name implies, this program does not rely on
59     C<bcdedit> or other windows programs or libraries, it works on any system
60     that supports at least perl version 5.14.
61    
62     =item Decodes and encodes BCD device elements
63    
64     PBCDEDIT can concisely decode and encode BCD device element contents. This
65     is pretty unique, and offers a lot of potential that can't be realised
66     with C<bcdedit> or any programs relying on it.
67    
68     =item Minimal files
69    
70     BCD files written by PBCDEDIT are always "minimal", that is, they don't
71     contain unused data areas and therefore don't contain old and potentially
72     sensitive data.
73    
74     =back
75    
76     The target audience for this program is professionals and tinkerers who
77     are rewady to invest time into learning how it works. It is not an easy
78     program to use and requires patience and a good understanding of BCD data
79     stores.
80    
81    
82     =head1 SUBCOMMANDS
83    
84     PCBEDIT expects a subcommand as first argument that tells it what to
85     do. The following subcommands exist:
86    
87     =over
88    
89     =item help
90    
91     Displays the whole manuale page (this document).
92    
93     =item export F<path>
94    
95     Reads a BCD data store and writes a JSON representation of it to standard
96     output.
97    
98     The format of the data is explained later in this document.
99    
100     Example: read a BCD store, modify it wiht an extenral program, write it again.
101    
102     pbcdedit export BCD | modify-json-somehow | pbcdedit import BCD
103    
104     =item import F<path>
105    
106     The reverse of C<export>: Reads a JSON representation of a BCD data store
107     from standard input, and creates or replaces the given BCD data store.
108    
109     =item edit F<path> instructions...
110    
111     #TODO
112    
113     =item lsblk
114    
115     On a GNU/Linux system, you can get a list of partition device descriptors
116     using this command - the external C<lsblk> command is required, as well as
117     a mounted C</sys> file system.
118    
119     The output will be a list of all partitions in the system and C<partition>
120     descriptors for GPT and both C<legacypartition> and C<partition>
121     descritpors for MBR partitions.
122    
123     =item objects [--json]
124    
125     Outputs two tables: a table listing all type aliases with their hex bcd
126     element ID, and all object name aliases with their GUID and default type
127     (if any).
128    
129     With C<--json> it prints similar information as a JSON object, for easier parsing.
130    
131     =item elements [--json]
132    
133     Outputs a table of known element aliases with their hex ID and the format
134     type.
135    
136     With C<--json> it prints similar information as a JSON object, for easier parsing.
137    
138     =item export-regf F<path>
139    
140     This has nothing to do with BCD data stores - it takes a registry hive
141     file as argument and outputs a JSON representation of it to standard
142     output.
143    
144     Hive versions 1.2 till 1.6 are supported.
145    
146     =item import-regf F<path>
147    
148     The reverse of C<export-regf>: reads a JSON representation of a registry
149     hive from standard input and creates or replaces the registry hive file given as
150     argument.
151    
152     The written hive will always be in a slightly modified version 1.3
153     format. It's not the format windows would generate, but it should be
154     understood by any conformant hive reader.
155    
156     Note that the representation chosen by PBCDEDIT currently throws away
157     clasname data (often used for feeble attemtps at hiding stuff by
158     Microsoft) and security descriptors, so if you write anything other than
159     a BCD hive you will most likely destroy it.
160    
161     =back
162    
163    
164     =head1 BCD DATA STORE REPRESENTATION FORMAT
165    
166     A BCD data store is represented as a JSON object with one special key,
167     C<meta>, and one key per BCD object. That is, each BCD object becomes
168     one key-value pair in the object, and an additional key called C<meta>
169     contains meta information.
170    
171     Here is an abridged example of a real BCD store:
172    
173     {
174     "meta" : {
175     "version" : 1
176     },
177     "{7ae02178-821d-11e7-8813-1c872c5f5ab0}" : {
178     "type" : "application::osloader",
179     "description" : "Windows 10",
180     "device" : "partition=<null>,harddisk,gpt,9742e468-9206-48a0-b4e4-c4e9745a356a,3ce6aceb-e90c-4fd2-9fba-47cab15f6faf",
181     "osdevice" : "partition=<null>,harddisk,gpt,9742e468-9206-48a0-b4e4-c4e9745a356a,3ce6aceb-e90c-4fd2-9fba-47cab15f6faf",
182     "path" : "\\Windows\\system32\\winload.exe",
183     "systemroot" : "\\Windows"
184     },
185     "{bootloadersettings}" : {
186     "inherit" : "{globalsettings} {hypervisorsettings}"
187     },
188     "{bootmgr}" : {
189     "description" : "Windows Boot Manager",
190     "device" : "partition=<null>,harddisk,mbr,ff3ba63b,1048576",
191     "displayorder" : "{7ae02178-821d-11e7-8813-1c872c5f5ab0}",
192     "inherit" : "{globalsettings}",
193     "displaybootmenu" : 0,
194     "timeout" : 30
195     },
196     "{globalsettings}" : {
197     "inherit" : "{dbgsettings} {emssettings} {badmemory}"
198     },
199     "{hypervisorsettings}" : {
200     "hypervisorbaudrate" : 115200,
201     "hypervisordebugport" : 1,
202     "hypervisordebugtype" : 0
203     },
204     # ...
205     }
206    
207 root 1.3 =head2 Minimal BCD to boot windows
208    
209     Experimentally I found the following BCD is the minimum required to
210     successfully boot any post-XP version of Windows (suitable C<device> and
211     C<osdevice> values, of course):
212    
213     {
214     "{bootmgr}" : {
215     "resumeobject" : "{45b547a7-8ca6-4417-9eb0-a257b61f35b4}"
216     },
217    
218     "{45b547a7-8ca6-4417-9eb0-a257b61f35b1}" : {
219     "type" : "application::osloader",
220     "description" : "Windows Boot",
221     "device" : "legacypartition=<null>,harddisk,mbr,47cbc08a,1",
222     "osdevice" : "legacypartition=<null>,harddisk,mbr,47cbc08a,1",
223     "path" : "\\Windows\\system32\\winload.exe",
224     "systemroot" : "\\Windows"
225     },
226     }
227    
228     Note that minimal doesn't mean recommended - Windows itself will add stuff
229     to this during or after boot, and you might or might not run into issues
230     when installing updates as it might not be able to find the F<bootmgr>.
231    
232 root 1.1 =head2 The C<meta> key
233    
234     The C<meta> key is not stored in the BCD data store but is used only
235     by PBCDEDIT. It is always generated when exporting, and importing will
236     be refused when it exists and the version stored inside doesn't store
237     the JSON schema version of PBCDEDIT. This ensures that differemt and
238     incompatible versions of PBCDEDIT will not read and misinterüret each
239     others data.
240    
241     =head2 The object keys
242    
243     Every other key is a BCD object. There is usually a BCD object for the
244     boot manager, one for every boot option and a few others that store common
245     settings inherited by these.
246    
247     Each BCD object is represented by a GUID wrapped in curly braces. These
248     are usually random GUIDs used only to distinguish bCD objects from each
249     other. When adding a new boot option, you can simply generate a new GUID.
250    
251     Some of these GUIDs are fixed well known GUIDs which PBCDEDIT will decode
252     into human-readable strings such as C<{globalsettings}>, which is the same
253     as C<{7ea2e1ac-2e61-4728-aaa3-896d9d0a9f0e}>.
254    
255     Each BCD, object has an associated type. For example,
256     C<application::osloader> for objects loading Windows via F<winload.exe>,
257     C<application::bootsector> for real mode applications and so on.
258    
259     The type of a object is stored in the pseudo BCD element C<type> (see next
260     section).
261    
262     Some well-known objects have a default type. If an object type matches
263     its default type, then the C<type> element will be omitted. Similarly, if
264     the C<type> element is missing and the BCD object has a default type, the
265     default type will be used when writing a BCD store.
266    
267     Running F<pbcdedit objects> will give you a list of object types,
268     well-known object aliases and their default types.
269    
270     If different string keys in a JSON BCD store map to the same BCD object
271     then a random one will "win" and the others will be discarded. To avoid
272     this, you should always use the "canonical" name of a BCD object, which is
273     the human-readable form (if it exists).
274    
275     =head2 The object values - BCD elements
276    
277     The value of each BCD object entry consists of key-value pairs called BCD
278     elements.
279    
280     BCD elements are identified by a 32 bit number, but to make things
281     simpler PBCDEDIT will replace these with well-known strings such as
282     C<description>, C<device> or C<path>.
283    
284     When PBCDEDIT does not know the BCD element, it will use
285     C<custom:HHHHHHHH>, where C<HHHHHHHH> is the 8-digit hex number of the
286     BCD element. For example, C<device> would be C<custom::11000001>. You can
287     get a list of all BCD elements known to PBCDEDIT by running F<pbcdedit
288     elements>.
289    
290     What was said about duplicate keys mapping to the same object is true for
291     elements as well, so, again, you should always use the canonical name,
292     whcih is the human radable alias, if known.
293    
294     =head3 BCD element types
295    
296     Each BCD element has a type such as I<string> or I<boolean>. This type
297     determines how the value is interpreted, and most of them are pretty easy
298     to explain:
299    
300     =over
301    
302     =item string
303    
304     This is simply a unicode string. For example, the C<description> and
305     C<systemroot> elements both are of this type, one storing a human-readable
306     name for this boot option, the other a file path to the windows root
307     directory:
308    
309     "description" : "Windows 10",
310     "systemroot" : "\\Windows",
311    
312     =item boolean
313    
314     Almost as simnple are booleans, which represent I<true>/I<false>,
315     I<on>/I<off> and similar values. In the JSON form, true is represented
316     by the number C<1>, and false is represented by the number C<0>. Other
317     values will be accepted, but PBCDEDIT doesn't guarantee how these are
318     interpreted.
319    
320     For example, C<displaybootmenu> is a boolean that decides whether to
321     enable the C<F8> boot menu. In the example BCD store above, this is
322     disabled:
323    
324     "displaybootmenu" : 0,
325    
326     =item integer
327    
328     Again, very simple, this is a 64 bit integer. IT can be either specified
329     as a decimal number, as a hex number (by prefixing it with C<0x>) or as a
330     binatry number (prefix C<0b>).
331    
332     For example, the boot C<timeout> is an integer, specifying the automatic
333     boot delay in seconds:
334    
335     "timeout" : 30,
336    
337     =item integer list
338    
339     This is a list of 64 bit integers separated by whitespace. It is not used
340     much, so here is a somewhat artificial an untested exanmple of using
341     C<customactions> to specify a certain custom, eh, action to be executed
342     when pressing C<F10> at boot:
343    
344     "customactions" : "0x1000044000001 0x54000001",
345    
346     =item guid
347    
348     This represents a single GUID value wrqapped in curly braces. It is used a
349     lot to refer from one BCD object to other one.
350    
351     For example, The C<{bootmgr}> object might refer to a resume boot option
352     using C<resumeobject>:
353    
354     "resumeobject" : "{7ae02178-821d-11e7-8813-1c872c5f5ab0}",
355    
356     Human readable aliases are used and allowed.
357    
358     =item guid list
359    
360     Similar to te guid type, this represents a list of such GUIDs, separated
361     by whitespace from each other.
362    
363     For example, many BCD objects can I<inherit> elements from other BCD
364     objects by specifying the GUIDs of those other objects ina GUID list
365     called surprisingly called C<inherit>:
366    
367     "inherit" : "{dbgsettings} {emssettings} {badmemory}",
368    
369     This example also shows how human readable aliases can be used.
370    
371     =item device
372    
373     This type is why I write I<most> are easy to explain earlier: This type
374     is the pinnacle of Microsoft-typical hacks layered on top of other
375     hacks. Understanding this type took more time than writing all the rest of
376     PBCDEDIT, and because it is so complex, this type has its own subsection
377     below.
378     =back
379    
380     =head4 The BCD "device" element type
381    
382     Device elements specify, well, devices. They are used for such diverse
383     purposes such as finding a TFTP network boot imagem serial ports or VMBUS
384     devices, but most commonly they are used to specify the disk (harddisk,
385     cdrom ramdisk, vhd...) to boot from.
386    
387     The device element is kind of a mini-language in its own which is much
388     more versatile then the limited windows interface to it - BCDEDIT -
389     reveals.
390    
391     While some information can be found on the BCD store and the windows
392     registry, there is pretty much no public information about the device
393     element, so almost everything known about it had to be researched first
394     in the process of writing this script, and consequently, support for BCD
395     device elements is partial only.
396    
397     On the other hand, the expressive power of PBCDEDIT in specifying devices
398     is much bigger than BCDEDIT and therefore more cna be don with it. The
399     downside is that BCD device elements are much more complicated than what
400     you might think from reading the BCDEDIT documentation.
401    
402     In other words, simple things are complicated, and complicated things are
403     possible.
404    
405     Anyway, the general syntax of device elements is an optional GUID,
406     followed by a device type, optionally followed by hexdecimal flags in
407     angle brackets, optionally followed by C<=> and a comma-separated list of
408     arguments, some of which can be (and often are) in turn devices again.
409    
410     [{GUID}]type[<flags>][=arg,arg...]
411    
412     Here are some examples:
413    
414     boot
415     {b097d29f-bc00-11e9-8a9a-525400123456}block=file,<boot>,\\EFI"
416     locate=<null>,element,systemroot
417     partition=<null>,harddisk,mbr,47cbc08a,1048576
418     partition=<null>,harddisk,gpt,9742e468-9206-48a0-b4e4-c4e9745a356a,76d39e5f-ad1b-407e-9c05-c81eb83b57dd
419     block<1>=ramdisk,<partition=<null>,harddisk,mbr,47cbc08a,68720525312>,0,0,0,\Recovery\b097d29e-bc00-11e9-8a9a-525400123456\Winre.wim
420     block=file,<partition=<null>,harddisk,gpt,9742e468-9206-48a0-b4e4-c4e9745a356a,ee3a393a-f0de-4057-9946-88584245ed48>,\
421     binary=050000000000000048000000000000000000000000000000000000000000000000000000000000000
422    
423     I hope you are suitably impressed. I was, too, when I realized decoding
424     these binary blobs is not as easy as I had assumed.
425    
426     The optional prefixed GUID seems to refer to a device BCD object, which
427     can be used to specify more device-specific BCD elements (for example
428     C<ramdisksdidevice> and C<ramdisksdpath>).
429    
430     The flags after the type are omitted when they are C<0>. The only known
431     flag is C<1>, which seems to indicate that the parent device is invalid. I
432     don't claim to fully understand it, but it seems to indicate that the
433     boot manager has to search the device itself. Why the device is specified
434     in the first place escapes me, but a lot of this device stuff seems to be
435     badly hacked together...
436    
437     The types understood and used by PBCDEDIT are as follows (keep in mind
438     that not of all the following is necessarily supported in PBCDEDIT):
439    
440     =over
441    
442     =item binary=hex...
443    
444     This type isn't actually a real BCD element type, but a fallback for those
445     cases where PBCDEDIT can't perfectly decode a device element (except for
446     the leading GUID, which it can always decode). In such cases, it will
447     convert the device into this type with a hexdump of the element data.
448    
449     =item null
450    
451     This is another special type - sometimes, a device all zero-filled, which
452     is not valid. This can mark the absence of a device or something PBCDEDIT
453     does not understand, so it decodes it into this special "all zero" type
454     called C<null>.
455    
456     It's most commonly found in devices that can use an optional parent
457     device, when no parent device is used.
458    
459     =item boot
460    
461     Another type without parameters, this refers to the device that was booted
462     from (nowadays typically the EFI system partition).
463    
464     =item vmbus=interfacetype,interfaceinstance
465    
466     This specifies a VMBUS device with the given interface type and interface
467     instance, both of which are "naked" (no curly braces) GUIDs.
468    
469     Made-up example (couldn't find a single example on the web):
470    
471     vmbus=c376c1c3-d276-48d2-90a9-c04748072c60,12345678-a234-b234-c234-d2345678abcd
472    
473     =item partition=<parent>,devicetype,partitiontype,diskid,partitionid
474    
475     This designates a specific partition on a block device. C<< <parent>
476     >> is an optional parent device on which to search on, and is often
477     C<null>. Note that the anfgle brackets are part of the syntax.
478    
479     C<devicetypes> is one of C<harddisk>, C<floppy>, C<cdrom>, C<ramdisk>,
480     C<file> or C<vhd>, where the first three should be self-explaining,
481     C<file> is usually used to locate a device by finding a magic file, and
482     C<vhd> is used for virtual harddisks - F<.vhd> and F<-vhdx> files.
483    
484     The C<partitiontype> is either C<mbr>, C<gpt> or C<raw>, the latter being
485     used for devices without partitions, such as cdroms, where the "partition"
486     is usually the whole device.
487    
488     The C<diskid> identifies the disk or device using a unique signature, and
489     the same is true for the C<partitionid>. How these are interpreted depends
490     on the C<partitiontype>:
491    
492     =over
493    
494     =item mbr
495    
496     The C<diskid> is the 32 bit disk signature stored at offset 0x1b8 in the
497     MBR, interpreted as a 32 bit unsigned little endian integer and written as
498     hex number. That is, the bytes C<01 02 03 04> would become C<04030201>.
499    
500     Diskpart (using the C<DETAIL> command) and the C<lsblk> comamnd typically
501     found on GNU/Linux systems (using e.g. C<lsblk -o NAME,PARTUUID>) can
502     display the disk id.
503    
504     The C<partitionid> is the byte offset(!) of the partition counting from
505     the beginning of the MBR.
506    
507     Example, use the partition on the harddisk with C<diskid> C<47cbc08a>
508     starting at sector C<2048> (= 1048576 / 512).
509    
510     partition=<null>,harddisk,mbr,47cbc08a,1048576
511    
512     =item gpt
513    
514     The C<diskid> is the disk UUID/disk identifier GUID from the partition
515     table (as displayed e.g. by C<gdisk>), and the C<partitionid> is the
516     partition unique GUID (displayed using e.g. the C<gdisk> C<i> command).
517    
518     Example: use the partition C<76d39e5f-ad1b-407e-9c05-c81eb83b57dd> on GPT
519     disk C<9742e468-9206-48a0-b4e4-c4e9745a356a>.
520    
521     partition=<null>,harddisk,gpt,9742e468-9206-48a0-b4e4-c4e9745a356a,76d39e5f-ad1b-407e-9c05-c81eb83b57dd
522    
523     =item raw
524    
525     Instead of diskid and partitionid, this type only accepts a decimal disk
526     number and signifies the whole disk. BCDEDIT cannot display the resulting
527     device, and I am doubtful whether it has a useful effect.
528    
529     =back
530    
531     =item legacypartition=<parent>,devicetype,partitiontype,diskid,partitionid
532    
533     This is exactly the same as the C<partition> type, except for a tiny
534     detail: instead of using the partition start offset, this type uses the
535     partition number for MBR disks. Behaviour other partition types should be
536     the same.
537    
538     The partition number starts at C<1> and skips unused partition, so if
539     there are two primary partitions and another partition inside the extended
540     partition, the primary partitions are number C<1> and C<2> and the
541     partition inside the extended partition is number C<3>, rwegardless of any
542     gaps.
543    
544     =item locate=<parent>,locatetype,locatearg
545    
546     This device description will make the bootloader search for a partition
547     with a given path.
548    
549     The C<< <parent> >> device is the device to search on (angle brackets are
550     still part of the syntax!) If it is C<< <null> >>, then C<locate> will
551     search all disks it can find.
552    
553     C<locatetype> is either C<element> or C<path>, and merely distinguishes
554     between two different ways to specify the path to search for: C<element>
555     uses an element ID (either as hex or as name) as C<locatearg> and C<path>
556     uses a relative path as C<locatearg>.
557    
558     Example: find any partition which has the C<magicfile.xxx> path in the
559     root.
560    
561     locate=<null>,path,\magicfile.xxx
562    
563     Example: find any partition which has the path specified in the
564     C<systemroot> element (typically C<\Windows>).
565    
566     locate=<null>,element,systemroot
567    
568     =item block=devicetype,args...
569    
570     Last not least, the most complex type, C<block>, which... specifies block
571     devices (which could be inside a F<vhdx> file for example).
572    
573     C<devicetypes> is one of C<harddisk>, C<floppy>, C<cdrom>, C<ramdisk>,
574     C<file> or C<vhd> - the same as for C<partiion=>.
575    
576     The remaining arguments change depending on the C<devicetype>:
577    
578     =over
579    
580     =item block=file,<parent>,path
581    
582     Interprets the C<< <parent> >> device (typically a partition) as a
583     filesystem and specifies a file path inside.
584    
585     =item block=vhd,<parent>
586    
587     Pretty much just changes the interpretation of C<< <parent> >>, which is
588     usually a disk image (C<block=file,...)>) to be a F<vhd> or F<vhdx> file.
589    
590     =item block=ramdisk,<parent>,base,size,offset,path
591    
592     Interprets the C<< <parent> >> device as RAM disk, using the (decimal)
593     base address, byte size and byte offset inside a file specified by
594     C<path>. The numbers are usually all C<0> because they cna be extracted
595     from the RAM disk image or other parameters.
596    
597     This is most commonly used to boot C<wim> images.
598    
599     =item block=floppy,drivenum
600    
601     Refers to a removable drive identified by a number. BCDEDIT cannot display
602     the resultinfg device, and it is not clear what effect it will have.
603    
604     =item block=cdrom,drivenum
605    
606     Pretty much the same as C<floppy> but for CD-ROMs.
607    
608     =item anything else
609    
610     Probably not yet implemented. Tell me of your needs...
611    
612     =back
613    
614     =back5 Examples
615    
616     This concludes the syntax overview for device elements, but probably
617     leaves many questions open. I can't help with most of them, as I also ave
618     many questions, but I can walk you through some actual examples using mroe
619     complex aspects.
620    
621     =item locate=<block=vhd,<block=file,<locate=<null>,path,\disk.vhdx>,\disk.vhdx>>,element,path
622    
623 root 1.4 Just like with C declarations, you best treat device descriptors as
624     instructions to find your device and work your way from the inside out:
625    
626     locate=<null>,path,\disk.vhdx
627    
628     First, the innermost device descriptor searches all partitions on the
629     system for a file called F<\disk.vhdx>:
630    
631     block=file,<see above>,\disk.vhdx
632    
633     Next, this takes the device locate has found and finds a file called
634     F<\disk.vhdx> on it. This is the same file locate was using, but that is
635     only because we find the device using the same path as finding the disk
636     image, so this is purely incidental, although quite common.
637    
638     Bext, this file will be opened as a virtual disk:
639    
640     block=vhd,<see above>
641    
642     And finally, inside this disk, another C<locate> will look for a partition
643     with a path as specified in the C<path> element, which most likely will be
644     F<\Windows\system32\winload.exe>:
645    
646     locate=<see above>,element,path
647    
648     As a result, this will boot the first Windows it finds on the first
649     F<disk.vhdx> disk image it can find anywhere.
650 root 1.1
651     =item locate=<block=vhd,<block=file,<partition=<null>,harddisk,mbr,47cbc08a,242643632128>,\win10.vhdx>>,element,path
652    
653 root 1.4 Pretty much the same as the previous case, but witzh a bit of variance. First, look for a specific partition on
654     an MBR-partitioned disk:
655    
656     partition=<null>,harddisk,mbr,47cbc08a,242643632128
657    
658     Then open the file F<\win10.vhdx> on that partition:
659    
660     block=file,<see above>,\win10.vhdx
661    
662     Then, again, the file is opened as a virtual disk image:
663    
664     block=vhd,<see above>
665    
666     And again the windows loader (or whatever is in C<path>) will be searched:
667    
668     locate=<see above>,element,path
669 root 1.1
670     =item {b097d2b2-bc00-11e9-8a9a-525400123456}block<1>=ramdisk,<partition=<null>,harddisk,mbr,47cbc08a,242643632128>,0,0,0,\boot.wim
671    
672 root 1.4 This is quite different. First, it starts with a GUID. This GUID belongs
673     to a BCD object of type C<device>, which has additional parameters:
674    
675     "{b097d2b2-bc00-11e9-8a9a-525400123456}" : {
676     "type" : "device",
677     "description" : "sdi file for ramdisk",
678     "ramdisksdidevice" : "partition=<null>,harddisk,mbr,47cbc08a,1048576",
679     "ramdisksdipath" : "\boot.sdi"
680     },
681    
682     I will not go into many details, but this specifies a (presumably empty)
683     template ramdisk image (F<\boot.sdi>) that is used to initiaolize the
684     ramdisk. The F<\boot.wim> file is then extracted into it. As you cna also
685     see, this F<.sdi> file resides on a different C<partition>.
686    
687     Continuitn, as always, form the inside out, first this device descriptor
688     finds a specific partition:
689    
690     partition=<null>,harddisk,mbr,47cbc08a,242643632128
691    
692     And then specifies a C<ramdisk> image on this partition:
693    
694     block<1>=ramdisk,<see above>,0,0,0,\boot.wim
695    
696     I don't know what the purpose ofd the C<< <1> >> flag value is, but it
697     seems to be always there on this kind of entry.
698 root 1.1
699    
700     =head1 SEE ALSO
701    
702     For ideas on what you can do, and some introductory material, try
703     L<http://www.mistyprojects.co.uk/documents/BCDEdit/index.html>.
704    
705     For good reference on BCD objects and elements, see Geoff Chappels pages
706     at L<http://www.geoffchappell.com/notes/windows/boot/bcd/index.htm>.
707    
708     =head1 AUTHOR
709    
710     Written by Marc A. Lehmann <pbcdedit@schmorp.de>.
711    
712     =head1 REPORTING BUGS
713    
714     Bugs can be reported dorectly tt he author at L<pcbedit@schmorp.de>.
715    
716     =head1 BUGS AND SHORTCOMINGS
717    
718     This should be a module. Of a series of modules, even.
719    
720     Registry code should preserve classname and security descriptor data, and
721     whatever else is necessary to read and write any registry hive file.
722    
723     I am also not happy with device descriptors being strings rather than a
724     data structure, but strings are probably better for command line usage. In
725     any case,. device descriptors could be converted by simply "splitting" at
726     "=" and "," into an array reference, recursively.
727    
728     =head1 HOMEPAGE
729    
730     Original versions of this program can be found at
731     L<http://software.schmorp.de/pkg/pbcdedit>.
732    
733     =head1 COPYRIGHT
734    
735     Copyright 2019 Marc A. Lehmann, licensed under GNU GPL version 3 or later,
736     see L<https://gnu.org/licenses/gpl.html>. This is free software: you are
737     free to change and redistribute it. There is NO WARRANTY, to the extent
738     permitted by law.
739    
740     =cut
741    
742     BEGIN { require "common/sense.pm"; common::sense->import } # common sense is optional, but recommended
743    
744     use Data::Dump;
745     use Encode ();
746     use List::Util ();
747     use IO::Handle ();
748     use Time::HiRes ();
749    
750     eval { unpack "Q", pack "Q", 1 }
751     or die "perl with 64 bit integer supported required.\n";
752    
753     our $JSON = eval { require JSON::XS; JSON::XS:: }
754     // eval { require JSON::PP; JSON::PP:: }
755     // die "either JSON::XS or JSON::PP must be installed\n";
756    
757     our $json_coder = $JSON->new->utf8->pretty->canonical->relaxed;
758    
759     # hack used for debugging
760     sub xxd($$) {
761     open my $xxd, "| xxd | sed -e 's/^/\Q$_[0]\E: /'";
762     syswrite $xxd, $_[1];
763     }
764    
765     # sources and resources used for this:
766     # registry:
767     # https://github.com/msuhanov/regf/blob/master/Windows%20registry%20file%20format%20specification.md
768     # http://amnesia.gtisc.gatech.edu/~moyix/suzibandit.ltd.uk/MSc/
769     # bcd:
770     # http://www.geoffchappell.com/notes/windows/boot/bcd/index.htm
771     # https://docs.microsoft.com/en-us/previous-versions/windows/hardware/design/dn653287(v=vs.85)
772     # bcd devices:
773     # reactos' boot/environ/include/bl.h
774     # windows .mof files
775    
776     #############################################################################
777     # registry stuff
778    
779     # we use a hardcoded securitya descriptor - full access for everyone
780     my $sid = pack "H*", "010100000000000100000000"; # S-1-1-0 everyone
781     my $ace = pack "C C S< L< a*", 0, 2, 8 + (length $sid), 0x000f003f, $sid; # type flags size mask sid
782     my $sacl = "";
783     my $dacl = pack "C x S< S< x2 a*", 2, 8 + (length $ace), 1, $ace; # rev size count ace*
784     my $sd = pack "C x S< L< L< L< L< a* a* a* a*",
785     # rev flags(SE_DACL_PRESENT SE_SELF_RELATIVE) owner group sacl dacl
786     1, 0x8004,
787     20 + (length $sacl) + (length $dacl),
788     20 + (length $sacl) + (length $dacl) + (length $sid),
789     0, 20,
790     $sacl, $dacl, $sid, $sid;
791     my $sk = pack "a2 x2 x4 x4 x4 L< a*", sk => (length $sd), $sd;
792    
793     sub NO_OFS() { 0xffffffff } # file pointer "NULL" value
794    
795     sub KEY_HIVE_ENTRY() { 0x0004 }
796     sub KEY_NO_DELETE () { 0x0008 }
797     sub KEY_COMP_NAME () { 0x0020 }
798    
799     sub VALUE_COMP_NAME() { 0x0001 }
800    
801     my @regf_typename = qw(
802     none sz expand_sz binary dword dword_be link multi_sz
803     resource_list full_resource_descriptor resource_requirements_list
804     qword qword_be
805     );
806    
807     my %regf_dec_type = (
808     sz => sub { $_[0] =~ s/\x00\x00$//; Encode::decode "UTF-16LE", $_[0] },
809     expand_sz => sub { $_[0] =~ s/\x00\x00$//; Encode::decode "UTF-16LE", $_[0] },
810     link => sub { $_[0] =~ s/\x00\x00$//; Encode::decode "UTF-16LE", $_[0] },
811     multi_sz => sub { $_[0] =~ s/(?:\x00\x00)?\x00\x00$//; [ split /\x00/, (Encode::decode "UTF-16LE", $_[0]), -1 ] },
812     dword => sub { unpack "L<", shift },
813     dword_be => sub { unpack "L>", shift },
814     qword => sub { unpack "Q<", shift },
815     qword_be => sub { unpack "Q>", shift },
816     );
817    
818     my %regf_enc_type = (
819     sz => sub { (Encode::encode "UTF-16LE", $_[0]) . "\x00\x00" },
820     expand_sz => sub { (Encode::encode "UTF-16LE", $_[0]) . "\x00\x00" },
821     link => sub { (Encode::encode "UTF-16LE", $_[0]) . "\x00\x00" },
822     multi_sz => sub { (join "", map +(Encode::encode "UTF-16LE", $_) . "\x00\x00", @{ $_[0] }) . "\x00\x00" },
823     dword => sub { pack "L<", shift },
824     dword_be => sub { pack "L>", shift },
825     qword => sub { pack "Q<", shift },
826     qword_be => sub { pack "Q>", shift },
827     );
828    
829     # decode a registry hive
830     sub regf_decode($) {
831     my ($hive) = @_;
832    
833     "regf" eq substr $hive, 0, 4
834     or die "not a registry hive\n";
835    
836     my ($major, $minor) = unpack "\@20 L< L<", $hive;
837    
838     $major == 1
839     or die "registry major version is not 1, but $major\n";
840    
841     $minor >= 2 && $minor <= 6
842     or die "registry minor version is $minor, only 2 .. 6 are supported\n";
843    
844     my $bins = substr $hive, 4096;
845    
846     my $decode_key = sub {
847     my ($ofs) = @_;
848    
849     my @res;
850    
851     my ($sze, $sig) = unpack "\@$ofs l< a2", $bins;
852    
853     $sze < 0
854     or die "key node points to unallocated cell\n";
855    
856     $sig eq "nk"
857     or die "expected key node at $ofs, got '$sig'\n";
858    
859     my ($flags, $snum, $sofs, $vnum, $vofs, $knamesze) = unpack "\@$ofs ( \@6 S< \@24 L< x4 L< x4 L< L< \@76 S< )", $bins;
860    
861     my $kname = unpack "\@$ofs x80 a$knamesze", $bins;
862    
863     # classnames, security descriptors
864     #my ($cofs, $xofs, $clen) = unpack "\@$ofs ( \@44 L< L< \@72 S< )", $bins;
865     #if ($cofs != NO_OFS && $clen) {
866     # #warn "cofs $cofs+$clen\n";
867     # xxd substr $bins, $cofs, 16;
868     #}
869    
870     $kname = Encode::decode "UTF-16LE", $kname
871     unless $flags & KEY_COMP_NAME;
872    
873     if ($vnum && $vofs != NO_OFS) {
874     for ($vofs += 4; $vnum--; $vofs += 4) {
875     my $kofs = unpack "\@$vofs L<", $bins;
876    
877     my ($sze, $sig) = unpack "\@$kofs l< a2", $bins;
878    
879     $sig eq "vk"
880     or die "key values list contains invalid node (expected vk got '$sig')\n";
881    
882     my ($nsze, $dsze, $dofs, $type, $flags) = unpack "\@$kofs x4 x2 S< L< L< L< L<", $bins;
883    
884     my $name = substr $bins, $kofs + 24, $nsze;
885    
886     $name = Encode::decode "UTF-16LE", $name
887     unless $flags & VALUE_COMP_NAME;
888    
889     my $data;
890     if ($dsze & 0x80000000) {
891     $data = substr $bins, $kofs + 12, $dsze & 0x7;
892     } elsif ($dsze > 16344 && $minor > 3) { # big data
893     my ($bsze, $bsig, $bnum, $bofs) = unpack "\@$dofs l< a2 S< L<", $bins;
894    
895     for ($bofs += 4; $bnum--; $bofs += 4) {
896     my $dofs = unpack "\@$bofs L<", $bins;
897     my $dsze = unpack "\@$dofs l<", $bins;
898     $data .= substr $bins, $dofs + 4, -$dsze - 4;
899     }
900     $data = substr $data, 0, $dsze; # cells might be longer than data
901     } else {
902     $data = substr $bins, $dofs + 4, $dsze;
903     }
904    
905     $type = $regf_typename[$type] if $type < @regf_typename;
906    
907     $data = ($regf_dec_type{$type} || sub { unpack "H*", shift })
908     ->($data);
909    
910     $res[0]{$name} = [$type, $data];
911     }
912     }
913    
914     if ($sofs != NO_OFS) {
915     my $decode_key = __SUB__;
916    
917     my $decode_subkeylist = sub {
918     my ($sofs) = @_;
919    
920     my ($sze, $sig, $snum) = unpack "\@$sofs l< a2 S<", $bins;
921    
922     if ($sig eq "ri") { # index root
923     for (my $lofs = $sofs + 8; $snum--; $lofs += 4) {
924     __SUB__->(unpack "\@$lofs L<", $bins);
925     }
926     } else {
927     my $inc;
928    
929     if ($sig eq "li") { # subkey list
930     $inc = 4;
931     } elsif ($sig eq "lf" or $sig eq "lh") { # subkey list with name hints or hashes
932     $inc = 8;
933     } else {
934     die "expected subkey list at $sofs, found '$sig'\n";
935     }
936    
937     for (my $lofs = $sofs + 8; $snum--; $lofs += $inc) {
938     my ($name, $data) = $decode_key->(unpack "\@$lofs L<", $bins);
939     $res[1]{$name} = $data;
940     }
941     }
942     };
943    
944     $decode_subkeylist->($sofs);
945     }
946    
947     ($kname, \@res);
948     };
949    
950     my ($rootcell) = unpack "\@36 L<", $hive;
951    
952     my ($rname, $root) = $decode_key->($rootcell);
953    
954     [$rname, $root]
955     }
956    
957     # return a binary windows fILETIME struct
958     sub filetime_now {
959     my ($s, $ms) = Time::HiRes::gettimeofday;
960    
961     pack "Q<", $s = ($s * 1_000_000 + $ms) * 10 + 116_444_736_000_000_000
962     }
963    
964     # encode a registry hive
965     sub regf_encode($) {
966     my ($hive) = @_;
967    
968     my %typeval = map +($regf_typename[$_] => $_), 0 .. $#regf_typename;
969    
970     # the filetime is apparently used to verify log file validity,
971     # so by generating a new timestamp the log files *should* automatically
972     # become invalidated and windows would "self-heal" them.
973     # (update: has been verified by reverse engineering)
974     # possibly the fact that the two sequence numbes match might also
975     # make windows think that the hive is not dirty and ignore logs.
976     # (update: has been verified by reverse engineering)
977    
978     my $now = filetime_now;
979    
980     # we only create a single hbin
981     my $bins = pack "a4 L< L< x8 a8 x4", "hbin", 0, 0, $now;
982    
983     # append cell to $bind, return offset
984     my $cell = sub {
985     my ($cell) = @_;
986    
987     my $res = length $bins;
988    
989     $cell .= "\x00" while 4 != (7 & length $cell); # slow and ugly
990    
991     $bins .= pack "l<", -(4 + length $cell);
992     $bins .= $cell;
993    
994     $res
995     };
996    
997     my $sdofs = $cell->($sk); # add a dummy security descriptor
998     my $sdref = 0; # refcount
999     substr $bins, $sdofs + 8, 4, pack "L<", $sdofs; # flink
1000     substr $bins, $sdofs + 12, 4, pack "L<", $sdofs; # blink
1001    
1002     my $encode_key = sub {
1003     my ($kname, $kdata, $flags) = @_;
1004     my ($values, $subkeys) = @$kdata;
1005    
1006     if ($kname =~ /[^\x00-\xff]/) {
1007     $kname = Encode::encode "UTF-16LE", $kname;
1008     } else {
1009     $flags |= KEY_COMP_NAME;
1010     }
1011    
1012     # encode subkeys
1013    
1014     my @snames =
1015     map $_->[1],
1016     sort { $a->[0] cmp $b->[0] }
1017     map [(uc $_), $_],
1018     keys %$subkeys;
1019    
1020     # normally, we'd have to encode each name, but we assume one char is at most two utf-16 cp's
1021     my $maxsname = 4 * List::Util::max map length, @snames;
1022    
1023     my @sofs = map __SUB__->($_, $subkeys->{$_}, 0), @snames;
1024    
1025     # encode values
1026     my $maxvname = 4 * List::Util::max map length, keys %$values;
1027     my @vofs;
1028     my $maxdsze = 0;
1029    
1030     while (my ($vname, $v) = each %$values) {
1031     my $flags = 0;
1032    
1033     if ($vname =~ /[^\x00-\xff]/) {
1034     $vname = Encode::encode "UTF-16LE", $kname;
1035     } else {
1036     $flags |= VALUE_COMP_NAME;
1037     }
1038    
1039     my ($type, $data) = @$v;
1040    
1041     $data = ($regf_enc_type{$type} || sub { pack "H*", shift })->($data);
1042    
1043     my $dsze;
1044     my $dofs;
1045    
1046     if (length $data <= 4) {
1047     $dsze = 0x80000000 | length $data;
1048     $dofs = unpack "L<", pack "a4", $data;
1049     } else {
1050     $dsze = length $data;
1051     $dofs = $cell->($data);
1052     }
1053    
1054     $type = $typeval{$type} // ($type =~ /^[0-9]+\z/ ? $type : die "cannot encode type '$type'");
1055    
1056     push @vofs, $cell->(pack "a2 S< L< L< L< S< x2 a*",
1057     vk => (length $vname), $dsze, $dofs, $type, $flags, $vname);
1058    
1059     $maxdsze = $dsze if $maxdsze < $dsze;
1060     }
1061    
1062     # encode key
1063    
1064     my $slist = @sofs ? $cell->(pack "a2 S< L<*", li => (scalar @sofs), @sofs) : NO_OFS;
1065     my $vlist = @vofs ? $cell->(pack "L<*", @vofs) : NO_OFS;
1066    
1067     my $kdata = pack "
1068     a2 S< a8 x4 x4
1069     L< L< L< L< L< L<
1070     L< L< L< L< L< L<
1071     x4 S< S< a*
1072     ",
1073     nk => $flags, $now,
1074     (scalar @sofs), 0, $slist, NO_OFS, (scalar @vofs), $vlist,
1075     $sdofs, NO_OFS, $maxsname, 0, $maxvname, $maxdsze,
1076     length $kname, 0, $kname;
1077     ++$sdref;
1078    
1079     my $res = $cell->($kdata);
1080    
1081     substr $bins, $_ + 16, 4, pack "L<", $res
1082     for @sofs;
1083    
1084     $res
1085     };
1086    
1087     my ($rname, $root) = @$hive;
1088    
1089     my $rofs = $encode_key->($rname, $root, KEY_HIVE_ENTRY | KEY_NO_DELETE); # 4 = root key
1090    
1091     if (my $pad = -(length $bins) & 4095) {
1092     $pad -= 4;
1093     $bins .= pack "l< x$pad", $pad + 4;
1094     }
1095    
1096     substr $bins, $sdofs + 16, 4, pack "L<", $sdref; # sd refcount
1097     substr $bins, 8, 4, pack "L<", length $bins;
1098    
1099     my $base = pack "
1100     a4 L< L< a8 L< L< L< L<
1101     L< L< L<
1102     a64
1103     x396
1104     ",
1105     regf => 1974, 1974, $now, 1, 3, 0, 1,
1106     $rofs, length $bins, 1,
1107     (Encode::encode "UTF-16LE", "\\pbcdedit.reg");
1108    
1109     my $chksum = List::Util::reduce { $a ^ $b } unpack "L<*", $base;
1110     $chksum = 0xfffffffe if $chksum == 0xffffffff;
1111     $chksum = 1 if $chksum == 0;
1112    
1113     $base .= pack "L<", $chksum;
1114    
1115     $base = pack "a* \@4095 x1", $base;
1116    
1117     $base . $bins
1118     }
1119    
1120     # load and parse registry from file
1121     sub regf_load($) {
1122     my ($path) = @_;
1123     open my $regf, "<:raw", $path
1124     or die "$path: $!\n";
1125     my $size = -s $regf;
1126     $size = read $regf, my $buf, $size
1127     or die "$path: short read\n";
1128    
1129     regf_decode $buf
1130     }
1131    
1132     # encode and save registry to file
1133     sub regf_save {
1134     my ($path, $hive) = @_;
1135    
1136     $hive = regf_encode $hive;
1137    
1138     open my $regf, ">:raw", "$path~"
1139     or die "$path~: $!\n";
1140     print $regf $hive
1141     or die "$path~: short write\n";
1142     $regf->sync;
1143     close $regf;
1144    
1145     rename "$path~", $path;
1146     }
1147    
1148     #############################################################################
1149     # bcd stuff
1150    
1151     # human-readable alises for GUID object identifiers
1152     our %bcd_objects = (
1153     '{0ce4991b-e6b3-4b16-b23c-5e0d9250e5d9}' => '{emssettings}',
1154     '{1afa9c49-16ab-4a5c-4a90-212802da9460}' => '{resumeloadersettings}',
1155     '{1cae1eb7-a0df-4d4d-9851-4860e34ef535}' => '{default}',
1156     '{313e8eed-7098-4586-a9bf-309c61f8d449}' => '{kerneldbgsettings}',
1157     '{4636856e-540f-4170-a130-a84776f4c654}' => '{dbgsettings}',
1158     '{466f5a88-0af2-4f76-9038-095b170dc21c}' => '{ntldr}',
1159     '{5189b25c-5558-4bf2-bca4-289b11bd29e2}' => '{badmemory}',
1160     '{6efb52bf-1766-41db-a6b3-0ee5eff72bd7}' => '{bootloadersettings}',
1161     '{7254a080-1510-4e85-ac0f-e7fb3d444736}' => '{ssetupefi}',
1162     '{7ea2e1ac-2e61-4728-aaa3-896d9d0a9f0e}' => '{globalsettings}',
1163     '{7ff607e0-4395-11db-b0de-0800200c9a66}' => '{hypervisorsettings}',
1164     '{9dea862c-5cdd-4e70-acc1-f32b344d4795}' => '{bootmgr}',
1165     '{a1943bbc-ea85-487c-97c7-c9ede908a38a}' => '{ostargettemplatepcat}',
1166     '{a5a30fa2-3d06-4e9f-b5f4-a01df9d1fcba}' => '{fwbootmgr}',
1167     '{ae5534e0-a924-466c-b836-758539a3ee3a}' => '{ramdiskoptions}',
1168     '{b012b84d-c47c-4ed5-b722-c0c42163e569}' => '{ostargettemplateefi}',
1169     '{b2721d73-1db4-4c62-bf78-c548a880142d}' => '{memdiag}',
1170     '{cbd971bf-b7b8-4885-951a-fa03044f5d71}' => '{setuppcat}',
1171     '{fa926493-6f1c-4193-a414-58f0b2456d1e}' => '{current}',
1172     );
1173    
1174     # default types
1175     our %bcd_object_types = (
1176     '{fwbootmgr}' => 0x10100001,
1177     '{bootmgr}' => 0x10100002,
1178     '{memdiag}' => 0x10200005,
1179     '{ntldr}' => 0x10300006,
1180     '{badmemory}' => 0x20100000,
1181     '{dbgsettings}' => 0x20100000,
1182     '{emssettings}' => 0x20100000,
1183     '{globalsettings}' => 0x20100000,
1184     '{bootloadersettings}' => 0x20200003,
1185     '{hypervisorsettings}' => 0x20200003,
1186     '{kerneldbgsettings}' => 0x20200003,
1187     '{resumeloadersettings}' => 0x20200004,
1188     '{ramdiskoptions}' => 0x30000000,
1189     );
1190    
1191     # object types
1192     our %bcd_types = (
1193     0x10100001 => 'application::fwbootmgr',
1194     0x10100002 => 'application::bootmgr',
1195     0x10200003 => 'application::osloader',
1196     0x10200004 => 'application::resume',
1197     0x10100005 => 'application::memdiag',
1198     0x10100006 => 'application::ntldr',
1199     0x10100007 => 'application::setupldr',
1200     0x10400008 => 'application::bootsector',
1201     0x10400009 => 'application::startup',
1202     0x1020000a => 'application::bootapp',
1203     0x20100000 => 'settings',
1204     0x20200001 => 'inherit::fwbootmgr',
1205     0x20200002 => 'inherit::bootmgr',
1206     0x20200003 => 'inherit::osloader',
1207     0x20200004 => 'inherit::resume',
1208     0x20200005 => 'inherit::memdiag',
1209     0x20200006 => 'inherit::ntldr',
1210     0x20200007 => 'inherit::setupldr',
1211     0x20200008 => 'inherit::bootsector',
1212     0x20200009 => 'inherit::startup',
1213     0x20300000 => 'inherit::device',
1214     0x30000000 => 'device',
1215     );
1216    
1217     our %rbcd_objects = reverse %bcd_objects;
1218    
1219     our $RE_GUID = qr<([0-9a-f]{8})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{12})>i;
1220    
1221     sub dec_guid($) {
1222     my ($p1, $p2, $p3, $p4, $p5) = unpack "VvvH4H12", shift;
1223     sprintf "%08x-%04x-%04x-%s-%s", $p1, $p2, $p3, $p4, $p5;
1224     }
1225    
1226     sub enc_guid($) {
1227     $_[0] =~ /^$RE_GUID\z/o
1228     or return;
1229    
1230     pack "VvvH4H12", hex $1, hex $2, hex $3, $4, $5
1231     }
1232    
1233     # "wguid" are guids wrapped in curly braces {...} also supporting aliases
1234     sub dec_wguid($) {
1235     my $guid = "{" . (dec_guid shift) . "}";
1236    
1237     $bcd_objects{$guid} // $guid
1238     }
1239    
1240     sub enc_wguid($) {
1241     my ($guid) = @_;
1242    
1243     if (my $alias = $rbcd_objects{$guid}) {
1244     $guid = $alias;
1245     }
1246    
1247     $guid =~ /^\{($RE_GUID)\}\z/o
1248     or return;
1249    
1250     enc_guid $1
1251     }
1252    
1253     sub BCDE_CLASS () { 0xf0000000 }
1254     sub BCDE_CLASS_LIBRARY () { 0x10000000 }
1255     sub BCDE_CLASS_APPLICATION () { 0x20000000 }
1256     sub BCDE_CLASS_DEVICE () { 0x30000000 }
1257     sub BCDE_CLASS_TEMPLATE () { 0x40000000 }
1258    
1259     sub BCDE_FORMAT () { 0x0f000000 }
1260     sub BCDE_FORMAT_DEVICE () { 0x01000000 }
1261     sub BCDE_FORMAT_STRING () { 0x02000000 }
1262     sub BCDE_FORMAT_GUID () { 0x03000000 }
1263     sub BCDE_FORMAT_GUID_LIST () { 0x04000000 }
1264     sub BCDE_FORMAT_INTEGER () { 0x05000000 }
1265     sub BCDE_FORMAT_BOOLEAN () { 0x06000000 }
1266     sub BCDE_FORMAT_INTEGER_LIST () { 0x07000000 }
1267    
1268     sub dec_device;
1269     sub enc_device;
1270    
1271     sub enc_integer($) {
1272     no warnings 'portable'; # ugh
1273     my $value = shift;
1274     $value = oct $value if $value =~ /^0[bBxX]/;
1275     unpack "H*", pack "Q<", $value
1276     }
1277    
1278     our %bcde_dec = (
1279     BCDE_FORMAT_DEVICE , \&dec_device,
1280     # # for round-trip verification
1281     # BCDE_FORMAT_DEVICE , sub {
1282     # my $dev = dec_device $_[0];
1283     # $_[0] eq enc_device $dev
1284     # or die "bcd device decoding does not round trip for $_[0]\n";
1285     # $dev
1286     # },
1287     BCDE_FORMAT_STRING , sub { shift },
1288     BCDE_FORMAT_GUID , sub { dec_wguid enc_wguid shift },
1289     BCDE_FORMAT_GUID_LIST , sub { join " ", map dec_wguid enc_wguid $_, @{+shift} },
1290     BCDE_FORMAT_INTEGER , sub { unpack "Q", pack "a8", pack "H*", shift }, # integer might be 4 or 8 bytes - caused by ms coding bugs
1291     BCDE_FORMAT_BOOLEAN , sub { shift eq "00" ? 0 : 1 },
1292     BCDE_FORMAT_INTEGER_LIST, sub { join " ", unpack "Q*", pack "H*", shift }, # not sure if this cna be 4 bytes
1293     );
1294    
1295     our %bcde_enc = (
1296     BCDE_FORMAT_DEVICE , sub { binary => enc_device shift },
1297     BCDE_FORMAT_STRING , sub { sz => shift },
1298     BCDE_FORMAT_GUID , sub { sz => "{" . (dec_guid enc_wguid shift) . "}" },
1299     BCDE_FORMAT_GUID_LIST , sub { multi_sz => [map "{" . (dec_guid enc_wguid $_) . "}", split /\s+/, shift ] },
1300     BCDE_FORMAT_INTEGER , sub { binary => enc_integer shift },
1301     BCDE_FORMAT_BOOLEAN , sub { binary => shift ? "01" : "00" },
1302     BCDE_FORMAT_INTEGER_LIST, sub { binary => join "", map enc_integer $_, split /\s+/, shift },
1303     );
1304    
1305     # BCD Elements
1306     our %bcde = (
1307     0x11000001 => 'device',
1308     0x12000002 => 'path',
1309     0x12000004 => 'description',
1310     0x12000005 => 'locale',
1311     0x14000006 => 'inherit',
1312     0x15000007 => 'truncatememory',
1313     0x14000008 => 'recoverysequence',
1314     0x16000009 => 'recoveryenabled',
1315     0x1700000a => 'badmemorylist',
1316     0x1600000b => 'badmemoryaccess',
1317     0x1500000c => 'firstmegabytepolicy',
1318     0x1500000d => 'relocatephysical',
1319     0x1500000e => 'avoidlowmemory',
1320     0x1600000f => 'traditionalkseg',
1321     0x16000010 => 'bootdebug',
1322     0x15000011 => 'debugtype',
1323     0x15000012 => 'debugaddress',
1324     0x15000013 => 'debugport',
1325     0x15000014 => 'baudrate',
1326     0x15000015 => 'channel',
1327     0x12000016 => 'targetname',
1328     0x16000017 => 'noumex',
1329     0x15000018 => 'debugstart',
1330     0x12000019 => 'busparams',
1331     0x1500001a => 'hostip',
1332     0x1500001b => 'port',
1333     0x1600001c => 'dhcp',
1334     0x1200001d => 'key',
1335     0x1600001e => 'vm',
1336     0x16000020 => 'bootems',
1337     0x15000022 => 'emsport',
1338     0x15000023 => 'emsbaudrate',
1339     0x12000030 => 'loadoptions',
1340     0x16000040 => 'advancedoptions',
1341     0x16000041 => 'optionsedit',
1342     0x15000042 => 'keyringaddress',
1343     0x11000043 => 'bootstatdevice',
1344     0x12000044 => 'bootstatfilepath',
1345     0x16000045 => 'preservebootstat',
1346     0x16000046 => 'graphicsmodedisabled',
1347     0x15000047 => 'configaccesspolicy',
1348     0x16000048 => 'nointegritychecks',
1349     0x16000049 => 'testsigning',
1350     0x1200004a => 'fontpath',
1351     0x1500004b => 'integrityservices',
1352     0x1500004c => 'volumebandid',
1353     0x16000050 => 'extendedinput',
1354     0x15000051 => 'initialconsoleinput',
1355     0x15000052 => 'graphicsresolution',
1356     0x16000053 => 'restartonfailure',
1357     0x16000054 => 'highestmode',
1358     0x16000060 => 'isolatedcontext',
1359     0x15000065 => 'displaymessage',
1360     0x15000066 => 'displaymessageoverride',
1361     0x16000068 => 'nobootuxtext',
1362     0x16000069 => 'nobootuxprogress',
1363     0x1600006a => 'nobootuxfade',
1364     0x1600006b => 'bootuxreservepooldebug',
1365     0x1600006c => 'bootuxdisabled',
1366     0x1500006d => 'bootuxfadeframes',
1367     0x1600006e => 'bootuxdumpstats',
1368     0x1600006f => 'bootuxshowstats',
1369     0x16000071 => 'multibootsystem',
1370     0x16000072 => 'nokeyboard',
1371     0x15000073 => 'aliaswindowskey',
1372     0x16000074 => 'bootshutdowndisabled',
1373     0x15000075 => 'performancefrequency',
1374     0x15000076 => 'securebootrawpolicy',
1375     0x17000077 => 'allowedinmemorysettings',
1376     0x15000079 => 'bootuxtransitiontime',
1377     0x1600007a => 'mobilegraphics',
1378     0x1600007b => 'forcefipscrypto',
1379     0x1500007d => 'booterrorux',
1380     0x1600007e => 'flightsigning',
1381     0x1500007f => 'measuredbootlogformat',
1382     0x15000080 => 'displayrotation',
1383     0x15000081 => 'logcontrol',
1384     0x16000082 => 'nofirmwaresync',
1385     0x11000084 => 'windowssyspart',
1386     0x16000087 => 'numlock',
1387     0x22000001 => 'bpbstring',
1388     0x24000001 => 'displayorder',
1389     0x21000001 => 'filedevice',
1390     0x21000001 => 'osdevice',
1391     0x25000001 => 'passcount',
1392     0x26000001 => 'pxesoftreboot',
1393     0x22000002 => 'applicationname',
1394     0x24000002 => 'bootsequence',
1395     0x22000002 => 'filepath',
1396     0x22000002 => 'systemroot',
1397     0x25000002 => 'testmix',
1398     0x26000003 => 'cacheenable',
1399     0x26000003 => 'customsettings',
1400     0x23000003 => 'default',
1401     0x25000003 => 'failurecount',
1402     0x23000003 => 'resumeobject',
1403     0x26000004 => 'failuresenabled',
1404     0x26000004 => 'pae',
1405     0x26000004 => 'stampdisks',
1406     0x25000004 => 'testtofail',
1407     0x25000004 => 'timeout',
1408     0x21000005 => 'associatedosdevice',
1409     0x26000005 => 'cacheenable',
1410     0x26000005 => 'resume',
1411     0x25000005 => 'stridefailcount',
1412     0x26000006 => 'debugoptionenabled',
1413     0x25000006 => 'invcfailcount',
1414     0x23000006 => 'resumeobject',
1415     0x25000007 => 'bootux',
1416     0x25000007 => 'matsfailcount',
1417     0x24000007 => 'startupsequence',
1418     0x25000008 => 'bootmenupolicy',
1419     0x25000008 => 'randfailcount',
1420     0x25000009 => 'chckrfailcount',
1421     0x26000010 => 'detecthal',
1422     0x24000010 => 'toolsdisplayorder',
1423     0x22000011 => 'kernel',
1424     0x22000012 => 'hal',
1425     0x22000013 => 'dbgtransport',
1426     0x26000020 => 'displaybootmenu',
1427     0x25000020 => 'nx',
1428     0x26000021 => 'noerrordisplay',
1429     0x25000021 => 'pae',
1430     0x21000022 => 'bcddevice',
1431     0x26000022 => 'winpe',
1432     0x22000023 => 'bcdfilepath',
1433     0x26000024 => 'hormenabled',
1434     0x26000024 => 'hormenabled',
1435     0x26000024 => 'nocrashautoreboot',
1436     0x26000025 => 'hiberboot',
1437     0x26000025 => 'lastknowngood',
1438     0x26000026 => 'oslnointegritychecks',
1439     0x22000026 => 'passwordoverride',
1440     0x26000027 => 'osltestsigning',
1441     0x22000027 => 'pinpassphraseoverride',
1442     0x26000028 => 'processcustomactionsfirst',
1443     0x27000030 => 'customactions',
1444     0x26000030 => 'nolowmem',
1445     0x26000031 => 'persistbootsequence',
1446     0x25000031 => 'removememory',
1447     0x25000032 => 'increaseuserva',
1448     0x26000032 => 'skipstartupsequence',
1449     0x25000033 => 'perfmem',
1450     0x22000040 => 'fverecoveryurl',
1451     0x26000040 => 'vga',
1452     0x22000041 => 'fverecoverymessage',
1453     0x26000041 => 'quietboot',
1454     0x26000042 => 'novesa',
1455     0x26000043 => 'novga',
1456     0x25000050 => 'clustermodeaddressing',
1457     0x26000051 => 'usephysicaldestination',
1458     0x25000052 => 'restrictapiccluster',
1459     0x22000053 => 'evstore',
1460     0x26000054 => 'uselegacyapicmode',
1461     0x26000060 => 'onecpu',
1462     0x25000061 => 'numproc',
1463     0x26000062 => 'maxproc',
1464     0x25000063 => 'configflags',
1465     0x26000064 => 'maxgroup',
1466     0x26000065 => 'groupaware',
1467     0x25000066 => 'groupsize',
1468     0x26000070 => 'usefirmwarepcisettings',
1469     0x25000071 => 'msi',
1470     0x25000072 => 'pciexpress',
1471     0x25000080 => 'safeboot',
1472     0x26000081 => 'safebootalternateshell',
1473     0x26000090 => 'bootlog',
1474     0x26000091 => 'sos',
1475     0x260000a0 => 'debug',
1476     0x260000a1 => 'halbreakpoint',
1477     0x260000a2 => 'useplatformclock',
1478     0x260000a3 => 'forcelegacyplatform',
1479     0x260000a4 => 'useplatformtick',
1480     0x260000a5 => 'disabledynamictick',
1481     0x250000a6 => 'tscsyncpolicy',
1482     0x260000b0 => 'ems',
1483     0x250000c0 => 'forcefailure',
1484     0x250000c1 => 'driverloadfailurepolicy',
1485     0x250000c2 => 'bootmenupolicy',
1486     0x260000c3 => 'onetimeadvancedoptions',
1487     0x260000c4 => 'onetimeoptionsedit',
1488     0x250000e0 => 'bootstatuspolicy',
1489     0x260000e1 => 'disableelamdrivers',
1490     0x250000f0 => 'hypervisorlaunchtype',
1491     0x220000f1 => 'hypervisorpath',
1492     0x260000f2 => 'hypervisordebug',
1493     0x250000f3 => 'hypervisordebugtype',
1494     0x250000f4 => 'hypervisordebugport',
1495     0x250000f5 => 'hypervisorbaudrate',
1496     0x250000f6 => 'hypervisorchannel',
1497     0x250000f7 => 'bootux',
1498     0x260000f8 => 'hypervisordisableslat',
1499     0x220000f9 => 'hypervisorbusparams',
1500     0x250000fa => 'hypervisornumproc',
1501     0x250000fb => 'hypervisorrootprocpernode',
1502     0x260000fc => 'hypervisoruselargevtlb',
1503     0x250000fd => 'hypervisorhostip',
1504     0x250000fe => 'hypervisorhostport',
1505     0x250000ff => 'hypervisordebugpages',
1506     0x25000100 => 'tpmbootentropy',
1507     0x22000110 => 'hypervisorusekey',
1508     0x22000112 => 'hypervisorproductskutype',
1509     0x25000113 => 'hypervisorrootproc',
1510     0x26000114 => 'hypervisordhcp',
1511     0x25000115 => 'hypervisoriommupolicy',
1512     0x26000116 => 'hypervisorusevapic',
1513     0x22000117 => 'hypervisorloadoptions',
1514     0x25000118 => 'hypervisormsrfilterpolicy',
1515     0x25000119 => 'hypervisormmionxpolicy',
1516     0x2500011a => 'hypervisorschedulertype',
1517     0x25000120 => 'xsavepolicy',
1518     0x25000121 => 'xsaveaddfeature0',
1519     0x25000122 => 'xsaveaddfeature1',
1520     0x25000123 => 'xsaveaddfeature2',
1521     0x25000124 => 'xsaveaddfeature3',
1522     0x25000125 => 'xsaveaddfeature4',
1523     0x25000126 => 'xsaveaddfeature5',
1524     0x25000127 => 'xsaveaddfeature6',
1525     0x25000128 => 'xsaveaddfeature7',
1526     0x25000129 => 'xsaveremovefeature',
1527     0x2500012a => 'xsaveprocessorsmask',
1528     0x2500012b => 'xsavedisable',
1529     0x2500012c => 'kerneldebugtype',
1530     0x2200012d => 'kernelbusparams',
1531     0x2500012e => 'kerneldebugaddress',
1532     0x2500012f => 'kerneldebugport',
1533     0x25000130 => 'claimedtpmcounter',
1534     0x25000131 => 'kernelchannel',
1535     0x22000132 => 'kerneltargetname',
1536     0x25000133 => 'kernelhostip',
1537     0x25000134 => 'kernelport',
1538     0x26000135 => 'kerneldhcp',
1539     0x22000136 => 'kernelkey',
1540     0x22000137 => 'imchivename',
1541     0x21000138 => 'imcdevice',
1542     0x25000139 => 'kernelbaudrate',
1543     0x22000140 => 'mfgmode',
1544     0x26000141 => 'event',
1545     0x25000142 => 'vsmlaunchtype',
1546     0x25000144 => 'hypervisorenforcedcodeintegrity',
1547     0x26000145 => 'enablebootdebugpolicy',
1548     0x26000146 => 'enablebootorderclean',
1549     0x26000147 => 'enabledeviceid',
1550     0x26000148 => 'enableffuloader',
1551     0x26000149 => 'enableiuloader',
1552     0x2600014a => 'enablemassstorage',
1553     0x2600014b => 'enablerpmbprovisioning',
1554     0x2600014c => 'enablesecurebootpolicy',
1555     0x2600014d => 'enablestartcharge',
1556     0x2600014e => 'enableresettpm',
1557     0x21000150 => 'systemdatadevice',
1558     0x21000151 => 'osarcdevice',
1559     0x21000153 => 'osdatadevice',
1560     0x21000154 => 'bspdevice',
1561     0x21000155 => 'bspfilepath',
1562     0x26000202 => 'skipffumode',
1563     0x26000203 => 'forceffumode',
1564     0x25000510 => 'chargethreshold',
1565     0x26000512 => 'offmodecharging',
1566     0x25000aaa => 'bootflow',
1567     0x35000001 => 'ramdiskimageoffset',
1568     0x35000002 => 'ramdisktftpclientport',
1569     0x31000003 => 'ramdisksdidevice',
1570     0x32000004 => 'ramdisksdipath',
1571     0x35000005 => 'ramdiskimagelength',
1572     0x36000006 => 'exportascd',
1573     0x35000007 => 'ramdisktftpblocksize',
1574     0x35000008 => 'ramdisktftpwindowsize',
1575     0x36000009 => 'ramdiskmcenabled',
1576     0x3600000a => 'ramdiskmctftpfallback',
1577     0x3600000b => 'ramdisktftpvarwindow',
1578     0x45000001 => 'devicetype',
1579     0x42000002 => 'applicationrelativepath',
1580     0x42000003 => 'ramdiskdevicerelativepath',
1581     0x46000004 => 'omitosloaderelements',
1582     0x47000006 => 'elementstomigrate',
1583     0x46000010 => 'recoveryos',
1584     );
1585    
1586     our %rbcde = reverse %bcde;
1587    
1588     sub dec_bcde_id($) {
1589     $bcde{$_[0]} // sprintf "custom:%08x", $_[0]
1590     }
1591    
1592     sub enc_bcde_id($) {
1593     $_[0] =~ /^custom:([0-9a-fA-F]{8}$)/
1594     ? hex $1
1595     : $rbcde{$_[0]}
1596     }
1597    
1598     # decode/encode bcd device element - the horror, no documentaion
1599     # whatsoever, supercomplex, superinconsistent.
1600    
1601     our @dev_type = qw(block type1 legacypartition serial udp boot partition vmbus locate);
1602     our @block_type = qw(harddisk floppy cdrom ramdisk type4 file vhd);
1603     our @part_type = qw(gpt mbr raw);
1604    
1605     our $NULL_DEVICE = "\x00" x 16;
1606    
1607     # biggest bitch to decode, ever
1608     # this decoded a device portion after the GUID
1609     sub dec_device_($);
1610     sub dec_device_($) {
1611     my ($device) = @_;
1612    
1613     my $res;
1614    
1615     my ($type, $flags, $length, $pad) = unpack "VVVV", substr $device, 0, 4 * 4, "";
1616    
1617     $pad == 0
1618     or die "non-zero reserved field in device descriptor\n";
1619    
1620     if ($length == 0 && $type == 0 && $flags == 0) {
1621     return ("null", $device);
1622     }
1623    
1624     $length >= 16
1625     or die "device element size too small ($length)\n";
1626    
1627     $type = $dev_type[$type] // die "$type: unknown device type\n";
1628     #d# warn "t<$type,$flags,$length,$pad>\n";#d#
1629    
1630     $res .= $type;
1631     $res .= sprintf "<%x>", $flags if $flags;
1632    
1633     my $tail = substr $device, $length - 4 * 4, 1e9, "";
1634    
1635     $length == 4 * 4 + length $device
1636     or die "device length mismatch ($length != " . (16 + length $device) . ")\n";
1637    
1638     my $dec_path = sub {
1639     my ($path, $error) = @_;
1640    
1641     $path =~ /^((?:..)*)\x00\x00\z/s
1642     or die "$error\n";
1643    
1644     $path = Encode::decode "UTF-16LE", $1;
1645    
1646     $path
1647     };
1648    
1649     if ($type eq "partition" or $type eq "legacypartition") {
1650     my $partdata = substr $device, 0, 16, "";
1651     my ($blocktype, $parttype) = unpack "VV", substr $device, 0, 4 * 2, "";
1652    
1653     $blocktype = $block_type[$blocktype] // die "unknown block device type '$blocktype'\n";
1654     $parttype = $part_type[$parttype] // die "unknown partition type\n";
1655    
1656     my $diskid = substr $device, 0, 16, "";
1657    
1658     $diskid = $parttype eq "gpt"
1659     ? dec_guid substr $diskid, 0, 16
1660     : sprintf "%08x", unpack "V", $diskid;
1661    
1662     my $partid = $parttype eq "gpt" ? dec_guid $partdata
1663     : $type eq "partition" ? unpack "Q<", $partdata # byte offset to partition start
1664     : unpack "L<", $partdata; # partition number, one-based
1665    
1666     (my $parent, $device) = dec_device_ $device;
1667    
1668     $res .= "=";
1669     $res .= "<$parent>";
1670     $res .= ",$blocktype,$parttype,$diskid,$partid";
1671    
1672     # PartitionType (gpt, mbr, raw)
1673     # guid | partsig | disknumber
1674    
1675     } elsif ($type eq "boot") {
1676     $device =~ s/^\x00{56}\z//
1677     or die "boot device type with extra data not supported\n";
1678    
1679     } elsif ($type eq "block") {
1680     my $blocktype = unpack "V", substr $device, 0, 4, "";
1681    
1682     $blocktype = $block_type[$blocktype] // die "unknown block device type '$blocktype'\n";
1683    
1684     # decode a "file path" structure
1685     my $dec_file = sub {
1686     my ($fver, $flen, $ftype) = unpack "VVV", substr $device, 0, 4 * 3, "";
1687    
1688     my $path = substr $device, 0, $flen - 12, "";
1689    
1690     $fver == 1
1691     or die "unsupported file descriptor version '$fver'\n";
1692    
1693     $ftype == 5
1694     or die "unsupported file descriptor path type '$type'\n";
1695    
1696     (my $parent, $path) = dec_device_ $path;
1697    
1698     $path = $dec_path->($path, "file device without path");
1699    
1700     ($parent, $path)
1701     };
1702    
1703     if ($blocktype eq "file") {
1704     my ($parent, $path) = $dec_file->();
1705    
1706     $res .= "=file,<$parent>,$path";
1707    
1708     } elsif ($blocktype eq "vhd") {
1709     $device =~ s/^\x00{20}//s
1710     or die "virtualdisk has non-zero fields I don't understand\n";
1711    
1712     (my $parent, $device) = dec_device_ $device;
1713    
1714     $res .= "=vhd,<$parent>";
1715    
1716     } elsif ($blocktype eq "ramdisk") {
1717     my ($base, $size, $offset) = unpack "Q< Q< L<", substr $device, 0, 8 + 8 + 4, "";
1718     my ($subdev, $path) = $dec_file->();
1719    
1720     $res .= "=ramdisk,<$subdev>,$base,$size,$offset,$path";
1721    
1722     } else {
1723     die "unsupported block type '$blocktype'\n";
1724     }
1725    
1726     } elsif ($type eq "locate") {
1727     # mode, bcde_id, unknown, string
1728     # we assume locate has _either_ an element id _or_ a path, but not both
1729    
1730     my ($mode, $elem, $parent) = unpack "VVV", substr $device, 0, 4 * 3, "";
1731    
1732     if ($parent) {
1733     # not sure why this is an offset - it must come after the path
1734     $parent = substr $device, $parent - 4 * 3 - 4 * 4, 1e9, "";
1735     ($parent, my $tail) = dec_device_ $parent;
1736     0 == length $tail
1737     or die "trailing data after locate device parent\n";
1738     } else {
1739     $parent = "null";
1740     }
1741    
1742     my $path = $device; $device = "";
1743     $path = $dec_path->($path, "device locate mode without path");
1744    
1745     $res .= "=<$parent>,";
1746    
1747     if ($mode == 0) { # "Element"
1748     !length $path
1749     or die "device locate mode 0 having non-empty path ($mode, $elem, $path)\n";
1750    
1751     $elem = dec_bcde_id $elem;
1752     $res .= "element,$elem";
1753    
1754     } elsif ($mode == 1) { # "String"
1755     !$elem
1756     or die "device locate mode 1 having non-zero element\n";
1757    
1758     $res .= "path,$path";
1759     } else {
1760     # mode 2 maybe called "ElementChild" with element and parent device? example needed
1761     die "device locate mode '$mode' not supported\n";
1762     }
1763    
1764     } elsif ($type eq "vmbus") {
1765     my $type = dec_guid substr $device, 0, 16, "";
1766     my $instance = dec_guid substr $device, 0, 16, "";
1767    
1768     $device =~ s/^\x00{24}\z//
1769     or die "vmbus has non-zero fields I don't understand\n";
1770    
1771     $res .= "=$type,$instance";
1772    
1773     } else {
1774     die "unsupported device type '$type'\n";
1775     }
1776    
1777     warn "unexpected trailing device data($res), " . unpack "H*",$device
1778     if length $device;
1779     #length $device
1780     # and die "unexpected trailing device data\n";
1781    
1782     ($res, $tail)
1783     }
1784    
1785     # decode a full binary BCD device descriptor
1786     sub dec_device($) {
1787     my ($device) = @_;
1788    
1789     $device = pack "H*", $device;
1790    
1791     my $guid = dec_guid substr $device, 0, 16, "";
1792     $guid = $guid eq "00000000-0000-0000-0000-000000000000"
1793     ? "" : "{$guid}";
1794    
1795     eval {
1796     my ($dev, $tail) = dec_device_ $device;
1797    
1798     $tail eq ""
1799     or die "unsupported trailing data after device descriptor\n";
1800    
1801     "$guid$dev"
1802     # } // scalar ((warn $@), "$guid$fallback")
1803     } // ($guid . "binary=" . unpack "H*", $device)
1804     }
1805    
1806     sub indexof($@) {
1807     my $value = shift;
1808    
1809     for (0 .. $#_) {
1810     $value eq $_[$_]
1811     and return $_;
1812     }
1813    
1814     undef
1815     }
1816    
1817     # encode the device portion after the GUID
1818     sub enc_device_;
1819     sub enc_device_ {
1820     my ($device) = @_;
1821    
1822     my $enc_path = sub {
1823     my $path = shift;
1824     $path =~ s/\//\\/g;
1825     (Encode::encode "UTF-16LE", $path) . "\x00\x00"
1826     };
1827    
1828     my $enc_file = sub {
1829     my ($parent, $path) = @_; # parent and path must already be encoded
1830    
1831     $path = $parent . $path;
1832    
1833     # fver 1, ftype 5
1834     pack "VVVa*", 1, 12 + length $path, 5, $path
1835     };
1836    
1837     my $parse_path = sub {
1838     s/^([\/\\][^<>"|?*\x00-\x1f]*)//
1839     or die "$_: invalid path\n";
1840    
1841     $enc_path->($1)
1842     };
1843    
1844     my $parse_parent = sub {
1845     my $parent;
1846    
1847     if (s/^<//) {
1848     ($parent, $_) = enc_device_ $_;
1849     s/^>//
1850     or die "$device: syntax error: parent device not followed by '>'\n";
1851     } else {
1852     $parent = $NULL_DEVICE;
1853     }
1854    
1855     $parent
1856     };
1857    
1858     for ($device) {
1859     s/^([a-z]+)//
1860     or die "$_: device does not start with type string\n";
1861    
1862     my $type = $1;
1863     my $flags = s/^<([0-9a-fA-F]+)>// ? hex $1 : 0;
1864     my $payload;
1865    
1866     if ($type eq "binary") {
1867     s/^=([0-9a-fA-F]+)//
1868     or die "binary type must have a hex string argument\n";
1869    
1870     $payload = pack "H*", $1;
1871    
1872     } elsif ($type eq "null") {
1873     return ($NULL_DEVICE, $_);
1874    
1875     } elsif ($type eq "boot") {
1876     $payload = "\x00" x 56;
1877    
1878     } elsif ($type eq "partition" or $type eq "legacypartition") {
1879     s/^=//
1880     or die "$_: missing '=' after $type\n";
1881    
1882     my $parent = $parse_parent->();
1883    
1884     s/^,//
1885     or die "$_: comma missing after partition parent device\n";
1886    
1887     s/^([a-z]+),//
1888     or die "$_: partition does not start with block type (e.g. hd or vhd)\n";
1889     my $blocktype = $1;
1890    
1891     s/^([a-z]+),//
1892     or die "$_: partition block type not followed by partiton type\n";
1893     my $parttype = $1;
1894    
1895     my ($partdata, $diskdata);
1896    
1897     if ($parttype eq "mbr") {
1898     s/^([0-9a-f]{8}),//i
1899     or die "$_: partition mbr disk id malformed (must be e.g. 1234abcd)\n";
1900     $diskdata = pack "Vx12", hex $1;
1901    
1902     s/^([0-9]+)//
1903     or die "$_: partition number or offset is missing or malformed (must be decimal)\n";
1904    
1905     # the following works for both 64 bit offset and 32 bit partno
1906     $partdata = pack "Q< x8", $1;
1907    
1908     } elsif ($parttype eq "gpt") {
1909     s/^($RE_GUID),//
1910     or die "$_: partition disk guid missing or malformed\n";
1911     $diskdata = enc_guid $1;
1912    
1913     s/^($RE_GUID)//
1914     or die "$_: partition guid missing or malformed\n";
1915     $partdata = enc_guid $1;
1916    
1917     } elsif ($parttype eq "raw") {
1918     s/^([0-9]+)//
1919     or die "$_: partition disk number missing or malformed (must be decimal)\n";
1920    
1921     $partdata = pack "L< x12", $1;
1922    
1923     } else {
1924     die "$parttype: partition type not supported\n";
1925     }
1926    
1927     $payload = pack "a16 L< L< a16 a*",
1928     $partdata,
1929     (indexof $blocktype, @block_type),
1930     (indexof $parttype, @part_type),
1931     $diskdata,
1932     $parent;
1933    
1934     } elsif ($type eq "locate") {
1935     s/^=//
1936     or die "$_: missing '=' after $type\n";
1937    
1938     my ($mode, $elem, $path);
1939    
1940     my $parent = $parse_parent->();
1941    
1942     s/^,//
1943     or die "$_: missing comma after locate parent device\n";
1944    
1945     if (s/^element,//) {
1946     s/^([0-9a-z]+)//i
1947     or die "$_ locate element must be either name or 8-digit hex id\n";
1948     $elem = enc_bcde_id $1;
1949     $mode = 0;
1950     $path = $enc_path->("");
1951    
1952     } elsif (s/^path,//) {
1953     $mode = 1;
1954     $path = $parse_path->();
1955    
1956     } else {
1957     die "$_ second locate argument must be subtype (either element or path)\n";
1958     }
1959    
1960     if ($parent ne $NULL_DEVICE) {
1961     ($parent, $path) = (4 * 4 + 4 * 3 + length $path, "$path$parent");
1962     } else {
1963     $parent = 0;
1964     }
1965    
1966     $payload = pack "VVVa*", $mode, $elem, $parent, $path;
1967    
1968     } elsif ($type eq "block") {
1969     s/^=//
1970     or die "$_: missing '=' after $type\n";
1971    
1972     s/^([a-z]+),//
1973     or die "$_: block device does not start with block type (e.g. disk)\n";
1974     my $blocktype = $1;
1975    
1976     my $blockdata;
1977    
1978     if ($blocktype eq "file") {
1979     my $parent = $parse_parent->();
1980     s/^,// or die "$_: comma missing after file block device parent\n";
1981     my $path = $parse_path->();
1982    
1983     $blockdata = $enc_file->($parent, $path);
1984    
1985     } elsif ($blocktype eq "vhd") {
1986     $blockdata = "\x00" x 20; # ENOTUNDERSTOOD
1987     $blockdata .= $parse_parent->();
1988    
1989     } elsif ($blocktype eq "ramdisk") {
1990     my $parent = $parse_parent->();
1991    
1992     s/^,(\d+),(\d+),(\d+),//a
1993     or die "$_: missing ramdisk base,size,offset after ramdisk parent device\n";
1994    
1995     my ($base, $size, $offset) = ($1, $2, $3);
1996    
1997     my $path = $parse_path->();
1998    
1999     $blockdata = pack "Q< Q< L< a*", $base, $size, $offset, $enc_file->($parent, $path);
2000    
2001     } elsif ($blocktype eq "cdrom" or $blocktype eq "floppy") {
2002     # this is guesswork
2003     s/^(\d+)//a
2004     or die "$_: missing device number for cdrom\n";
2005     $blockdata = pack "V", $1;
2006    
2007     } else {
2008     die "$blocktype: unsupported block type (must be file, vhd, ramdisk, floppy, cdrom)\n";
2009     }
2010    
2011     $payload = pack "Va*",
2012     (indexof $blocktype, @block_type),
2013     $blockdata;
2014    
2015     } elsif ($type eq "vmbus") {
2016     s/^=($RE_GUID)//
2017     or die "$_: malformed or missing vmbus interface type guid\n";
2018     my $type = enc_guid $1;
2019     s/^,($RE_GUID)//
2020     or die "$_: malformed or missing vmbus interface instance guid\n";
2021     my $instance = enc_guid $1;
2022    
2023     $payload = pack "a16a16x24", $type, $instance;
2024    
2025     } else {
2026     die "$type: not a supported device type (binary, null, boot, legacypartition, partition, block, locate)\n";
2027     }
2028    
2029     return (
2030     (pack "VVVVa*", (indexof $type, @dev_type), $flags, 16 + length $payload, 0, $payload),
2031     $_
2032     );
2033     }
2034     }
2035    
2036     # encode a full binary BCD device descriptor
2037     sub enc_device {
2038     my ($device) = @_;
2039    
2040     my $guid = "\x00" x 16;
2041    
2042     if ($device =~ s/^\{([A-Za-z0-9\-]+)\}//) {
2043     $guid = enc_guid $1
2044     or die "$device: does not start with valid guid\n";
2045     }
2046    
2047     my ($descriptor, $tail) = enc_device_ $device;
2048    
2049     length $tail
2050     and die "$device: garbage after device descriptor\n";
2051    
2052     unpack "H*", $guid . $descriptor
2053     }
2054    
2055     # decode a registry hive into the BCD structure used by pbcdedit
2056     sub bcd_decode {
2057     my ($hive) = @_;
2058    
2059     my %bcd;
2060    
2061     my $objects = $hive->[1][1]{Objects}[1];
2062    
2063     while (my ($k, $v) = each %$objects) {
2064     my %kv;
2065     $v = $v->[1];
2066    
2067     $k = $bcd_objects{$k} // $k;
2068    
2069     my $type = $v->{Description}[0]{Type}[1];
2070    
2071     if ($type != $bcd_object_types{$k}) {
2072     $type = $bcd_types{$type} // sprintf "0x%08x", $type;
2073     $kv{type} = $type;
2074     }
2075    
2076     my $elems = $v->{Elements}[1];
2077    
2078     while (my ($k, $v) = each %$elems) {
2079     my $k = hex $k;
2080    
2081     my $v = $bcde_dec{$k & BCDE_FORMAT}->($v->[0]{Element}[1]);
2082     my $k = dec_bcde_id $k;
2083    
2084     $kv{$k} = $v;
2085     }
2086    
2087     $bcd{$k} = \%kv;
2088     }
2089    
2090     $bcd{meta} = { version => $JSON_VERSION };
2091    
2092     \%bcd
2093     }
2094    
2095     # encode a pbcdedit structure into a registry hive
2096     sub bcd_encode {
2097     my ($bcd) = @_;
2098    
2099     if (my $meta = $bcd->{meta}) {
2100     $meta->{version} eq $JSON_VERSION
2101     or die "BCD meta version ($meta->{version}) does not match executable version ($JSON_VERSION)\n";
2102     }
2103    
2104     my %objects;
2105     my %rbcd_types = reverse %bcd_types;
2106    
2107     while (my ($k, $v) = each %$bcd) {
2108     my %kv;
2109    
2110     next if $k eq "meta";
2111    
2112     $k = lc $k; # I know you windows types!
2113    
2114     my $type = $v->{type};
2115    
2116     if ($type) {
2117     $type = $type =~ /^(?:0x)[0-9a-fA-F]+$/
2118     ? hex $type
2119     : $rbcd_types{$type} // die "$type: unable to parse bcd object type\n";
2120     }
2121    
2122     my $guid = enc_wguid $k
2123     or die "$k: invalid bcd object identifier\n";
2124    
2125     # default type if not given
2126     $type //= $bcd_object_types{dec_wguid $guid} // die "$k: unable to deduce bcd object type\n";
2127    
2128     my %elem;
2129    
2130     while (my ($k, $v) = each %$v) {
2131     next if $k eq "type";
2132    
2133     $k = (enc_bcde_id $k) // die "$k: invalid bcde element name or id\n";
2134     $elem{sprintf "%08x", $k} = [{
2135     Element => [ ($bcde_enc{$k & BCDE_FORMAT} // die "$k: unable to encode unknown bcd element type}")->($v)]
2136     }];
2137     }
2138    
2139     $guid = dec_guid $guid;
2140    
2141     $objects{"{$guid}"} = [undef, {
2142     Description => [{ Type => [dword => $type] }],
2143     Elements => [undef, \%elem],
2144     }];
2145     }
2146    
2147     [NewStoreRoot => [undef, {
2148     Description => [{
2149     KeyName => [sz => "BCD00000001"],
2150     System => [dword => 1],
2151     pbcdedit => [sz => $VERSION],
2152     # other values seen: GuidCache => ..., TreatAsSystem => 0x00000001
2153     }],
2154     Objects => [undef, \%objects],
2155     }]]
2156     }
2157    
2158     #############################################################################
2159    
2160     # json to stdout
2161     sub prjson($) {
2162     print $json_coder->encode ($_[0]);
2163     }
2164    
2165     # json from stdin
2166     sub rdjson() {
2167     my $json;
2168     1 while read STDIN, $json, 65536, length $json;
2169     $json_coder->decode ($json)
2170     }
2171    
2172     # all subcommands
2173     our %CMD = (
2174     help => sub {
2175     require Pod::Usage;
2176     Pod::Usage::pod2usage (-verbose => 2);
2177     },
2178    
2179     objects => sub {
2180     my %rbcd_types = reverse %bcd_types;
2181     $_ = sprintf "%08x", $_ for values %rbcd_types;
2182    
2183     if ($_[0] eq "--json") {
2184     my %default_type = %bcd_object_types;
2185     $_ = sprintf "%08x", $_ for values %default_type;
2186    
2187     prjson {
2188     version => $JSON_VERSION,
2189     object_alias => \%bcd_objects,
2190     object_type => \%rbcd_types,
2191     object_default_type => \%default_type,
2192     };
2193     } else {
2194     my %rbcd_objects = reverse %bcd_objects;
2195    
2196     print "\n";
2197    
2198     printf "%-9s %s\n", "Type", "Alias";
2199     for my $tname (sort keys %rbcd_types) {
2200     printf "%-9s %s\n", $rbcd_types{$tname}, $tname;
2201     }
2202    
2203     print "\n";
2204    
2205     printf "%-39s %-23s %s\n", "Object GUID", "Alias", "(Hex) Default Type";
2206     for my $name (sort keys %rbcd_objects) {
2207     my $guid = $rbcd_objects{$name};
2208     my $type = $bcd_object_types{$name};
2209     my $tname = $bcd_types{$type};
2210    
2211     $type = $type ? sprintf "(%08x) %s", $type, $tname : "-";
2212    
2213     printf "%-39s %-23s %s\n", $guid, $name, $type;
2214     }
2215    
2216     print "\n";
2217     }
2218     },
2219    
2220     elements => sub {
2221     my $json = $_[0] eq "--json";
2222    
2223     my %format_name = (
2224     BCDE_FORMAT_DEVICE , "device",
2225     BCDE_FORMAT_STRING , "string",
2226     BCDE_FORMAT_GUID , "guid",
2227     BCDE_FORMAT_GUID_LIST , "guid list",
2228     BCDE_FORMAT_INTEGER , "integer",
2229     BCDE_FORMAT_BOOLEAN , "boolean",
2230     BCDE_FORMAT_INTEGER_LIST, "integer list",
2231     );
2232     my %rbcde = reverse %bcde;
2233     $_ = sprintf "%08x", $_ for values %rbcde;
2234    
2235     my %element;
2236    
2237     unless ($json) {
2238     print "\n";
2239     printf "%-9s %-12s %s\n", "Element", "Format", "Name Alias";
2240     }
2241     for my $name (sort keys %rbcde) {
2242     my $id = $rbcde{$name};
2243     my $format = $format_name{(hex $id) & BCDE_FORMAT};
2244    
2245     if ($json) {
2246     $element{$id} = [$format, $name];
2247     } else {
2248     printf "%-9s %-12s %s\n", $id, $format, $name;
2249     }
2250     }
2251     print "\n" unless $json;
2252    
2253     prjson {
2254     version => $JSON_VERSION,
2255     element => \%element,
2256     } if $json;
2257    
2258     },
2259    
2260     export => sub {
2261     prjson bcd_decode regf_load shift;
2262     },
2263    
2264     import => sub {
2265     regf_save shift, bcd_encode rdjson;
2266     },
2267    
2268     "export-regf" => sub {
2269     prjson regf_load shift;
2270    
2271     },
2272    
2273     "import-regf" => sub {
2274     regf_save shift, rdjson;
2275     },
2276    
2277     lsblk => sub {
2278     printf "%-10s %-8.8s %-6.6s %-3s %s\n", "DEVICE", "LABEL", "FSTYPE", "PT", "DEVICE DESCRIPTOR";
2279    
2280     my $lsblk = $json_coder->decode (scalar qx<lsblk --json -o PATH,KNAME,TYPE,PTTYPE,PTUUID,PARTUUID,LABEL,FSTYPE>);
2281    
2282     for my $dev (@{ $lsblk->{blockdevices} }) {
2283     my $pr = sub {
2284     printf "%-10s %-8.8s %-6.6s %-3s %s\n",
2285     $dev->{path}, $dev->{label}, $dev->{fstype}, $dev->{pttype}, $_[0];
2286     };
2287    
2288     if ($dev->{type} eq "part") {
2289     if ($dev->{pttype} eq "gpt") {
2290     $pr->("partition=<null>,harddisk,gpt,$dev->{ptuuid},$dev->{partuuid}");
2291     } elsif ($dev->{pttype} eq "dos") { # why not "mbr" :(
2292     if ($dev->{partuuid} =~ /^([0-9a-f]{8})-([0-9a-f]{2})\z/i) {
2293     my ($diskid, $partno) = ($1, hex $2);
2294     $pr->("legacypartition=<null>,harddisk,mbr,$diskid,$partno");
2295     if (open my $fh, "/sys/class/block/$dev->{kname}/start") {
2296     my $start = 512 * readline $fh;
2297     $pr->("partition=<null>,harddisk,mbr,$diskid,$start");
2298     }
2299     }
2300     }
2301     }
2302     }
2303     },
2304     );
2305    
2306     my $cmd = shift;
2307    
2308     unless (exists $CMD{$cmd}) {
2309     warn "Usage: $0 subcommand args...\nTry $0 help\n";
2310     exit 126;
2311     }
2312    
2313     $CMD{$cmd}->(@ARGV);
2314