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