ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/pbcdedit/pbcdedit
Revision: 1.36
Committed: Thu Aug 15 08:53:14 2019 UTC (4 years, 9 months ago) by root
Branch: MAIN
CVS Tags: rel-1_1
Changes since 1.35: +7 -8 lines
Log Message:
1.1

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