ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/pbcdedit/pbcdedit
Revision: 1.39
Committed: Thu Aug 15 23:49:22 2019 UTC (4 years, 9 months ago) by root
Branch: MAIN
Changes since 1.38: +2 -1 lines
Log Message:
*** empty log message ***

File Contents

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