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