ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/pbcdedit/pbcdedit
Revision: 1.49
Committed: Thu Aug 22 07:42:01 2019 UTC (4 years, 9 months ago) by root
Branch: MAIN
Changes since 1.48: +1 -1 lines
Log Message:
*** empty log message ***

File Contents

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