ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/pbcdedit/pbcdedit
Revision: 1.17
Committed: Wed Aug 14 23:29:41 2019 UTC (4 years, 9 months ago) by root
Branch: MAIN
Changes since 1.16: +6 -6 lines
Log Message:
*** empty log message ***

File Contents

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