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