ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/pbcdedit/pbcdedit
Revision: 1.42
Committed: Fri Aug 16 23:59:40 2019 UTC (4 years, 9 months ago) by root
Branch: MAIN
Changes since 1.41: +4 -4 lines
Log Message:
*** empty log message ***

File Contents

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