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