ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/pbcdedit/pbcdedit
Revision: 1.3
Committed: Wed Aug 14 21:27:54 2019 UTC (4 years, 9 months ago) by root
Branch: MAIN
Changes since 1.2: +25 -0 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     #todo
624    
625     =item locate=<block=vhd,<block=file,<partition=<null>,harddisk,mbr,47cbc08a,242643632128>,\win10.vhdx>>,element,path
626    
627     #todo
628    
629     =item {b097d2b2-bc00-11e9-8a9a-525400123456}block<1>=ramdisk,<partition=<null>,harddisk,mbr,47cbc08a,242643632128>,0,0,0,\boot.wim
630    
631     #todo
632    
633    
634     =head1 SEE ALSO
635    
636     For ideas on what you can do, and some introductory material, try
637     L<http://www.mistyprojects.co.uk/documents/BCDEdit/index.html>.
638    
639     For good reference on BCD objects and elements, see Geoff Chappels pages
640     at L<http://www.geoffchappell.com/notes/windows/boot/bcd/index.htm>.
641    
642     =head1 AUTHOR
643    
644     Written by Marc A. Lehmann <pbcdedit@schmorp.de>.
645    
646     =head1 REPORTING BUGS
647    
648     Bugs can be reported dorectly tt he author at L<pcbedit@schmorp.de>.
649    
650     =head1 BUGS AND SHORTCOMINGS
651    
652     This should be a module. Of a series of modules, even.
653    
654     Registry code should preserve classname and security descriptor data, and
655     whatever else is necessary to read and write any registry hive file.
656    
657     I am also not happy with device descriptors being strings rather than a
658     data structure, but strings are probably better for command line usage. In
659     any case,. device descriptors could be converted by simply "splitting" at
660     "=" and "," into an array reference, recursively.
661    
662     =head1 HOMEPAGE
663    
664     Original versions of this program can be found at
665     L<http://software.schmorp.de/pkg/pbcdedit>.
666    
667     =head1 COPYRIGHT
668    
669     Copyright 2019 Marc A. Lehmann, licensed under GNU GPL version 3 or later,
670     see L<https://gnu.org/licenses/gpl.html>. This is free software: you are
671     free to change and redistribute it. There is NO WARRANTY, to the extent
672     permitted by law.
673    
674     =cut
675    
676     BEGIN { require "common/sense.pm"; common::sense->import } # common sense is optional, but recommended
677    
678     use Data::Dump;
679     use Encode ();
680     use List::Util ();
681     use IO::Handle ();
682     use Time::HiRes ();
683    
684     eval { unpack "Q", pack "Q", 1 }
685     or die "perl with 64 bit integer supported required.\n";
686    
687     our $JSON = eval { require JSON::XS; JSON::XS:: }
688     // eval { require JSON::PP; JSON::PP:: }
689     // die "either JSON::XS or JSON::PP must be installed\n";
690    
691     our $json_coder = $JSON->new->utf8->pretty->canonical->relaxed;
692    
693     # hack used for debugging
694     sub xxd($$) {
695     open my $xxd, "| xxd | sed -e 's/^/\Q$_[0]\E: /'";
696     syswrite $xxd, $_[1];
697     }
698    
699     # sources and resources used for this:
700     # registry:
701     # https://github.com/msuhanov/regf/blob/master/Windows%20registry%20file%20format%20specification.md
702     # http://amnesia.gtisc.gatech.edu/~moyix/suzibandit.ltd.uk/MSc/
703     # bcd:
704     # http://www.geoffchappell.com/notes/windows/boot/bcd/index.htm
705     # https://docs.microsoft.com/en-us/previous-versions/windows/hardware/design/dn653287(v=vs.85)
706     # bcd devices:
707     # reactos' boot/environ/include/bl.h
708     # windows .mof files
709    
710     #############################################################################
711     # registry stuff
712    
713     # we use a hardcoded securitya descriptor - full access for everyone
714     my $sid = pack "H*", "010100000000000100000000"; # S-1-1-0 everyone
715     my $ace = pack "C C S< L< a*", 0, 2, 8 + (length $sid), 0x000f003f, $sid; # type flags size mask sid
716     my $sacl = "";
717     my $dacl = pack "C x S< S< x2 a*", 2, 8 + (length $ace), 1, $ace; # rev size count ace*
718     my $sd = pack "C x S< L< L< L< L< a* a* a* a*",
719     # rev flags(SE_DACL_PRESENT SE_SELF_RELATIVE) owner group sacl dacl
720     1, 0x8004,
721     20 + (length $sacl) + (length $dacl),
722     20 + (length $sacl) + (length $dacl) + (length $sid),
723     0, 20,
724     $sacl, $dacl, $sid, $sid;
725     my $sk = pack "a2 x2 x4 x4 x4 L< a*", sk => (length $sd), $sd;
726    
727     sub NO_OFS() { 0xffffffff } # file pointer "NULL" value
728    
729     sub KEY_HIVE_ENTRY() { 0x0004 }
730     sub KEY_NO_DELETE () { 0x0008 }
731     sub KEY_COMP_NAME () { 0x0020 }
732    
733     sub VALUE_COMP_NAME() { 0x0001 }
734    
735     my @regf_typename = qw(
736     none sz expand_sz binary dword dword_be link multi_sz
737     resource_list full_resource_descriptor resource_requirements_list
738     qword qword_be
739     );
740    
741     my %regf_dec_type = (
742     sz => sub { $_[0] =~ s/\x00\x00$//; Encode::decode "UTF-16LE", $_[0] },
743     expand_sz => sub { $_[0] =~ s/\x00\x00$//; Encode::decode "UTF-16LE", $_[0] },
744     link => sub { $_[0] =~ s/\x00\x00$//; Encode::decode "UTF-16LE", $_[0] },
745     multi_sz => sub { $_[0] =~ s/(?:\x00\x00)?\x00\x00$//; [ split /\x00/, (Encode::decode "UTF-16LE", $_[0]), -1 ] },
746     dword => sub { unpack "L<", shift },
747     dword_be => sub { unpack "L>", shift },
748     qword => sub { unpack "Q<", shift },
749     qword_be => sub { unpack "Q>", shift },
750     );
751    
752     my %regf_enc_type = (
753     sz => sub { (Encode::encode "UTF-16LE", $_[0]) . "\x00\x00" },
754     expand_sz => sub { (Encode::encode "UTF-16LE", $_[0]) . "\x00\x00" },
755     link => sub { (Encode::encode "UTF-16LE", $_[0]) . "\x00\x00" },
756     multi_sz => sub { (join "", map +(Encode::encode "UTF-16LE", $_) . "\x00\x00", @{ $_[0] }) . "\x00\x00" },
757     dword => sub { pack "L<", shift },
758     dword_be => sub { pack "L>", shift },
759     qword => sub { pack "Q<", shift },
760     qword_be => sub { pack "Q>", shift },
761     );
762    
763     # decode a registry hive
764     sub regf_decode($) {
765     my ($hive) = @_;
766    
767     "regf" eq substr $hive, 0, 4
768     or die "not a registry hive\n";
769    
770     my ($major, $minor) = unpack "\@20 L< L<", $hive;
771    
772     $major == 1
773     or die "registry major version is not 1, but $major\n";
774    
775     $minor >= 2 && $minor <= 6
776     or die "registry minor version is $minor, only 2 .. 6 are supported\n";
777    
778     my $bins = substr $hive, 4096;
779    
780     my $decode_key = sub {
781     my ($ofs) = @_;
782    
783     my @res;
784    
785     my ($sze, $sig) = unpack "\@$ofs l< a2", $bins;
786    
787     $sze < 0
788     or die "key node points to unallocated cell\n";
789    
790     $sig eq "nk"
791     or die "expected key node at $ofs, got '$sig'\n";
792    
793     my ($flags, $snum, $sofs, $vnum, $vofs, $knamesze) = unpack "\@$ofs ( \@6 S< \@24 L< x4 L< x4 L< L< \@76 S< )", $bins;
794    
795     my $kname = unpack "\@$ofs x80 a$knamesze", $bins;
796    
797     # classnames, security descriptors
798     #my ($cofs, $xofs, $clen) = unpack "\@$ofs ( \@44 L< L< \@72 S< )", $bins;
799     #if ($cofs != NO_OFS && $clen) {
800     # #warn "cofs $cofs+$clen\n";
801     # xxd substr $bins, $cofs, 16;
802     #}
803    
804     $kname = Encode::decode "UTF-16LE", $kname
805     unless $flags & KEY_COMP_NAME;
806    
807     if ($vnum && $vofs != NO_OFS) {
808     for ($vofs += 4; $vnum--; $vofs += 4) {
809     my $kofs = unpack "\@$vofs L<", $bins;
810    
811     my ($sze, $sig) = unpack "\@$kofs l< a2", $bins;
812    
813     $sig eq "vk"
814     or die "key values list contains invalid node (expected vk got '$sig')\n";
815    
816     my ($nsze, $dsze, $dofs, $type, $flags) = unpack "\@$kofs x4 x2 S< L< L< L< L<", $bins;
817    
818     my $name = substr $bins, $kofs + 24, $nsze;
819    
820     $name = Encode::decode "UTF-16LE", $name
821     unless $flags & VALUE_COMP_NAME;
822    
823     my $data;
824     if ($dsze & 0x80000000) {
825     $data = substr $bins, $kofs + 12, $dsze & 0x7;
826     } elsif ($dsze > 16344 && $minor > 3) { # big data
827     my ($bsze, $bsig, $bnum, $bofs) = unpack "\@$dofs l< a2 S< L<", $bins;
828    
829     for ($bofs += 4; $bnum--; $bofs += 4) {
830     my $dofs = unpack "\@$bofs L<", $bins;
831     my $dsze = unpack "\@$dofs l<", $bins;
832     $data .= substr $bins, $dofs + 4, -$dsze - 4;
833     }
834     $data = substr $data, 0, $dsze; # cells might be longer than data
835     } else {
836     $data = substr $bins, $dofs + 4, $dsze;
837     }
838    
839     $type = $regf_typename[$type] if $type < @regf_typename;
840    
841     $data = ($regf_dec_type{$type} || sub { unpack "H*", shift })
842     ->($data);
843    
844     $res[0]{$name} = [$type, $data];
845     }
846     }
847    
848     if ($sofs != NO_OFS) {
849     my $decode_key = __SUB__;
850    
851     my $decode_subkeylist = sub {
852     my ($sofs) = @_;
853    
854     my ($sze, $sig, $snum) = unpack "\@$sofs l< a2 S<", $bins;
855    
856     if ($sig eq "ri") { # index root
857     for (my $lofs = $sofs + 8; $snum--; $lofs += 4) {
858     __SUB__->(unpack "\@$lofs L<", $bins);
859     }
860     } else {
861     my $inc;
862    
863     if ($sig eq "li") { # subkey list
864     $inc = 4;
865     } elsif ($sig eq "lf" or $sig eq "lh") { # subkey list with name hints or hashes
866     $inc = 8;
867     } else {
868     die "expected subkey list at $sofs, found '$sig'\n";
869     }
870    
871     for (my $lofs = $sofs + 8; $snum--; $lofs += $inc) {
872     my ($name, $data) = $decode_key->(unpack "\@$lofs L<", $bins);
873     $res[1]{$name} = $data;
874     }
875     }
876     };
877    
878     $decode_subkeylist->($sofs);
879     }
880    
881     ($kname, \@res);
882     };
883    
884     my ($rootcell) = unpack "\@36 L<", $hive;
885    
886     my ($rname, $root) = $decode_key->($rootcell);
887    
888     [$rname, $root]
889     }
890    
891     # return a binary windows fILETIME struct
892     sub filetime_now {
893     my ($s, $ms) = Time::HiRes::gettimeofday;
894    
895     pack "Q<", $s = ($s * 1_000_000 + $ms) * 10 + 116_444_736_000_000_000
896     }
897    
898     # encode a registry hive
899     sub regf_encode($) {
900     my ($hive) = @_;
901    
902     my %typeval = map +($regf_typename[$_] => $_), 0 .. $#regf_typename;
903    
904     # the filetime is apparently used to verify log file validity,
905     # so by generating a new timestamp the log files *should* automatically
906     # become invalidated and windows would "self-heal" them.
907     # (update: has been verified by reverse engineering)
908     # possibly the fact that the two sequence numbes match might also
909     # make windows think that the hive is not dirty and ignore logs.
910     # (update: has been verified by reverse engineering)
911    
912     my $now = filetime_now;
913    
914     # we only create a single hbin
915     my $bins = pack "a4 L< L< x8 a8 x4", "hbin", 0, 0, $now;
916    
917     # append cell to $bind, return offset
918     my $cell = sub {
919     my ($cell) = @_;
920    
921     my $res = length $bins;
922    
923     $cell .= "\x00" while 4 != (7 & length $cell); # slow and ugly
924    
925     $bins .= pack "l<", -(4 + length $cell);
926     $bins .= $cell;
927    
928     $res
929     };
930    
931     my $sdofs = $cell->($sk); # add a dummy security descriptor
932     my $sdref = 0; # refcount
933     substr $bins, $sdofs + 8, 4, pack "L<", $sdofs; # flink
934     substr $bins, $sdofs + 12, 4, pack "L<", $sdofs; # blink
935    
936     my $encode_key = sub {
937     my ($kname, $kdata, $flags) = @_;
938     my ($values, $subkeys) = @$kdata;
939    
940     if ($kname =~ /[^\x00-\xff]/) {
941     $kname = Encode::encode "UTF-16LE", $kname;
942     } else {
943     $flags |= KEY_COMP_NAME;
944     }
945    
946     # encode subkeys
947    
948     my @snames =
949     map $_->[1],
950     sort { $a->[0] cmp $b->[0] }
951     map [(uc $_), $_],
952     keys %$subkeys;
953    
954     # normally, we'd have to encode each name, but we assume one char is at most two utf-16 cp's
955     my $maxsname = 4 * List::Util::max map length, @snames;
956    
957     my @sofs = map __SUB__->($_, $subkeys->{$_}, 0), @snames;
958    
959     # encode values
960     my $maxvname = 4 * List::Util::max map length, keys %$values;
961     my @vofs;
962     my $maxdsze = 0;
963    
964     while (my ($vname, $v) = each %$values) {
965     my $flags = 0;
966    
967     if ($vname =~ /[^\x00-\xff]/) {
968     $vname = Encode::encode "UTF-16LE", $kname;
969     } else {
970     $flags |= VALUE_COMP_NAME;
971     }
972    
973     my ($type, $data) = @$v;
974    
975     $data = ($regf_enc_type{$type} || sub { pack "H*", shift })->($data);
976    
977     my $dsze;
978     my $dofs;
979    
980     if (length $data <= 4) {
981     $dsze = 0x80000000 | length $data;
982     $dofs = unpack "L<", pack "a4", $data;
983     } else {
984     $dsze = length $data;
985     $dofs = $cell->($data);
986     }
987    
988     $type = $typeval{$type} // ($type =~ /^[0-9]+\z/ ? $type : die "cannot encode type '$type'");
989    
990     push @vofs, $cell->(pack "a2 S< L< L< L< S< x2 a*",
991     vk => (length $vname), $dsze, $dofs, $type, $flags, $vname);
992    
993     $maxdsze = $dsze if $maxdsze < $dsze;
994     }
995    
996     # encode key
997    
998     my $slist = @sofs ? $cell->(pack "a2 S< L<*", li => (scalar @sofs), @sofs) : NO_OFS;
999     my $vlist = @vofs ? $cell->(pack "L<*", @vofs) : NO_OFS;
1000    
1001     my $kdata = pack "
1002     a2 S< a8 x4 x4
1003     L< L< L< L< L< L<
1004     L< L< L< L< L< L<
1005     x4 S< S< a*
1006     ",
1007     nk => $flags, $now,
1008     (scalar @sofs), 0, $slist, NO_OFS, (scalar @vofs), $vlist,
1009     $sdofs, NO_OFS, $maxsname, 0, $maxvname, $maxdsze,
1010     length $kname, 0, $kname;
1011     ++$sdref;
1012    
1013     my $res = $cell->($kdata);
1014    
1015     substr $bins, $_ + 16, 4, pack "L<", $res
1016     for @sofs;
1017    
1018     $res
1019     };
1020    
1021     my ($rname, $root) = @$hive;
1022    
1023     my $rofs = $encode_key->($rname, $root, KEY_HIVE_ENTRY | KEY_NO_DELETE); # 4 = root key
1024    
1025     if (my $pad = -(length $bins) & 4095) {
1026     $pad -= 4;
1027     $bins .= pack "l< x$pad", $pad + 4;
1028     }
1029    
1030     substr $bins, $sdofs + 16, 4, pack "L<", $sdref; # sd refcount
1031     substr $bins, 8, 4, pack "L<", length $bins;
1032    
1033     my $base = pack "
1034     a4 L< L< a8 L< L< L< L<
1035     L< L< L<
1036     a64
1037     x396
1038     ",
1039     regf => 1974, 1974, $now, 1, 3, 0, 1,
1040     $rofs, length $bins, 1,
1041     (Encode::encode "UTF-16LE", "\\pbcdedit.reg");
1042    
1043     my $chksum = List::Util::reduce { $a ^ $b } unpack "L<*", $base;
1044     $chksum = 0xfffffffe if $chksum == 0xffffffff;
1045     $chksum = 1 if $chksum == 0;
1046    
1047     $base .= pack "L<", $chksum;
1048    
1049     $base = pack "a* \@4095 x1", $base;
1050    
1051     $base . $bins
1052     }
1053    
1054     # load and parse registry from file
1055     sub regf_load($) {
1056     my ($path) = @_;
1057     open my $regf, "<:raw", $path
1058     or die "$path: $!\n";
1059     my $size = -s $regf;
1060     $size = read $regf, my $buf, $size
1061     or die "$path: short read\n";
1062    
1063     regf_decode $buf
1064     }
1065    
1066     # encode and save registry to file
1067     sub regf_save {
1068     my ($path, $hive) = @_;
1069    
1070     $hive = regf_encode $hive;
1071    
1072     open my $regf, ">:raw", "$path~"
1073     or die "$path~: $!\n";
1074     print $regf $hive
1075     or die "$path~: short write\n";
1076     $regf->sync;
1077     close $regf;
1078    
1079     rename "$path~", $path;
1080     }
1081    
1082     #############################################################################
1083     # bcd stuff
1084    
1085     # human-readable alises for GUID object identifiers
1086     our %bcd_objects = (
1087     '{0ce4991b-e6b3-4b16-b23c-5e0d9250e5d9}' => '{emssettings}',
1088     '{1afa9c49-16ab-4a5c-4a90-212802da9460}' => '{resumeloadersettings}',
1089     '{1cae1eb7-a0df-4d4d-9851-4860e34ef535}' => '{default}',
1090     '{313e8eed-7098-4586-a9bf-309c61f8d449}' => '{kerneldbgsettings}',
1091     '{4636856e-540f-4170-a130-a84776f4c654}' => '{dbgsettings}',
1092     '{466f5a88-0af2-4f76-9038-095b170dc21c}' => '{ntldr}',
1093     '{5189b25c-5558-4bf2-bca4-289b11bd29e2}' => '{badmemory}',
1094     '{6efb52bf-1766-41db-a6b3-0ee5eff72bd7}' => '{bootloadersettings}',
1095     '{7254a080-1510-4e85-ac0f-e7fb3d444736}' => '{ssetupefi}',
1096     '{7ea2e1ac-2e61-4728-aaa3-896d9d0a9f0e}' => '{globalsettings}',
1097     '{7ff607e0-4395-11db-b0de-0800200c9a66}' => '{hypervisorsettings}',
1098     '{9dea862c-5cdd-4e70-acc1-f32b344d4795}' => '{bootmgr}',
1099     '{a1943bbc-ea85-487c-97c7-c9ede908a38a}' => '{ostargettemplatepcat}',
1100     '{a5a30fa2-3d06-4e9f-b5f4-a01df9d1fcba}' => '{fwbootmgr}',
1101     '{ae5534e0-a924-466c-b836-758539a3ee3a}' => '{ramdiskoptions}',
1102     '{b012b84d-c47c-4ed5-b722-c0c42163e569}' => '{ostargettemplateefi}',
1103     '{b2721d73-1db4-4c62-bf78-c548a880142d}' => '{memdiag}',
1104     '{cbd971bf-b7b8-4885-951a-fa03044f5d71}' => '{setuppcat}',
1105     '{fa926493-6f1c-4193-a414-58f0b2456d1e}' => '{current}',
1106     );
1107    
1108     # default types
1109     our %bcd_object_types = (
1110     '{fwbootmgr}' => 0x10100001,
1111     '{bootmgr}' => 0x10100002,
1112     '{memdiag}' => 0x10200005,
1113     '{ntldr}' => 0x10300006,
1114     '{badmemory}' => 0x20100000,
1115     '{dbgsettings}' => 0x20100000,
1116     '{emssettings}' => 0x20100000,
1117     '{globalsettings}' => 0x20100000,
1118     '{bootloadersettings}' => 0x20200003,
1119     '{hypervisorsettings}' => 0x20200003,
1120     '{kerneldbgsettings}' => 0x20200003,
1121     '{resumeloadersettings}' => 0x20200004,
1122     '{ramdiskoptions}' => 0x30000000,
1123     );
1124    
1125     # object types
1126     our %bcd_types = (
1127     0x10100001 => 'application::fwbootmgr',
1128     0x10100002 => 'application::bootmgr',
1129     0x10200003 => 'application::osloader',
1130     0x10200004 => 'application::resume',
1131     0x10100005 => 'application::memdiag',
1132     0x10100006 => 'application::ntldr',
1133     0x10100007 => 'application::setupldr',
1134     0x10400008 => 'application::bootsector',
1135     0x10400009 => 'application::startup',
1136     0x1020000a => 'application::bootapp',
1137     0x20100000 => 'settings',
1138     0x20200001 => 'inherit::fwbootmgr',
1139     0x20200002 => 'inherit::bootmgr',
1140     0x20200003 => 'inherit::osloader',
1141     0x20200004 => 'inherit::resume',
1142     0x20200005 => 'inherit::memdiag',
1143     0x20200006 => 'inherit::ntldr',
1144     0x20200007 => 'inherit::setupldr',
1145     0x20200008 => 'inherit::bootsector',
1146     0x20200009 => 'inherit::startup',
1147     0x20300000 => 'inherit::device',
1148     0x30000000 => 'device',
1149     );
1150    
1151     our %rbcd_objects = reverse %bcd_objects;
1152    
1153     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;
1154    
1155     sub dec_guid($) {
1156     my ($p1, $p2, $p3, $p4, $p5) = unpack "VvvH4H12", shift;
1157     sprintf "%08x-%04x-%04x-%s-%s", $p1, $p2, $p3, $p4, $p5;
1158     }
1159    
1160     sub enc_guid($) {
1161     $_[0] =~ /^$RE_GUID\z/o
1162     or return;
1163    
1164     pack "VvvH4H12", hex $1, hex $2, hex $3, $4, $5
1165     }
1166    
1167     # "wguid" are guids wrapped in curly braces {...} also supporting aliases
1168     sub dec_wguid($) {
1169     my $guid = "{" . (dec_guid shift) . "}";
1170    
1171     $bcd_objects{$guid} // $guid
1172     }
1173    
1174     sub enc_wguid($) {
1175     my ($guid) = @_;
1176    
1177     if (my $alias = $rbcd_objects{$guid}) {
1178     $guid = $alias;
1179     }
1180    
1181     $guid =~ /^\{($RE_GUID)\}\z/o
1182     or return;
1183    
1184     enc_guid $1
1185     }
1186    
1187     sub BCDE_CLASS () { 0xf0000000 }
1188     sub BCDE_CLASS_LIBRARY () { 0x10000000 }
1189     sub BCDE_CLASS_APPLICATION () { 0x20000000 }
1190     sub BCDE_CLASS_DEVICE () { 0x30000000 }
1191     sub BCDE_CLASS_TEMPLATE () { 0x40000000 }
1192    
1193     sub BCDE_FORMAT () { 0x0f000000 }
1194     sub BCDE_FORMAT_DEVICE () { 0x01000000 }
1195     sub BCDE_FORMAT_STRING () { 0x02000000 }
1196     sub BCDE_FORMAT_GUID () { 0x03000000 }
1197     sub BCDE_FORMAT_GUID_LIST () { 0x04000000 }
1198     sub BCDE_FORMAT_INTEGER () { 0x05000000 }
1199     sub BCDE_FORMAT_BOOLEAN () { 0x06000000 }
1200     sub BCDE_FORMAT_INTEGER_LIST () { 0x07000000 }
1201    
1202     sub dec_device;
1203     sub enc_device;
1204    
1205     sub enc_integer($) {
1206     no warnings 'portable'; # ugh
1207     my $value = shift;
1208     $value = oct $value if $value =~ /^0[bBxX]/;
1209     unpack "H*", pack "Q<", $value
1210     }
1211    
1212     our %bcde_dec = (
1213     BCDE_FORMAT_DEVICE , \&dec_device,
1214     # # for round-trip verification
1215     # BCDE_FORMAT_DEVICE , sub {
1216     # my $dev = dec_device $_[0];
1217     # $_[0] eq enc_device $dev
1218     # or die "bcd device decoding does not round trip for $_[0]\n";
1219     # $dev
1220     # },
1221     BCDE_FORMAT_STRING , sub { shift },
1222     BCDE_FORMAT_GUID , sub { dec_wguid enc_wguid shift },
1223     BCDE_FORMAT_GUID_LIST , sub { join " ", map dec_wguid enc_wguid $_, @{+shift} },
1224     BCDE_FORMAT_INTEGER , sub { unpack "Q", pack "a8", pack "H*", shift }, # integer might be 4 or 8 bytes - caused by ms coding bugs
1225     BCDE_FORMAT_BOOLEAN , sub { shift eq "00" ? 0 : 1 },
1226     BCDE_FORMAT_INTEGER_LIST, sub { join " ", unpack "Q*", pack "H*", shift }, # not sure if this cna be 4 bytes
1227     );
1228    
1229     our %bcde_enc = (
1230     BCDE_FORMAT_DEVICE , sub { binary => enc_device shift },
1231     BCDE_FORMAT_STRING , sub { sz => shift },
1232     BCDE_FORMAT_GUID , sub { sz => "{" . (dec_guid enc_wguid shift) . "}" },
1233     BCDE_FORMAT_GUID_LIST , sub { multi_sz => [map "{" . (dec_guid enc_wguid $_) . "}", split /\s+/, shift ] },
1234     BCDE_FORMAT_INTEGER , sub { binary => enc_integer shift },
1235     BCDE_FORMAT_BOOLEAN , sub { binary => shift ? "01" : "00" },
1236     BCDE_FORMAT_INTEGER_LIST, sub { binary => join "", map enc_integer $_, split /\s+/, shift },
1237     );
1238    
1239     # BCD Elements
1240     our %bcde = (
1241     0x11000001 => 'device',
1242     0x12000002 => 'path',
1243     0x12000004 => 'description',
1244     0x12000005 => 'locale',
1245     0x14000006 => 'inherit',
1246     0x15000007 => 'truncatememory',
1247     0x14000008 => 'recoverysequence',
1248     0x16000009 => 'recoveryenabled',
1249     0x1700000a => 'badmemorylist',
1250     0x1600000b => 'badmemoryaccess',
1251     0x1500000c => 'firstmegabytepolicy',
1252     0x1500000d => 'relocatephysical',
1253     0x1500000e => 'avoidlowmemory',
1254     0x1600000f => 'traditionalkseg',
1255     0x16000010 => 'bootdebug',
1256     0x15000011 => 'debugtype',
1257     0x15000012 => 'debugaddress',
1258     0x15000013 => 'debugport',
1259     0x15000014 => 'baudrate',
1260     0x15000015 => 'channel',
1261     0x12000016 => 'targetname',
1262     0x16000017 => 'noumex',
1263     0x15000018 => 'debugstart',
1264     0x12000019 => 'busparams',
1265     0x1500001a => 'hostip',
1266     0x1500001b => 'port',
1267     0x1600001c => 'dhcp',
1268     0x1200001d => 'key',
1269     0x1600001e => 'vm',
1270     0x16000020 => 'bootems',
1271     0x15000022 => 'emsport',
1272     0x15000023 => 'emsbaudrate',
1273     0x12000030 => 'loadoptions',
1274     0x16000040 => 'advancedoptions',
1275     0x16000041 => 'optionsedit',
1276     0x15000042 => 'keyringaddress',
1277     0x11000043 => 'bootstatdevice',
1278     0x12000044 => 'bootstatfilepath',
1279     0x16000045 => 'preservebootstat',
1280     0x16000046 => 'graphicsmodedisabled',
1281     0x15000047 => 'configaccesspolicy',
1282     0x16000048 => 'nointegritychecks',
1283     0x16000049 => 'testsigning',
1284     0x1200004a => 'fontpath',
1285     0x1500004b => 'integrityservices',
1286     0x1500004c => 'volumebandid',
1287     0x16000050 => 'extendedinput',
1288     0x15000051 => 'initialconsoleinput',
1289     0x15000052 => 'graphicsresolution',
1290     0x16000053 => 'restartonfailure',
1291     0x16000054 => 'highestmode',
1292     0x16000060 => 'isolatedcontext',
1293     0x15000065 => 'displaymessage',
1294     0x15000066 => 'displaymessageoverride',
1295     0x16000068 => 'nobootuxtext',
1296     0x16000069 => 'nobootuxprogress',
1297     0x1600006a => 'nobootuxfade',
1298     0x1600006b => 'bootuxreservepooldebug',
1299     0x1600006c => 'bootuxdisabled',
1300     0x1500006d => 'bootuxfadeframes',
1301     0x1600006e => 'bootuxdumpstats',
1302     0x1600006f => 'bootuxshowstats',
1303     0x16000071 => 'multibootsystem',
1304     0x16000072 => 'nokeyboard',
1305     0x15000073 => 'aliaswindowskey',
1306     0x16000074 => 'bootshutdowndisabled',
1307     0x15000075 => 'performancefrequency',
1308     0x15000076 => 'securebootrawpolicy',
1309     0x17000077 => 'allowedinmemorysettings',
1310     0x15000079 => 'bootuxtransitiontime',
1311     0x1600007a => 'mobilegraphics',
1312     0x1600007b => 'forcefipscrypto',
1313     0x1500007d => 'booterrorux',
1314     0x1600007e => 'flightsigning',
1315     0x1500007f => 'measuredbootlogformat',
1316     0x15000080 => 'displayrotation',
1317     0x15000081 => 'logcontrol',
1318     0x16000082 => 'nofirmwaresync',
1319     0x11000084 => 'windowssyspart',
1320     0x16000087 => 'numlock',
1321     0x22000001 => 'bpbstring',
1322     0x24000001 => 'displayorder',
1323     0x21000001 => 'filedevice',
1324     0x21000001 => 'osdevice',
1325     0x25000001 => 'passcount',
1326     0x26000001 => 'pxesoftreboot',
1327     0x22000002 => 'applicationname',
1328     0x24000002 => 'bootsequence',
1329     0x22000002 => 'filepath',
1330     0x22000002 => 'systemroot',
1331     0x25000002 => 'testmix',
1332     0x26000003 => 'cacheenable',
1333     0x26000003 => 'customsettings',
1334     0x23000003 => 'default',
1335     0x25000003 => 'failurecount',
1336     0x23000003 => 'resumeobject',
1337     0x26000004 => 'failuresenabled',
1338     0x26000004 => 'pae',
1339     0x26000004 => 'stampdisks',
1340     0x25000004 => 'testtofail',
1341     0x25000004 => 'timeout',
1342     0x21000005 => 'associatedosdevice',
1343     0x26000005 => 'cacheenable',
1344     0x26000005 => 'resume',
1345     0x25000005 => 'stridefailcount',
1346     0x26000006 => 'debugoptionenabled',
1347     0x25000006 => 'invcfailcount',
1348     0x23000006 => 'resumeobject',
1349     0x25000007 => 'bootux',
1350     0x25000007 => 'matsfailcount',
1351     0x24000007 => 'startupsequence',
1352     0x25000008 => 'bootmenupolicy',
1353     0x25000008 => 'randfailcount',
1354     0x25000009 => 'chckrfailcount',
1355     0x26000010 => 'detecthal',
1356     0x24000010 => 'toolsdisplayorder',
1357     0x22000011 => 'kernel',
1358     0x22000012 => 'hal',
1359     0x22000013 => 'dbgtransport',
1360     0x26000020 => 'displaybootmenu',
1361     0x25000020 => 'nx',
1362     0x26000021 => 'noerrordisplay',
1363     0x25000021 => 'pae',
1364     0x21000022 => 'bcddevice',
1365     0x26000022 => 'winpe',
1366     0x22000023 => 'bcdfilepath',
1367     0x26000024 => 'hormenabled',
1368     0x26000024 => 'hormenabled',
1369     0x26000024 => 'nocrashautoreboot',
1370     0x26000025 => 'hiberboot',
1371     0x26000025 => 'lastknowngood',
1372     0x26000026 => 'oslnointegritychecks',
1373     0x22000026 => 'passwordoverride',
1374     0x26000027 => 'osltestsigning',
1375     0x22000027 => 'pinpassphraseoverride',
1376     0x26000028 => 'processcustomactionsfirst',
1377     0x27000030 => 'customactions',
1378     0x26000030 => 'nolowmem',
1379     0x26000031 => 'persistbootsequence',
1380     0x25000031 => 'removememory',
1381     0x25000032 => 'increaseuserva',
1382     0x26000032 => 'skipstartupsequence',
1383     0x25000033 => 'perfmem',
1384     0x22000040 => 'fverecoveryurl',
1385     0x26000040 => 'vga',
1386     0x22000041 => 'fverecoverymessage',
1387     0x26000041 => 'quietboot',
1388     0x26000042 => 'novesa',
1389     0x26000043 => 'novga',
1390     0x25000050 => 'clustermodeaddressing',
1391     0x26000051 => 'usephysicaldestination',
1392     0x25000052 => 'restrictapiccluster',
1393     0x22000053 => 'evstore',
1394     0x26000054 => 'uselegacyapicmode',
1395     0x26000060 => 'onecpu',
1396     0x25000061 => 'numproc',
1397     0x26000062 => 'maxproc',
1398     0x25000063 => 'configflags',
1399     0x26000064 => 'maxgroup',
1400     0x26000065 => 'groupaware',
1401     0x25000066 => 'groupsize',
1402     0x26000070 => 'usefirmwarepcisettings',
1403     0x25000071 => 'msi',
1404     0x25000072 => 'pciexpress',
1405     0x25000080 => 'safeboot',
1406     0x26000081 => 'safebootalternateshell',
1407     0x26000090 => 'bootlog',
1408     0x26000091 => 'sos',
1409     0x260000a0 => 'debug',
1410     0x260000a1 => 'halbreakpoint',
1411     0x260000a2 => 'useplatformclock',
1412     0x260000a3 => 'forcelegacyplatform',
1413     0x260000a4 => 'useplatformtick',
1414     0x260000a5 => 'disabledynamictick',
1415     0x250000a6 => 'tscsyncpolicy',
1416     0x260000b0 => 'ems',
1417     0x250000c0 => 'forcefailure',
1418     0x250000c1 => 'driverloadfailurepolicy',
1419     0x250000c2 => 'bootmenupolicy',
1420     0x260000c3 => 'onetimeadvancedoptions',
1421     0x260000c4 => 'onetimeoptionsedit',
1422     0x250000e0 => 'bootstatuspolicy',
1423     0x260000e1 => 'disableelamdrivers',
1424     0x250000f0 => 'hypervisorlaunchtype',
1425     0x220000f1 => 'hypervisorpath',
1426     0x260000f2 => 'hypervisordebug',
1427     0x250000f3 => 'hypervisordebugtype',
1428     0x250000f4 => 'hypervisordebugport',
1429     0x250000f5 => 'hypervisorbaudrate',
1430     0x250000f6 => 'hypervisorchannel',
1431     0x250000f7 => 'bootux',
1432     0x260000f8 => 'hypervisordisableslat',
1433     0x220000f9 => 'hypervisorbusparams',
1434     0x250000fa => 'hypervisornumproc',
1435     0x250000fb => 'hypervisorrootprocpernode',
1436     0x260000fc => 'hypervisoruselargevtlb',
1437     0x250000fd => 'hypervisorhostip',
1438     0x250000fe => 'hypervisorhostport',
1439     0x250000ff => 'hypervisordebugpages',
1440     0x25000100 => 'tpmbootentropy',
1441     0x22000110 => 'hypervisorusekey',
1442     0x22000112 => 'hypervisorproductskutype',
1443     0x25000113 => 'hypervisorrootproc',
1444     0x26000114 => 'hypervisordhcp',
1445     0x25000115 => 'hypervisoriommupolicy',
1446     0x26000116 => 'hypervisorusevapic',
1447     0x22000117 => 'hypervisorloadoptions',
1448     0x25000118 => 'hypervisormsrfilterpolicy',
1449     0x25000119 => 'hypervisormmionxpolicy',
1450     0x2500011a => 'hypervisorschedulertype',
1451     0x25000120 => 'xsavepolicy',
1452     0x25000121 => 'xsaveaddfeature0',
1453     0x25000122 => 'xsaveaddfeature1',
1454     0x25000123 => 'xsaveaddfeature2',
1455     0x25000124 => 'xsaveaddfeature3',
1456     0x25000125 => 'xsaveaddfeature4',
1457     0x25000126 => 'xsaveaddfeature5',
1458     0x25000127 => 'xsaveaddfeature6',
1459     0x25000128 => 'xsaveaddfeature7',
1460     0x25000129 => 'xsaveremovefeature',
1461     0x2500012a => 'xsaveprocessorsmask',
1462     0x2500012b => 'xsavedisable',
1463     0x2500012c => 'kerneldebugtype',
1464     0x2200012d => 'kernelbusparams',
1465     0x2500012e => 'kerneldebugaddress',
1466     0x2500012f => 'kerneldebugport',
1467     0x25000130 => 'claimedtpmcounter',
1468     0x25000131 => 'kernelchannel',
1469     0x22000132 => 'kerneltargetname',
1470     0x25000133 => 'kernelhostip',
1471     0x25000134 => 'kernelport',
1472     0x26000135 => 'kerneldhcp',
1473     0x22000136 => 'kernelkey',
1474     0x22000137 => 'imchivename',
1475     0x21000138 => 'imcdevice',
1476     0x25000139 => 'kernelbaudrate',
1477     0x22000140 => 'mfgmode',
1478     0x26000141 => 'event',
1479     0x25000142 => 'vsmlaunchtype',
1480     0x25000144 => 'hypervisorenforcedcodeintegrity',
1481     0x26000145 => 'enablebootdebugpolicy',
1482     0x26000146 => 'enablebootorderclean',
1483     0x26000147 => 'enabledeviceid',
1484     0x26000148 => 'enableffuloader',
1485     0x26000149 => 'enableiuloader',
1486     0x2600014a => 'enablemassstorage',
1487     0x2600014b => 'enablerpmbprovisioning',
1488     0x2600014c => 'enablesecurebootpolicy',
1489     0x2600014d => 'enablestartcharge',
1490     0x2600014e => 'enableresettpm',
1491     0x21000150 => 'systemdatadevice',
1492     0x21000151 => 'osarcdevice',
1493     0x21000153 => 'osdatadevice',
1494     0x21000154 => 'bspdevice',
1495     0x21000155 => 'bspfilepath',
1496     0x26000202 => 'skipffumode',
1497     0x26000203 => 'forceffumode',
1498     0x25000510 => 'chargethreshold',
1499     0x26000512 => 'offmodecharging',
1500     0x25000aaa => 'bootflow',
1501     0x35000001 => 'ramdiskimageoffset',
1502     0x35000002 => 'ramdisktftpclientport',
1503     0x31000003 => 'ramdisksdidevice',
1504     0x32000004 => 'ramdisksdipath',
1505     0x35000005 => 'ramdiskimagelength',
1506     0x36000006 => 'exportascd',
1507     0x35000007 => 'ramdisktftpblocksize',
1508     0x35000008 => 'ramdisktftpwindowsize',
1509     0x36000009 => 'ramdiskmcenabled',
1510     0x3600000a => 'ramdiskmctftpfallback',
1511     0x3600000b => 'ramdisktftpvarwindow',
1512     0x45000001 => 'devicetype',
1513     0x42000002 => 'applicationrelativepath',
1514     0x42000003 => 'ramdiskdevicerelativepath',
1515     0x46000004 => 'omitosloaderelements',
1516     0x47000006 => 'elementstomigrate',
1517     0x46000010 => 'recoveryos',
1518     );
1519    
1520     our %rbcde = reverse %bcde;
1521    
1522     sub dec_bcde_id($) {
1523     $bcde{$_[0]} // sprintf "custom:%08x", $_[0]
1524     }
1525    
1526     sub enc_bcde_id($) {
1527     $_[0] =~ /^custom:([0-9a-fA-F]{8}$)/
1528     ? hex $1
1529     : $rbcde{$_[0]}
1530     }
1531    
1532     # decode/encode bcd device element - the horror, no documentaion
1533     # whatsoever, supercomplex, superinconsistent.
1534    
1535     our @dev_type = qw(block type1 legacypartition serial udp boot partition vmbus locate);
1536     our @block_type = qw(harddisk floppy cdrom ramdisk type4 file vhd);
1537     our @part_type = qw(gpt mbr raw);
1538    
1539     our $NULL_DEVICE = "\x00" x 16;
1540    
1541     # biggest bitch to decode, ever
1542     # this decoded a device portion after the GUID
1543     sub dec_device_($);
1544     sub dec_device_($) {
1545     my ($device) = @_;
1546    
1547     my $res;
1548    
1549     my ($type, $flags, $length, $pad) = unpack "VVVV", substr $device, 0, 4 * 4, "";
1550    
1551     $pad == 0
1552     or die "non-zero reserved field in device descriptor\n";
1553    
1554     if ($length == 0 && $type == 0 && $flags == 0) {
1555     return ("null", $device);
1556     }
1557    
1558     $length >= 16
1559     or die "device element size too small ($length)\n";
1560    
1561     $type = $dev_type[$type] // die "$type: unknown device type\n";
1562     #d# warn "t<$type,$flags,$length,$pad>\n";#d#
1563    
1564     $res .= $type;
1565     $res .= sprintf "<%x>", $flags if $flags;
1566    
1567     my $tail = substr $device, $length - 4 * 4, 1e9, "";
1568    
1569     $length == 4 * 4 + length $device
1570     or die "device length mismatch ($length != " . (16 + length $device) . ")\n";
1571    
1572     my $dec_path = sub {
1573     my ($path, $error) = @_;
1574    
1575     $path =~ /^((?:..)*)\x00\x00\z/s
1576     or die "$error\n";
1577    
1578     $path = Encode::decode "UTF-16LE", $1;
1579    
1580     $path
1581     };
1582    
1583     if ($type eq "partition" or $type eq "legacypartition") {
1584     my $partdata = substr $device, 0, 16, "";
1585     my ($blocktype, $parttype) = unpack "VV", substr $device, 0, 4 * 2, "";
1586    
1587     $blocktype = $block_type[$blocktype] // die "unknown block device type '$blocktype'\n";
1588     $parttype = $part_type[$parttype] // die "unknown partition type\n";
1589    
1590     my $diskid = substr $device, 0, 16, "";
1591    
1592     $diskid = $parttype eq "gpt"
1593     ? dec_guid substr $diskid, 0, 16
1594     : sprintf "%08x", unpack "V", $diskid;
1595    
1596     my $partid = $parttype eq "gpt" ? dec_guid $partdata
1597     : $type eq "partition" ? unpack "Q<", $partdata # byte offset to partition start
1598     : unpack "L<", $partdata; # partition number, one-based
1599    
1600     (my $parent, $device) = dec_device_ $device;
1601    
1602     $res .= "=";
1603     $res .= "<$parent>";
1604     $res .= ",$blocktype,$parttype,$diskid,$partid";
1605    
1606     # PartitionType (gpt, mbr, raw)
1607     # guid | partsig | disknumber
1608    
1609     } elsif ($type eq "boot") {
1610     $device =~ s/^\x00{56}\z//
1611     or die "boot device type with extra data not supported\n";
1612    
1613     } elsif ($type eq "block") {
1614     my $blocktype = unpack "V", substr $device, 0, 4, "";
1615    
1616     $blocktype = $block_type[$blocktype] // die "unknown block device type '$blocktype'\n";
1617    
1618     # decode a "file path" structure
1619     my $dec_file = sub {
1620     my ($fver, $flen, $ftype) = unpack "VVV", substr $device, 0, 4 * 3, "";
1621    
1622     my $path = substr $device, 0, $flen - 12, "";
1623    
1624     $fver == 1
1625     or die "unsupported file descriptor version '$fver'\n";
1626    
1627     $ftype == 5
1628     or die "unsupported file descriptor path type '$type'\n";
1629    
1630     (my $parent, $path) = dec_device_ $path;
1631    
1632     $path = $dec_path->($path, "file device without path");
1633    
1634     ($parent, $path)
1635     };
1636    
1637     if ($blocktype eq "file") {
1638     my ($parent, $path) = $dec_file->();
1639    
1640     $res .= "=file,<$parent>,$path";
1641    
1642     } elsif ($blocktype eq "vhd") {
1643     $device =~ s/^\x00{20}//s
1644     or die "virtualdisk has non-zero fields I don't understand\n";
1645    
1646     (my $parent, $device) = dec_device_ $device;
1647    
1648     $res .= "=vhd,<$parent>";
1649    
1650     } elsif ($blocktype eq "ramdisk") {
1651     my ($base, $size, $offset) = unpack "Q< Q< L<", substr $device, 0, 8 + 8 + 4, "";
1652     my ($subdev, $path) = $dec_file->();
1653    
1654     $res .= "=ramdisk,<$subdev>,$base,$size,$offset,$path";
1655    
1656     } else {
1657     die "unsupported block type '$blocktype'\n";
1658     }
1659    
1660     } elsif ($type eq "locate") {
1661     # mode, bcde_id, unknown, string
1662     # we assume locate has _either_ an element id _or_ a path, but not both
1663    
1664     my ($mode, $elem, $parent) = unpack "VVV", substr $device, 0, 4 * 3, "";
1665    
1666     if ($parent) {
1667     # not sure why this is an offset - it must come after the path
1668     $parent = substr $device, $parent - 4 * 3 - 4 * 4, 1e9, "";
1669     ($parent, my $tail) = dec_device_ $parent;
1670     0 == length $tail
1671     or die "trailing data after locate device parent\n";
1672     } else {
1673     $parent = "null";
1674     }
1675    
1676     my $path = $device; $device = "";
1677     $path = $dec_path->($path, "device locate mode without path");
1678    
1679     $res .= "=<$parent>,";
1680    
1681     if ($mode == 0) { # "Element"
1682     !length $path
1683     or die "device locate mode 0 having non-empty path ($mode, $elem, $path)\n";
1684    
1685     $elem = dec_bcde_id $elem;
1686     $res .= "element,$elem";
1687    
1688     } elsif ($mode == 1) { # "String"
1689     !$elem
1690     or die "device locate mode 1 having non-zero element\n";
1691    
1692     $res .= "path,$path";
1693     } else {
1694     # mode 2 maybe called "ElementChild" with element and parent device? example needed
1695     die "device locate mode '$mode' not supported\n";
1696     }
1697    
1698     } elsif ($type eq "vmbus") {
1699     my $type = dec_guid substr $device, 0, 16, "";
1700     my $instance = dec_guid substr $device, 0, 16, "";
1701    
1702     $device =~ s/^\x00{24}\z//
1703     or die "vmbus has non-zero fields I don't understand\n";
1704    
1705     $res .= "=$type,$instance";
1706    
1707     } else {
1708     die "unsupported device type '$type'\n";
1709     }
1710    
1711     warn "unexpected trailing device data($res), " . unpack "H*",$device
1712     if length $device;
1713     #length $device
1714     # and die "unexpected trailing device data\n";
1715    
1716     ($res, $tail)
1717     }
1718    
1719     # decode a full binary BCD device descriptor
1720     sub dec_device($) {
1721     my ($device) = @_;
1722    
1723     $device = pack "H*", $device;
1724    
1725     my $guid = dec_guid substr $device, 0, 16, "";
1726     $guid = $guid eq "00000000-0000-0000-0000-000000000000"
1727     ? "" : "{$guid}";
1728    
1729     eval {
1730     my ($dev, $tail) = dec_device_ $device;
1731    
1732     $tail eq ""
1733     or die "unsupported trailing data after device descriptor\n";
1734    
1735     "$guid$dev"
1736     # } // scalar ((warn $@), "$guid$fallback")
1737     } // ($guid . "binary=" . unpack "H*", $device)
1738     }
1739    
1740     sub indexof($@) {
1741     my $value = shift;
1742    
1743     for (0 .. $#_) {
1744     $value eq $_[$_]
1745     and return $_;
1746     }
1747    
1748     undef
1749     }
1750    
1751     # encode the device portion after the GUID
1752     sub enc_device_;
1753     sub enc_device_ {
1754     my ($device) = @_;
1755    
1756     my $enc_path = sub {
1757     my $path = shift;
1758     $path =~ s/\//\\/g;
1759     (Encode::encode "UTF-16LE", $path) . "\x00\x00"
1760     };
1761    
1762     my $enc_file = sub {
1763     my ($parent, $path) = @_; # parent and path must already be encoded
1764    
1765     $path = $parent . $path;
1766    
1767     # fver 1, ftype 5
1768     pack "VVVa*", 1, 12 + length $path, 5, $path
1769     };
1770    
1771     my $parse_path = sub {
1772     s/^([\/\\][^<>"|?*\x00-\x1f]*)//
1773     or die "$_: invalid path\n";
1774    
1775     $enc_path->($1)
1776     };
1777    
1778     my $parse_parent = sub {
1779     my $parent;
1780    
1781     if (s/^<//) {
1782     ($parent, $_) = enc_device_ $_;
1783     s/^>//
1784     or die "$device: syntax error: parent device not followed by '>'\n";
1785     } else {
1786     $parent = $NULL_DEVICE;
1787     }
1788    
1789     $parent
1790     };
1791    
1792     for ($device) {
1793     s/^([a-z]+)//
1794     or die "$_: device does not start with type string\n";
1795    
1796     my $type = $1;
1797     my $flags = s/^<([0-9a-fA-F]+)>// ? hex $1 : 0;
1798     my $payload;
1799    
1800     if ($type eq "binary") {
1801     s/^=([0-9a-fA-F]+)//
1802     or die "binary type must have a hex string argument\n";
1803    
1804     $payload = pack "H*", $1;
1805    
1806     } elsif ($type eq "null") {
1807     return ($NULL_DEVICE, $_);
1808    
1809     } elsif ($type eq "boot") {
1810     $payload = "\x00" x 56;
1811    
1812     } elsif ($type eq "partition" or $type eq "legacypartition") {
1813     s/^=//
1814     or die "$_: missing '=' after $type\n";
1815    
1816     my $parent = $parse_parent->();
1817    
1818     s/^,//
1819     or die "$_: comma missing after partition parent device\n";
1820    
1821     s/^([a-z]+),//
1822     or die "$_: partition does not start with block type (e.g. hd or vhd)\n";
1823     my $blocktype = $1;
1824    
1825     s/^([a-z]+),//
1826     or die "$_: partition block type not followed by partiton type\n";
1827     my $parttype = $1;
1828    
1829     my ($partdata, $diskdata);
1830    
1831     if ($parttype eq "mbr") {
1832     s/^([0-9a-f]{8}),//i
1833     or die "$_: partition mbr disk id malformed (must be e.g. 1234abcd)\n";
1834     $diskdata = pack "Vx12", hex $1;
1835    
1836     s/^([0-9]+)//
1837     or die "$_: partition number or offset is missing or malformed (must be decimal)\n";
1838    
1839     # the following works for both 64 bit offset and 32 bit partno
1840     $partdata = pack "Q< x8", $1;
1841    
1842     } elsif ($parttype eq "gpt") {
1843     s/^($RE_GUID),//
1844     or die "$_: partition disk guid missing or malformed\n";
1845     $diskdata = enc_guid $1;
1846    
1847     s/^($RE_GUID)//
1848     or die "$_: partition guid missing or malformed\n";
1849     $partdata = enc_guid $1;
1850    
1851     } elsif ($parttype eq "raw") {
1852     s/^([0-9]+)//
1853     or die "$_: partition disk number missing or malformed (must be decimal)\n";
1854    
1855     $partdata = pack "L< x12", $1;
1856    
1857     } else {
1858     die "$parttype: partition type not supported\n";
1859     }
1860    
1861     $payload = pack "a16 L< L< a16 a*",
1862     $partdata,
1863     (indexof $blocktype, @block_type),
1864     (indexof $parttype, @part_type),
1865     $diskdata,
1866     $parent;
1867    
1868     } elsif ($type eq "locate") {
1869     s/^=//
1870     or die "$_: missing '=' after $type\n";
1871    
1872     my ($mode, $elem, $path);
1873    
1874     my $parent = $parse_parent->();
1875    
1876     s/^,//
1877     or die "$_: missing comma after locate parent device\n";
1878    
1879     if (s/^element,//) {
1880     s/^([0-9a-z]+)//i
1881     or die "$_ locate element must be either name or 8-digit hex id\n";
1882     $elem = enc_bcde_id $1;
1883     $mode = 0;
1884     $path = $enc_path->("");
1885    
1886     } elsif (s/^path,//) {
1887     $mode = 1;
1888     $path = $parse_path->();
1889    
1890     } else {
1891     die "$_ second locate argument must be subtype (either element or path)\n";
1892     }
1893    
1894     if ($parent ne $NULL_DEVICE) {
1895     ($parent, $path) = (4 * 4 + 4 * 3 + length $path, "$path$parent");
1896     } else {
1897     $parent = 0;
1898     }
1899    
1900     $payload = pack "VVVa*", $mode, $elem, $parent, $path;
1901    
1902     } elsif ($type eq "block") {
1903     s/^=//
1904     or die "$_: missing '=' after $type\n";
1905    
1906     s/^([a-z]+),//
1907     or die "$_: block device does not start with block type (e.g. disk)\n";
1908     my $blocktype = $1;
1909    
1910     my $blockdata;
1911    
1912     if ($blocktype eq "file") {
1913     my $parent = $parse_parent->();
1914     s/^,// or die "$_: comma missing after file block device parent\n";
1915     my $path = $parse_path->();
1916    
1917     $blockdata = $enc_file->($parent, $path);
1918    
1919     } elsif ($blocktype eq "vhd") {
1920     $blockdata = "\x00" x 20; # ENOTUNDERSTOOD
1921     $blockdata .= $parse_parent->();
1922    
1923     } elsif ($blocktype eq "ramdisk") {
1924     my $parent = $parse_parent->();
1925    
1926     s/^,(\d+),(\d+),(\d+),//a
1927     or die "$_: missing ramdisk base,size,offset after ramdisk parent device\n";
1928    
1929     my ($base, $size, $offset) = ($1, $2, $3);
1930    
1931     my $path = $parse_path->();
1932    
1933     $blockdata = pack "Q< Q< L< a*", $base, $size, $offset, $enc_file->($parent, $path);
1934    
1935     } elsif ($blocktype eq "cdrom" or $blocktype eq "floppy") {
1936     # this is guesswork
1937     s/^(\d+)//a
1938     or die "$_: missing device number for cdrom\n";
1939     $blockdata = pack "V", $1;
1940    
1941     } else {
1942     die "$blocktype: unsupported block type (must be file, vhd, ramdisk, floppy, cdrom)\n";
1943     }
1944    
1945     $payload = pack "Va*",
1946     (indexof $blocktype, @block_type),
1947     $blockdata;
1948    
1949     } elsif ($type eq "vmbus") {
1950     s/^=($RE_GUID)//
1951     or die "$_: malformed or missing vmbus interface type guid\n";
1952     my $type = enc_guid $1;
1953     s/^,($RE_GUID)//
1954     or die "$_: malformed or missing vmbus interface instance guid\n";
1955     my $instance = enc_guid $1;
1956    
1957     $payload = pack "a16a16x24", $type, $instance;
1958    
1959     } else {
1960     die "$type: not a supported device type (binary, null, boot, legacypartition, partition, block, locate)\n";
1961     }
1962    
1963     return (
1964     (pack "VVVVa*", (indexof $type, @dev_type), $flags, 16 + length $payload, 0, $payload),
1965     $_
1966     );
1967     }
1968     }
1969    
1970     # encode a full binary BCD device descriptor
1971     sub enc_device {
1972     my ($device) = @_;
1973    
1974     my $guid = "\x00" x 16;
1975    
1976     if ($device =~ s/^\{([A-Za-z0-9\-]+)\}//) {
1977     $guid = enc_guid $1
1978     or die "$device: does not start with valid guid\n";
1979     }
1980    
1981     my ($descriptor, $tail) = enc_device_ $device;
1982    
1983     length $tail
1984     and die "$device: garbage after device descriptor\n";
1985    
1986     unpack "H*", $guid . $descriptor
1987     }
1988    
1989     # decode a registry hive into the BCD structure used by pbcdedit
1990     sub bcd_decode {
1991     my ($hive) = @_;
1992    
1993     my %bcd;
1994    
1995     my $objects = $hive->[1][1]{Objects}[1];
1996    
1997     while (my ($k, $v) = each %$objects) {
1998     my %kv;
1999     $v = $v->[1];
2000    
2001     $k = $bcd_objects{$k} // $k;
2002    
2003     my $type = $v->{Description}[0]{Type}[1];
2004    
2005     if ($type != $bcd_object_types{$k}) {
2006     $type = $bcd_types{$type} // sprintf "0x%08x", $type;
2007     $kv{type} = $type;
2008     }
2009    
2010     my $elems = $v->{Elements}[1];
2011    
2012     while (my ($k, $v) = each %$elems) {
2013     my $k = hex $k;
2014    
2015     my $v = $bcde_dec{$k & BCDE_FORMAT}->($v->[0]{Element}[1]);
2016     my $k = dec_bcde_id $k;
2017    
2018     $kv{$k} = $v;
2019     }
2020    
2021     $bcd{$k} = \%kv;
2022     }
2023    
2024     $bcd{meta} = { version => $JSON_VERSION };
2025    
2026     \%bcd
2027     }
2028    
2029     # encode a pbcdedit structure into a registry hive
2030     sub bcd_encode {
2031     my ($bcd) = @_;
2032    
2033     if (my $meta = $bcd->{meta}) {
2034     $meta->{version} eq $JSON_VERSION
2035     or die "BCD meta version ($meta->{version}) does not match executable version ($JSON_VERSION)\n";
2036     }
2037    
2038     my %objects;
2039     my %rbcd_types = reverse %bcd_types;
2040    
2041     while (my ($k, $v) = each %$bcd) {
2042     my %kv;
2043    
2044     next if $k eq "meta";
2045    
2046     $k = lc $k; # I know you windows types!
2047    
2048     my $type = $v->{type};
2049    
2050     if ($type) {
2051     $type = $type =~ /^(?:0x)[0-9a-fA-F]+$/
2052     ? hex $type
2053     : $rbcd_types{$type} // die "$type: unable to parse bcd object type\n";
2054     }
2055    
2056     my $guid = enc_wguid $k
2057     or die "$k: invalid bcd object identifier\n";
2058    
2059     # default type if not given
2060     $type //= $bcd_object_types{dec_wguid $guid} // die "$k: unable to deduce bcd object type\n";
2061    
2062     my %elem;
2063    
2064     while (my ($k, $v) = each %$v) {
2065     next if $k eq "type";
2066    
2067     $k = (enc_bcde_id $k) // die "$k: invalid bcde element name or id\n";
2068     $elem{sprintf "%08x", $k} = [{
2069     Element => [ ($bcde_enc{$k & BCDE_FORMAT} // die "$k: unable to encode unknown bcd element type}")->($v)]
2070     }];
2071     }
2072    
2073     $guid = dec_guid $guid;
2074    
2075     $objects{"{$guid}"} = [undef, {
2076     Description => [{ Type => [dword => $type] }],
2077     Elements => [undef, \%elem],
2078     }];
2079     }
2080    
2081     [NewStoreRoot => [undef, {
2082     Description => [{
2083     KeyName => [sz => "BCD00000001"],
2084     System => [dword => 1],
2085     pbcdedit => [sz => $VERSION],
2086     # other values seen: GuidCache => ..., TreatAsSystem => 0x00000001
2087     }],
2088     Objects => [undef, \%objects],
2089     }]]
2090     }
2091    
2092     #############################################################################
2093    
2094     # json to stdout
2095     sub prjson($) {
2096     print $json_coder->encode ($_[0]);
2097     }
2098    
2099     # json from stdin
2100     sub rdjson() {
2101     my $json;
2102     1 while read STDIN, $json, 65536, length $json;
2103     $json_coder->decode ($json)
2104     }
2105    
2106     # all subcommands
2107     our %CMD = (
2108     help => sub {
2109     require Pod::Usage;
2110     Pod::Usage::pod2usage (-verbose => 2);
2111     },
2112    
2113     objects => sub {
2114     my %rbcd_types = reverse %bcd_types;
2115     $_ = sprintf "%08x", $_ for values %rbcd_types;
2116    
2117     if ($_[0] eq "--json") {
2118     my %default_type = %bcd_object_types;
2119     $_ = sprintf "%08x", $_ for values %default_type;
2120    
2121     prjson {
2122     version => $JSON_VERSION,
2123     object_alias => \%bcd_objects,
2124     object_type => \%rbcd_types,
2125     object_default_type => \%default_type,
2126     };
2127     } else {
2128     my %rbcd_objects = reverse %bcd_objects;
2129    
2130     print "\n";
2131    
2132     printf "%-9s %s\n", "Type", "Alias";
2133     for my $tname (sort keys %rbcd_types) {
2134     printf "%-9s %s\n", $rbcd_types{$tname}, $tname;
2135     }
2136    
2137     print "\n";
2138    
2139     printf "%-39s %-23s %s\n", "Object GUID", "Alias", "(Hex) Default Type";
2140     for my $name (sort keys %rbcd_objects) {
2141     my $guid = $rbcd_objects{$name};
2142     my $type = $bcd_object_types{$name};
2143     my $tname = $bcd_types{$type};
2144    
2145     $type = $type ? sprintf "(%08x) %s", $type, $tname : "-";
2146    
2147     printf "%-39s %-23s %s\n", $guid, $name, $type;
2148     }
2149    
2150     print "\n";
2151     }
2152     },
2153    
2154     elements => sub {
2155     my $json = $_[0] eq "--json";
2156    
2157     my %format_name = (
2158     BCDE_FORMAT_DEVICE , "device",
2159     BCDE_FORMAT_STRING , "string",
2160     BCDE_FORMAT_GUID , "guid",
2161     BCDE_FORMAT_GUID_LIST , "guid list",
2162     BCDE_FORMAT_INTEGER , "integer",
2163     BCDE_FORMAT_BOOLEAN , "boolean",
2164     BCDE_FORMAT_INTEGER_LIST, "integer list",
2165     );
2166     my %rbcde = reverse %bcde;
2167     $_ = sprintf "%08x", $_ for values %rbcde;
2168    
2169     my %element;
2170    
2171     unless ($json) {
2172     print "\n";
2173     printf "%-9s %-12s %s\n", "Element", "Format", "Name Alias";
2174     }
2175     for my $name (sort keys %rbcde) {
2176     my $id = $rbcde{$name};
2177     my $format = $format_name{(hex $id) & BCDE_FORMAT};
2178    
2179     if ($json) {
2180     $element{$id} = [$format, $name];
2181     } else {
2182     printf "%-9s %-12s %s\n", $id, $format, $name;
2183     }
2184     }
2185     print "\n" unless $json;
2186    
2187     prjson {
2188     version => $JSON_VERSION,
2189     element => \%element,
2190     } if $json;
2191    
2192     },
2193    
2194     export => sub {
2195     prjson bcd_decode regf_load shift;
2196     },
2197    
2198     import => sub {
2199     regf_save shift, bcd_encode rdjson;
2200     },
2201    
2202     "export-regf" => sub {
2203     prjson regf_load shift;
2204    
2205     },
2206    
2207     "import-regf" => sub {
2208     regf_save shift, rdjson;
2209     },
2210    
2211     lsblk => sub {
2212     printf "%-10s %-8.8s %-6.6s %-3s %s\n", "DEVICE", "LABEL", "FSTYPE", "PT", "DEVICE DESCRIPTOR";
2213    
2214     my $lsblk = $json_coder->decode (scalar qx<lsblk --json -o PATH,KNAME,TYPE,PTTYPE,PTUUID,PARTUUID,LABEL,FSTYPE>);
2215    
2216     for my $dev (@{ $lsblk->{blockdevices} }) {
2217     my $pr = sub {
2218     printf "%-10s %-8.8s %-6.6s %-3s %s\n",
2219     $dev->{path}, $dev->{label}, $dev->{fstype}, $dev->{pttype}, $_[0];
2220     };
2221    
2222     if ($dev->{type} eq "part") {
2223     if ($dev->{pttype} eq "gpt") {
2224     $pr->("partition=<null>,harddisk,gpt,$dev->{ptuuid},$dev->{partuuid}");
2225     } elsif ($dev->{pttype} eq "dos") { # why not "mbr" :(
2226     if ($dev->{partuuid} =~ /^([0-9a-f]{8})-([0-9a-f]{2})\z/i) {
2227     my ($diskid, $partno) = ($1, hex $2);
2228     $pr->("legacypartition=<null>,harddisk,mbr,$diskid,$partno");
2229     if (open my $fh, "/sys/class/block/$dev->{kname}/start") {
2230     my $start = 512 * readline $fh;
2231     $pr->("partition=<null>,harddisk,mbr,$diskid,$start");
2232     }
2233     }
2234     }
2235     }
2236     }
2237     },
2238     );
2239    
2240     my $cmd = shift;
2241    
2242     unless (exists $CMD{$cmd}) {
2243     warn "Usage: $0 subcommand args...\nTry $0 help\n";
2244     exit 126;
2245     }
2246    
2247     $CMD{$cmd}->(@ARGV);
2248