ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/pbcdedit/pbcdedit
Revision: 1.58
Committed: Thu Aug 22 09:09:12 2019 UTC (4 years, 8 months ago) by root
Branch: MAIN
Changes since 1.57: +5 -4 lines
Log Message:
*** empty log message ***

File Contents

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