ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/pbcdedit/pbcdedit
Revision: 1.33
Committed: Thu Aug 15 08:39:11 2019 UTC (4 years, 9 months ago) by root
Branch: MAIN
Changes since 1.32: +2 -2 lines
Log Message:
temporary workaround

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.31 This has nothing to do with BCD stores, but simply exposes PCBEDIT's
153 root 1.30 internal registry hive reader - it takes a registry hive file as argument
154     and outputs a JSON representation of it to standard output.
155 root 1.1
156     Hive versions 1.2 till 1.6 are supported.
157    
158 root 1.20 =item C<import-regf> F<path>
159 root 1.1
160     The reverse of C<export-regf>: reads a JSON representation of a registry
161 root 1.20 hive from standard input and creates or replaces the registry hive file
162     given as argument.
163 root 1.1
164     The written hive will always be in a slightly modified version 1.3
165     format. It's not the format windows would generate, but it should be
166     understood by any conformant hive reader.
167    
168     Note that the representation chosen by PBCDEDIT currently throws away
169 root 1.11 classname data (often used for feeble attempts at hiding stuff by
170 root 1.1 Microsoft) and security descriptors, so if you write anything other than
171     a BCD hive you will most likely destroy it.
172    
173     =back
174    
175    
176 root 1.27 =head1 BCD STORE REPRESENTATION FORMAT
177 root 1.1
178     A BCD data store is represented as a JSON object with one special key,
179     C<meta>, and one key per BCD object. That is, each BCD object becomes
180     one key-value pair in the object, and an additional key called C<meta>
181     contains meta information.
182    
183     Here is an abridged example of a real BCD store:
184    
185     {
186     "meta" : {
187     "version" : 1
188     },
189     "{7ae02178-821d-11e7-8813-1c872c5f5ab0}" : {
190     "type" : "application::osloader",
191     "description" : "Windows 10",
192     "device" : "partition=<null>,harddisk,gpt,9742e468-9206-48a0-b4e4-c4e9745a356a,3ce6aceb-e90c-4fd2-9fba-47cab15f6faf",
193     "osdevice" : "partition=<null>,harddisk,gpt,9742e468-9206-48a0-b4e4-c4e9745a356a,3ce6aceb-e90c-4fd2-9fba-47cab15f6faf",
194     "path" : "\\Windows\\system32\\winload.exe",
195     "systemroot" : "\\Windows"
196     },
197     "{bootloadersettings}" : {
198     "inherit" : "{globalsettings} {hypervisorsettings}"
199     },
200     "{bootmgr}" : {
201     "description" : "Windows Boot Manager",
202     "device" : "partition=<null>,harddisk,mbr,ff3ba63b,1048576",
203     "displayorder" : "{7ae02178-821d-11e7-8813-1c872c5f5ab0}",
204     "inherit" : "{globalsettings}",
205     "displaybootmenu" : 0,
206     "timeout" : 30
207     },
208     "{globalsettings}" : {
209     "inherit" : "{dbgsettings} {emssettings} {badmemory}"
210     },
211     "{hypervisorsettings}" : {
212     "hypervisorbaudrate" : 115200,
213     "hypervisordebugport" : 1,
214     "hypervisordebugtype" : 0
215     },
216     # ...
217     }
218    
219 root 1.3 =head2 Minimal BCD to boot windows
220    
221     Experimentally I found the following BCD is the minimum required to
222     successfully boot any post-XP version of Windows (suitable C<device> and
223     C<osdevice> values, of course):
224    
225     {
226     "{bootmgr}" : {
227     "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 root 1.32 # common sense is optional, but recommended
830     #BEGIN { eval { require "common/sensex.pm"; } && common::sensex->import }
831 root 1.33 no warnings;
832     no strict;
833 root 1.1
834     use Encode ();
835     use List::Util ();
836     use IO::Handle ();
837     use Time::HiRes ();
838    
839     eval { unpack "Q", pack "Q", 1 }
840     or die "perl with 64 bit integer supported required.\n";
841    
842     our $JSON = eval { require JSON::XS; JSON::XS:: }
843     // eval { require JSON::PP; JSON::PP:: }
844     // die "either JSON::XS or JSON::PP must be installed\n";
845    
846     our $json_coder = $JSON->new->utf8->pretty->canonical->relaxed;
847    
848     # hack used for debugging
849     sub xxd($$) {
850     open my $xxd, "| xxd | sed -e 's/^/\Q$_[0]\E: /'";
851     syswrite $xxd, $_[1];
852     }
853    
854 root 1.6 sub file_load($) {
855     my ($path) = @_;
856    
857     open my $fh, "<:raw", $path
858     or die "$path: $!\n";
859     my $size = -s $fh;
860     $size = read $fh, my $buf, $size
861     or die "$path: short read\n";
862    
863     $buf
864     }
865    
866 root 1.29 # sources and resources used for writing pbcdedit
867     #
868 root 1.1 # registry:
869     # https://github.com/msuhanov/regf/blob/master/Windows%20registry%20file%20format%20specification.md
870     # http://amnesia.gtisc.gatech.edu/~moyix/suzibandit.ltd.uk/MSc/
871     # bcd:
872     # http://www.geoffchappell.com/notes/windows/boot/bcd/index.htm
873     # https://docs.microsoft.com/en-us/previous-versions/windows/hardware/design/dn653287(v=vs.85)
874     # bcd devices:
875     # reactos' boot/environ/include/bl.h
876     # windows .mof files
877    
878     #############################################################################
879     # registry stuff
880    
881     # we use a hardcoded securitya descriptor - full access for everyone
882     my $sid = pack "H*", "010100000000000100000000"; # S-1-1-0 everyone
883     my $ace = pack "C C S< L< a*", 0, 2, 8 + (length $sid), 0x000f003f, $sid; # type flags size mask sid
884     my $sacl = "";
885     my $dacl = pack "C x S< S< x2 a*", 2, 8 + (length $ace), 1, $ace; # rev size count ace*
886     my $sd = pack "C x S< L< L< L< L< a* a* a* a*",
887     # rev flags(SE_DACL_PRESENT SE_SELF_RELATIVE) owner group sacl dacl
888     1, 0x8004,
889     20 + (length $sacl) + (length $dacl),
890     20 + (length $sacl) + (length $dacl) + (length $sid),
891     0, 20,
892     $sacl, $dacl, $sid, $sid;
893     my $sk = pack "a2 x2 x4 x4 x4 L< a*", sk => (length $sd), $sd;
894    
895     sub NO_OFS() { 0xffffffff } # file pointer "NULL" value
896    
897     sub KEY_HIVE_ENTRY() { 0x0004 }
898     sub KEY_NO_DELETE () { 0x0008 }
899     sub KEY_COMP_NAME () { 0x0020 }
900    
901     sub VALUE_COMP_NAME() { 0x0001 }
902    
903     my @regf_typename = qw(
904     none sz expand_sz binary dword dword_be link multi_sz
905     resource_list full_resource_descriptor resource_requirements_list
906     qword qword_be
907     );
908    
909     my %regf_dec_type = (
910     sz => sub { $_[0] =~ s/\x00\x00$//; Encode::decode "UTF-16LE", $_[0] },
911     expand_sz => sub { $_[0] =~ s/\x00\x00$//; Encode::decode "UTF-16LE", $_[0] },
912     link => sub { $_[0] =~ s/\x00\x00$//; Encode::decode "UTF-16LE", $_[0] },
913     multi_sz => sub { $_[0] =~ s/(?:\x00\x00)?\x00\x00$//; [ split /\x00/, (Encode::decode "UTF-16LE", $_[0]), -1 ] },
914     dword => sub { unpack "L<", shift },
915     dword_be => sub { unpack "L>", shift },
916     qword => sub { unpack "Q<", shift },
917     qword_be => sub { unpack "Q>", shift },
918     );
919    
920     my %regf_enc_type = (
921     sz => sub { (Encode::encode "UTF-16LE", $_[0]) . "\x00\x00" },
922     expand_sz => sub { (Encode::encode "UTF-16LE", $_[0]) . "\x00\x00" },
923     link => sub { (Encode::encode "UTF-16LE", $_[0]) . "\x00\x00" },
924     multi_sz => sub { (join "", map +(Encode::encode "UTF-16LE", $_) . "\x00\x00", @{ $_[0] }) . "\x00\x00" },
925     dword => sub { pack "L<", shift },
926     dword_be => sub { pack "L>", shift },
927     qword => sub { pack "Q<", shift },
928     qword_be => sub { pack "Q>", shift },
929     );
930    
931     # decode a registry hive
932     sub regf_decode($) {
933     my ($hive) = @_;
934    
935     "regf" eq substr $hive, 0, 4
936     or die "not a registry hive\n";
937    
938     my ($major, $minor) = unpack "\@20 L< L<", $hive;
939    
940     $major == 1
941     or die "registry major version is not 1, but $major\n";
942    
943     $minor >= 2 && $minor <= 6
944     or die "registry minor version is $minor, only 2 .. 6 are supported\n";
945    
946     my $bins = substr $hive, 4096;
947    
948     my $decode_key = sub {
949     my ($ofs) = @_;
950    
951     my @res;
952    
953     my ($sze, $sig) = unpack "\@$ofs l< a2", $bins;
954    
955     $sze < 0
956     or die "key node points to unallocated cell\n";
957    
958     $sig eq "nk"
959     or die "expected key node at $ofs, got '$sig'\n";
960    
961     my ($flags, $snum, $sofs, $vnum, $vofs, $knamesze) = unpack "\@$ofs ( \@6 S< \@24 L< x4 L< x4 L< L< \@76 S< )", $bins;
962    
963     my $kname = unpack "\@$ofs x80 a$knamesze", $bins;
964    
965     # classnames, security descriptors
966     #my ($cofs, $xofs, $clen) = unpack "\@$ofs ( \@44 L< L< \@72 S< )", $bins;
967     #if ($cofs != NO_OFS && $clen) {
968     # #warn "cofs $cofs+$clen\n";
969     # xxd substr $bins, $cofs, 16;
970     #}
971    
972     $kname = Encode::decode "UTF-16LE", $kname
973     unless $flags & KEY_COMP_NAME;
974    
975     if ($vnum && $vofs != NO_OFS) {
976     for ($vofs += 4; $vnum--; $vofs += 4) {
977     my $kofs = unpack "\@$vofs L<", $bins;
978    
979     my ($sze, $sig) = unpack "\@$kofs l< a2", $bins;
980    
981     $sig eq "vk"
982     or die "key values list contains invalid node (expected vk got '$sig')\n";
983    
984     my ($nsze, $dsze, $dofs, $type, $flags) = unpack "\@$kofs x4 x2 S< L< L< L< L<", $bins;
985    
986     my $name = substr $bins, $kofs + 24, $nsze;
987    
988     $name = Encode::decode "UTF-16LE", $name
989     unless $flags & VALUE_COMP_NAME;
990    
991     my $data;
992     if ($dsze & 0x80000000) {
993     $data = substr $bins, $kofs + 12, $dsze & 0x7;
994     } elsif ($dsze > 16344 && $minor > 3) { # big data
995     my ($bsze, $bsig, $bnum, $bofs) = unpack "\@$dofs l< a2 S< L<", $bins;
996    
997     for ($bofs += 4; $bnum--; $bofs += 4) {
998     my $dofs = unpack "\@$bofs L<", $bins;
999     my $dsze = unpack "\@$dofs l<", $bins;
1000     $data .= substr $bins, $dofs + 4, -$dsze - 4;
1001     }
1002     $data = substr $data, 0, $dsze; # cells might be longer than data
1003     } else {
1004     $data = substr $bins, $dofs + 4, $dsze;
1005     }
1006    
1007     $type = $regf_typename[$type] if $type < @regf_typename;
1008    
1009     $data = ($regf_dec_type{$type} || sub { unpack "H*", shift })
1010     ->($data);
1011    
1012     $res[0]{$name} = [$type, $data];
1013     }
1014     }
1015    
1016     if ($sofs != NO_OFS) {
1017     my $decode_key = __SUB__;
1018    
1019     my $decode_subkeylist = sub {
1020     my ($sofs) = @_;
1021    
1022     my ($sze, $sig, $snum) = unpack "\@$sofs l< a2 S<", $bins;
1023    
1024     if ($sig eq "ri") { # index root
1025     for (my $lofs = $sofs + 8; $snum--; $lofs += 4) {
1026     __SUB__->(unpack "\@$lofs L<", $bins);
1027     }
1028     } else {
1029     my $inc;
1030    
1031     if ($sig eq "li") { # subkey list
1032     $inc = 4;
1033     } elsif ($sig eq "lf" or $sig eq "lh") { # subkey list with name hints or hashes
1034     $inc = 8;
1035     } else {
1036     die "expected subkey list at $sofs, found '$sig'\n";
1037     }
1038    
1039     for (my $lofs = $sofs + 8; $snum--; $lofs += $inc) {
1040     my ($name, $data) = $decode_key->(unpack "\@$lofs L<", $bins);
1041     $res[1]{$name} = $data;
1042     }
1043     }
1044     };
1045    
1046     $decode_subkeylist->($sofs);
1047     }
1048    
1049     ($kname, \@res);
1050     };
1051    
1052     my ($rootcell) = unpack "\@36 L<", $hive;
1053    
1054     my ($rname, $root) = $decode_key->($rootcell);
1055    
1056     [$rname, $root]
1057     }
1058    
1059     # return a binary windows fILETIME struct
1060     sub filetime_now {
1061     my ($s, $ms) = Time::HiRes::gettimeofday;
1062    
1063     pack "Q<", $s = ($s * 1_000_000 + $ms) * 10 + 116_444_736_000_000_000
1064     }
1065    
1066     # encode a registry hive
1067     sub regf_encode($) {
1068     my ($hive) = @_;
1069    
1070     my %typeval = map +($regf_typename[$_] => $_), 0 .. $#regf_typename;
1071    
1072     # the filetime is apparently used to verify log file validity,
1073     # so by generating a new timestamp the log files *should* automatically
1074     # become invalidated and windows would "self-heal" them.
1075     # (update: has been verified by reverse engineering)
1076     # possibly the fact that the two sequence numbes match might also
1077     # make windows think that the hive is not dirty and ignore logs.
1078     # (update: has been verified by reverse engineering)
1079    
1080     my $now = filetime_now;
1081    
1082     # we only create a single hbin
1083     my $bins = pack "a4 L< L< x8 a8 x4", "hbin", 0, 0, $now;
1084    
1085     # append cell to $bind, return offset
1086     my $cell = sub {
1087     my ($cell) = @_;
1088    
1089     my $res = length $bins;
1090    
1091     $cell .= "\x00" while 4 != (7 & length $cell); # slow and ugly
1092    
1093     $bins .= pack "l<", -(4 + length $cell);
1094     $bins .= $cell;
1095    
1096     $res
1097     };
1098    
1099     my $sdofs = $cell->($sk); # add a dummy security descriptor
1100     my $sdref = 0; # refcount
1101     substr $bins, $sdofs + 8, 4, pack "L<", $sdofs; # flink
1102     substr $bins, $sdofs + 12, 4, pack "L<", $sdofs; # blink
1103    
1104     my $encode_key = sub {
1105     my ($kname, $kdata, $flags) = @_;
1106     my ($values, $subkeys) = @$kdata;
1107    
1108     if ($kname =~ /[^\x00-\xff]/) {
1109     $kname = Encode::encode "UTF-16LE", $kname;
1110     } else {
1111     $flags |= KEY_COMP_NAME;
1112     }
1113    
1114     # encode subkeys
1115    
1116     my @snames =
1117     map $_->[1],
1118     sort { $a->[0] cmp $b->[0] }
1119     map [(uc $_), $_],
1120     keys %$subkeys;
1121    
1122     # normally, we'd have to encode each name, but we assume one char is at most two utf-16 cp's
1123     my $maxsname = 4 * List::Util::max map length, @snames;
1124    
1125     my @sofs = map __SUB__->($_, $subkeys->{$_}, 0), @snames;
1126    
1127     # encode values
1128     my $maxvname = 4 * List::Util::max map length, keys %$values;
1129     my @vofs;
1130     my $maxdsze = 0;
1131    
1132     while (my ($vname, $v) = each %$values) {
1133     my $flags = 0;
1134    
1135     if ($vname =~ /[^\x00-\xff]/) {
1136     $vname = Encode::encode "UTF-16LE", $kname;
1137     } else {
1138     $flags |= VALUE_COMP_NAME;
1139     }
1140    
1141     my ($type, $data) = @$v;
1142    
1143     $data = ($regf_enc_type{$type} || sub { pack "H*", shift })->($data);
1144    
1145     my $dsze;
1146     my $dofs;
1147    
1148     if (length $data <= 4) {
1149     $dsze = 0x80000000 | length $data;
1150     $dofs = unpack "L<", pack "a4", $data;
1151     } else {
1152     $dsze = length $data;
1153     $dofs = $cell->($data);
1154     }
1155    
1156     $type = $typeval{$type} // ($type =~ /^[0-9]+\z/ ? $type : die "cannot encode type '$type'");
1157    
1158     push @vofs, $cell->(pack "a2 S< L< L< L< S< x2 a*",
1159     vk => (length $vname), $dsze, $dofs, $type, $flags, $vname);
1160    
1161     $maxdsze = $dsze if $maxdsze < $dsze;
1162     }
1163    
1164     # encode key
1165    
1166     my $slist = @sofs ? $cell->(pack "a2 S< L<*", li => (scalar @sofs), @sofs) : NO_OFS;
1167     my $vlist = @vofs ? $cell->(pack "L<*", @vofs) : NO_OFS;
1168    
1169     my $kdata = pack "
1170     a2 S< a8 x4 x4
1171     L< L< L< L< L< L<
1172     L< L< L< L< L< L<
1173     x4 S< S< a*
1174     ",
1175     nk => $flags, $now,
1176     (scalar @sofs), 0, $slist, NO_OFS, (scalar @vofs), $vlist,
1177     $sdofs, NO_OFS, $maxsname, 0, $maxvname, $maxdsze,
1178     length $kname, 0, $kname;
1179     ++$sdref;
1180    
1181     my $res = $cell->($kdata);
1182    
1183     substr $bins, $_ + 16, 4, pack "L<", $res
1184     for @sofs;
1185    
1186     $res
1187     };
1188    
1189     my ($rname, $root) = @$hive;
1190    
1191     my $rofs = $encode_key->($rname, $root, KEY_HIVE_ENTRY | KEY_NO_DELETE); # 4 = root key
1192    
1193     if (my $pad = -(length $bins) & 4095) {
1194     $pad -= 4;
1195     $bins .= pack "l< x$pad", $pad + 4;
1196     }
1197    
1198     substr $bins, $sdofs + 16, 4, pack "L<", $sdref; # sd refcount
1199     substr $bins, 8, 4, pack "L<", length $bins;
1200    
1201     my $base = pack "
1202     a4 L< L< a8 L< L< L< L<
1203     L< L< L<
1204     a64
1205     x396
1206     ",
1207     regf => 1974, 1974, $now, 1, 3, 0, 1,
1208     $rofs, length $bins, 1,
1209     (Encode::encode "UTF-16LE", "\\pbcdedit.reg");
1210    
1211     my $chksum = List::Util::reduce { $a ^ $b } unpack "L<*", $base;
1212     $chksum = 0xfffffffe if $chksum == 0xffffffff;
1213     $chksum = 1 if $chksum == 0;
1214    
1215     $base .= pack "L<", $chksum;
1216    
1217     $base = pack "a* \@4095 x1", $base;
1218    
1219     $base . $bins
1220     }
1221    
1222     # load and parse registry from file
1223     sub regf_load($) {
1224     my ($path) = @_;
1225    
1226 root 1.6 regf_decode file_load $path
1227 root 1.1 }
1228    
1229     # encode and save registry to file
1230     sub regf_save {
1231     my ($path, $hive) = @_;
1232    
1233     $hive = regf_encode $hive;
1234    
1235     open my $regf, ">:raw", "$path~"
1236     or die "$path~: $!\n";
1237     print $regf $hive
1238     or die "$path~: short write\n";
1239     $regf->sync;
1240     close $regf;
1241    
1242     rename "$path~", $path;
1243     }
1244    
1245     #############################################################################
1246     # bcd stuff
1247    
1248     # human-readable alises for GUID object identifiers
1249     our %bcd_objects = (
1250     '{0ce4991b-e6b3-4b16-b23c-5e0d9250e5d9}' => '{emssettings}',
1251     '{1afa9c49-16ab-4a5c-4a90-212802da9460}' => '{resumeloadersettings}',
1252     '{1cae1eb7-a0df-4d4d-9851-4860e34ef535}' => '{default}',
1253     '{313e8eed-7098-4586-a9bf-309c61f8d449}' => '{kerneldbgsettings}',
1254     '{4636856e-540f-4170-a130-a84776f4c654}' => '{dbgsettings}',
1255     '{466f5a88-0af2-4f76-9038-095b170dc21c}' => '{ntldr}',
1256     '{5189b25c-5558-4bf2-bca4-289b11bd29e2}' => '{badmemory}',
1257     '{6efb52bf-1766-41db-a6b3-0ee5eff72bd7}' => '{bootloadersettings}',
1258     '{7254a080-1510-4e85-ac0f-e7fb3d444736}' => '{ssetupefi}',
1259     '{7ea2e1ac-2e61-4728-aaa3-896d9d0a9f0e}' => '{globalsettings}',
1260     '{7ff607e0-4395-11db-b0de-0800200c9a66}' => '{hypervisorsettings}',
1261     '{9dea862c-5cdd-4e70-acc1-f32b344d4795}' => '{bootmgr}',
1262     '{a1943bbc-ea85-487c-97c7-c9ede908a38a}' => '{ostargettemplatepcat}',
1263     '{a5a30fa2-3d06-4e9f-b5f4-a01df9d1fcba}' => '{fwbootmgr}',
1264     '{ae5534e0-a924-466c-b836-758539a3ee3a}' => '{ramdiskoptions}',
1265     '{b012b84d-c47c-4ed5-b722-c0c42163e569}' => '{ostargettemplateefi}',
1266     '{b2721d73-1db4-4c62-bf78-c548a880142d}' => '{memdiag}',
1267     '{cbd971bf-b7b8-4885-951a-fa03044f5d71}' => '{setuppcat}',
1268     '{fa926493-6f1c-4193-a414-58f0b2456d1e}' => '{current}',
1269     );
1270    
1271     # default types
1272     our %bcd_object_types = (
1273     '{fwbootmgr}' => 0x10100001,
1274     '{bootmgr}' => 0x10100002,
1275     '{memdiag}' => 0x10200005,
1276     '{ntldr}' => 0x10300006,
1277     '{badmemory}' => 0x20100000,
1278     '{dbgsettings}' => 0x20100000,
1279     '{emssettings}' => 0x20100000,
1280     '{globalsettings}' => 0x20100000,
1281     '{bootloadersettings}' => 0x20200003,
1282     '{hypervisorsettings}' => 0x20200003,
1283     '{kerneldbgsettings}' => 0x20200003,
1284     '{resumeloadersettings}' => 0x20200004,
1285     '{ramdiskoptions}' => 0x30000000,
1286     );
1287    
1288     # object types
1289     our %bcd_types = (
1290     0x10100001 => 'application::fwbootmgr',
1291     0x10100002 => 'application::bootmgr',
1292     0x10200003 => 'application::osloader',
1293     0x10200004 => 'application::resume',
1294     0x10100005 => 'application::memdiag',
1295     0x10100006 => 'application::ntldr',
1296     0x10100007 => 'application::setupldr',
1297     0x10400008 => 'application::bootsector',
1298     0x10400009 => 'application::startup',
1299     0x1020000a => 'application::bootapp',
1300     0x20100000 => 'settings',
1301     0x20200001 => 'inherit::fwbootmgr',
1302     0x20200002 => 'inherit::bootmgr',
1303     0x20200003 => 'inherit::osloader',
1304     0x20200004 => 'inherit::resume',
1305     0x20200005 => 'inherit::memdiag',
1306     0x20200006 => 'inherit::ntldr',
1307     0x20200007 => 'inherit::setupldr',
1308     0x20200008 => 'inherit::bootsector',
1309     0x20200009 => 'inherit::startup',
1310     0x20300000 => 'inherit::device',
1311     0x30000000 => 'device',
1312     );
1313    
1314     our %rbcd_objects = reverse %bcd_objects;
1315    
1316     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;
1317    
1318     sub dec_guid($) {
1319     my ($p1, $p2, $p3, $p4, $p5) = unpack "VvvH4H12", shift;
1320     sprintf "%08x-%04x-%04x-%s-%s", $p1, $p2, $p3, $p4, $p5;
1321     }
1322    
1323     sub enc_guid($) {
1324     $_[0] =~ /^$RE_GUID\z/o
1325     or return;
1326    
1327     pack "VvvH4H12", hex $1, hex $2, hex $3, $4, $5
1328     }
1329    
1330     # "wguid" are guids wrapped in curly braces {...} also supporting aliases
1331     sub dec_wguid($) {
1332     my $guid = "{" . (dec_guid shift) . "}";
1333    
1334     $bcd_objects{$guid} // $guid
1335     }
1336    
1337     sub enc_wguid($) {
1338     my ($guid) = @_;
1339    
1340     if (my $alias = $rbcd_objects{$guid}) {
1341     $guid = $alias;
1342     }
1343    
1344     $guid =~ /^\{($RE_GUID)\}\z/o
1345     or return;
1346    
1347     enc_guid $1
1348     }
1349    
1350     sub BCDE_CLASS () { 0xf0000000 }
1351     sub BCDE_CLASS_LIBRARY () { 0x10000000 }
1352     sub BCDE_CLASS_APPLICATION () { 0x20000000 }
1353     sub BCDE_CLASS_DEVICE () { 0x30000000 }
1354     sub BCDE_CLASS_TEMPLATE () { 0x40000000 }
1355    
1356     sub BCDE_FORMAT () { 0x0f000000 }
1357     sub BCDE_FORMAT_DEVICE () { 0x01000000 }
1358     sub BCDE_FORMAT_STRING () { 0x02000000 }
1359     sub BCDE_FORMAT_GUID () { 0x03000000 }
1360     sub BCDE_FORMAT_GUID_LIST () { 0x04000000 }
1361     sub BCDE_FORMAT_INTEGER () { 0x05000000 }
1362     sub BCDE_FORMAT_BOOLEAN () { 0x06000000 }
1363     sub BCDE_FORMAT_INTEGER_LIST () { 0x07000000 }
1364    
1365     sub dec_device;
1366     sub enc_device;
1367    
1368     sub enc_integer($) {
1369     no warnings 'portable'; # ugh
1370     my $value = shift;
1371     $value = oct $value if $value =~ /^0[bBxX]/;
1372     unpack "H*", pack "Q<", $value
1373     }
1374    
1375     our %bcde_dec = (
1376     BCDE_FORMAT_DEVICE , \&dec_device,
1377     # # for round-trip verification
1378     # BCDE_FORMAT_DEVICE , sub {
1379     # my $dev = dec_device $_[0];
1380     # $_[0] eq enc_device $dev
1381     # or die "bcd device decoding does not round trip for $_[0]\n";
1382     # $dev
1383     # },
1384     BCDE_FORMAT_STRING , sub { shift },
1385     BCDE_FORMAT_GUID , sub { dec_wguid enc_wguid shift },
1386     BCDE_FORMAT_GUID_LIST , sub { join " ", map dec_wguid enc_wguid $_, @{+shift} },
1387     BCDE_FORMAT_INTEGER , sub { unpack "Q", pack "a8", pack "H*", shift }, # integer might be 4 or 8 bytes - caused by ms coding bugs
1388     BCDE_FORMAT_BOOLEAN , sub { shift eq "00" ? 0 : 1 },
1389     BCDE_FORMAT_INTEGER_LIST, sub { join " ", unpack "Q*", pack "H*", shift }, # not sure if this cna be 4 bytes
1390     );
1391    
1392     our %bcde_enc = (
1393     BCDE_FORMAT_DEVICE , sub { binary => enc_device shift },
1394     BCDE_FORMAT_STRING , sub { sz => shift },
1395     BCDE_FORMAT_GUID , sub { sz => "{" . (dec_guid enc_wguid shift) . "}" },
1396     BCDE_FORMAT_GUID_LIST , sub { multi_sz => [map "{" . (dec_guid enc_wguid $_) . "}", split /\s+/, shift ] },
1397     BCDE_FORMAT_INTEGER , sub { binary => enc_integer shift },
1398     BCDE_FORMAT_BOOLEAN , sub { binary => shift ? "01" : "00" },
1399     BCDE_FORMAT_INTEGER_LIST, sub { binary => join "", map enc_integer $_, split /\s+/, shift },
1400     );
1401    
1402     # BCD Elements
1403     our %bcde = (
1404     0x11000001 => 'device',
1405     0x12000002 => 'path',
1406     0x12000004 => 'description',
1407     0x12000005 => 'locale',
1408     0x14000006 => 'inherit',
1409     0x15000007 => 'truncatememory',
1410     0x14000008 => 'recoverysequence',
1411     0x16000009 => 'recoveryenabled',
1412     0x1700000a => 'badmemorylist',
1413     0x1600000b => 'badmemoryaccess',
1414     0x1500000c => 'firstmegabytepolicy',
1415     0x1500000d => 'relocatephysical',
1416     0x1500000e => 'avoidlowmemory',
1417     0x1600000f => 'traditionalkseg',
1418     0x16000010 => 'bootdebug',
1419     0x15000011 => 'debugtype',
1420     0x15000012 => 'debugaddress',
1421     0x15000013 => 'debugport',
1422     0x15000014 => 'baudrate',
1423     0x15000015 => 'channel',
1424     0x12000016 => 'targetname',
1425     0x16000017 => 'noumex',
1426     0x15000018 => 'debugstart',
1427     0x12000019 => 'busparams',
1428     0x1500001a => 'hostip',
1429     0x1500001b => 'port',
1430     0x1600001c => 'dhcp',
1431     0x1200001d => 'key',
1432     0x1600001e => 'vm',
1433     0x16000020 => 'bootems',
1434     0x15000022 => 'emsport',
1435     0x15000023 => 'emsbaudrate',
1436     0x12000030 => 'loadoptions',
1437     0x16000040 => 'advancedoptions',
1438     0x16000041 => 'optionsedit',
1439     0x15000042 => 'keyringaddress',
1440     0x11000043 => 'bootstatdevice',
1441     0x12000044 => 'bootstatfilepath',
1442     0x16000045 => 'preservebootstat',
1443     0x16000046 => 'graphicsmodedisabled',
1444     0x15000047 => 'configaccesspolicy',
1445     0x16000048 => 'nointegritychecks',
1446     0x16000049 => 'testsigning',
1447     0x1200004a => 'fontpath',
1448     0x1500004b => 'integrityservices',
1449     0x1500004c => 'volumebandid',
1450     0x16000050 => 'extendedinput',
1451     0x15000051 => 'initialconsoleinput',
1452     0x15000052 => 'graphicsresolution',
1453     0x16000053 => 'restartonfailure',
1454     0x16000054 => 'highestmode',
1455     0x16000060 => 'isolatedcontext',
1456     0x15000065 => 'displaymessage',
1457     0x15000066 => 'displaymessageoverride',
1458     0x16000068 => 'nobootuxtext',
1459     0x16000069 => 'nobootuxprogress',
1460     0x1600006a => 'nobootuxfade',
1461     0x1600006b => 'bootuxreservepooldebug',
1462     0x1600006c => 'bootuxdisabled',
1463     0x1500006d => 'bootuxfadeframes',
1464     0x1600006e => 'bootuxdumpstats',
1465     0x1600006f => 'bootuxshowstats',
1466     0x16000071 => 'multibootsystem',
1467     0x16000072 => 'nokeyboard',
1468     0x15000073 => 'aliaswindowskey',
1469     0x16000074 => 'bootshutdowndisabled',
1470     0x15000075 => 'performancefrequency',
1471     0x15000076 => 'securebootrawpolicy',
1472     0x17000077 => 'allowedinmemorysettings',
1473     0x15000079 => 'bootuxtransitiontime',
1474     0x1600007a => 'mobilegraphics',
1475     0x1600007b => 'forcefipscrypto',
1476     0x1500007d => 'booterrorux',
1477     0x1600007e => 'flightsigning',
1478     0x1500007f => 'measuredbootlogformat',
1479     0x15000080 => 'displayrotation',
1480     0x15000081 => 'logcontrol',
1481     0x16000082 => 'nofirmwaresync',
1482     0x11000084 => 'windowssyspart',
1483     0x16000087 => 'numlock',
1484     0x22000001 => 'bpbstring',
1485     0x24000001 => 'displayorder',
1486     0x21000001 => 'filedevice',
1487     0x21000001 => 'osdevice',
1488     0x25000001 => 'passcount',
1489     0x26000001 => 'pxesoftreboot',
1490     0x22000002 => 'applicationname',
1491     0x24000002 => 'bootsequence',
1492     0x22000002 => 'filepath',
1493     0x22000002 => 'systemroot',
1494     0x25000002 => 'testmix',
1495     0x26000003 => 'cacheenable',
1496     0x26000003 => 'customsettings',
1497     0x23000003 => 'default',
1498     0x25000003 => 'failurecount',
1499     0x23000003 => 'resumeobject',
1500     0x26000004 => 'failuresenabled',
1501     0x26000004 => 'pae',
1502     0x26000004 => 'stampdisks',
1503     0x25000004 => 'testtofail',
1504     0x25000004 => 'timeout',
1505     0x21000005 => 'associatedosdevice',
1506     0x26000005 => 'cacheenable',
1507     0x26000005 => 'resume',
1508     0x25000005 => 'stridefailcount',
1509     0x26000006 => 'debugoptionenabled',
1510     0x25000006 => 'invcfailcount',
1511     0x23000006 => 'resumeobject',
1512     0x25000007 => 'bootux',
1513     0x25000007 => 'matsfailcount',
1514     0x24000007 => 'startupsequence',
1515     0x25000008 => 'bootmenupolicy',
1516     0x25000008 => 'randfailcount',
1517     0x25000009 => 'chckrfailcount',
1518     0x26000010 => 'detecthal',
1519     0x24000010 => 'toolsdisplayorder',
1520     0x22000011 => 'kernel',
1521     0x22000012 => 'hal',
1522     0x22000013 => 'dbgtransport',
1523     0x26000020 => 'displaybootmenu',
1524     0x25000020 => 'nx',
1525     0x26000021 => 'noerrordisplay',
1526     0x25000021 => 'pae',
1527     0x21000022 => 'bcddevice',
1528     0x26000022 => 'winpe',
1529     0x22000023 => 'bcdfilepath',
1530     0x26000024 => 'hormenabled',
1531     0x26000024 => 'hormenabled',
1532     0x26000024 => 'nocrashautoreboot',
1533     0x26000025 => 'hiberboot',
1534     0x26000025 => 'lastknowngood',
1535     0x26000026 => 'oslnointegritychecks',
1536     0x22000026 => 'passwordoverride',
1537     0x26000027 => 'osltestsigning',
1538     0x22000027 => 'pinpassphraseoverride',
1539     0x26000028 => 'processcustomactionsfirst',
1540     0x27000030 => 'customactions',
1541     0x26000030 => 'nolowmem',
1542     0x26000031 => 'persistbootsequence',
1543     0x25000031 => 'removememory',
1544     0x25000032 => 'increaseuserva',
1545     0x26000032 => 'skipstartupsequence',
1546     0x25000033 => 'perfmem',
1547     0x22000040 => 'fverecoveryurl',
1548     0x26000040 => 'vga',
1549     0x22000041 => 'fverecoverymessage',
1550     0x26000041 => 'quietboot',
1551     0x26000042 => 'novesa',
1552     0x26000043 => 'novga',
1553     0x25000050 => 'clustermodeaddressing',
1554     0x26000051 => 'usephysicaldestination',
1555     0x25000052 => 'restrictapiccluster',
1556     0x22000053 => 'evstore',
1557     0x26000054 => 'uselegacyapicmode',
1558     0x26000060 => 'onecpu',
1559     0x25000061 => 'numproc',
1560     0x26000062 => 'maxproc',
1561     0x25000063 => 'configflags',
1562     0x26000064 => 'maxgroup',
1563     0x26000065 => 'groupaware',
1564     0x25000066 => 'groupsize',
1565     0x26000070 => 'usefirmwarepcisettings',
1566     0x25000071 => 'msi',
1567     0x25000072 => 'pciexpress',
1568     0x25000080 => 'safeboot',
1569     0x26000081 => 'safebootalternateshell',
1570     0x26000090 => 'bootlog',
1571     0x26000091 => 'sos',
1572     0x260000a0 => 'debug',
1573     0x260000a1 => 'halbreakpoint',
1574     0x260000a2 => 'useplatformclock',
1575     0x260000a3 => 'forcelegacyplatform',
1576     0x260000a4 => 'useplatformtick',
1577     0x260000a5 => 'disabledynamictick',
1578     0x250000a6 => 'tscsyncpolicy',
1579     0x260000b0 => 'ems',
1580     0x250000c0 => 'forcefailure',
1581     0x250000c1 => 'driverloadfailurepolicy',
1582     0x250000c2 => 'bootmenupolicy',
1583     0x260000c3 => 'onetimeadvancedoptions',
1584     0x260000c4 => 'onetimeoptionsedit',
1585     0x250000e0 => 'bootstatuspolicy',
1586     0x260000e1 => 'disableelamdrivers',
1587     0x250000f0 => 'hypervisorlaunchtype',
1588     0x220000f1 => 'hypervisorpath',
1589     0x260000f2 => 'hypervisordebug',
1590     0x250000f3 => 'hypervisordebugtype',
1591     0x250000f4 => 'hypervisordebugport',
1592     0x250000f5 => 'hypervisorbaudrate',
1593     0x250000f6 => 'hypervisorchannel',
1594     0x250000f7 => 'bootux',
1595     0x260000f8 => 'hypervisordisableslat',
1596     0x220000f9 => 'hypervisorbusparams',
1597     0x250000fa => 'hypervisornumproc',
1598     0x250000fb => 'hypervisorrootprocpernode',
1599     0x260000fc => 'hypervisoruselargevtlb',
1600     0x250000fd => 'hypervisorhostip',
1601     0x250000fe => 'hypervisorhostport',
1602     0x250000ff => 'hypervisordebugpages',
1603     0x25000100 => 'tpmbootentropy',
1604     0x22000110 => 'hypervisorusekey',
1605     0x22000112 => 'hypervisorproductskutype',
1606     0x25000113 => 'hypervisorrootproc',
1607     0x26000114 => 'hypervisordhcp',
1608     0x25000115 => 'hypervisoriommupolicy',
1609     0x26000116 => 'hypervisorusevapic',
1610     0x22000117 => 'hypervisorloadoptions',
1611     0x25000118 => 'hypervisormsrfilterpolicy',
1612     0x25000119 => 'hypervisormmionxpolicy',
1613     0x2500011a => 'hypervisorschedulertype',
1614     0x25000120 => 'xsavepolicy',
1615     0x25000121 => 'xsaveaddfeature0',
1616     0x25000122 => 'xsaveaddfeature1',
1617     0x25000123 => 'xsaveaddfeature2',
1618     0x25000124 => 'xsaveaddfeature3',
1619     0x25000125 => 'xsaveaddfeature4',
1620     0x25000126 => 'xsaveaddfeature5',
1621     0x25000127 => 'xsaveaddfeature6',
1622     0x25000128 => 'xsaveaddfeature7',
1623     0x25000129 => 'xsaveremovefeature',
1624     0x2500012a => 'xsaveprocessorsmask',
1625     0x2500012b => 'xsavedisable',
1626     0x2500012c => 'kerneldebugtype',
1627     0x2200012d => 'kernelbusparams',
1628     0x2500012e => 'kerneldebugaddress',
1629     0x2500012f => 'kerneldebugport',
1630     0x25000130 => 'claimedtpmcounter',
1631     0x25000131 => 'kernelchannel',
1632     0x22000132 => 'kerneltargetname',
1633     0x25000133 => 'kernelhostip',
1634     0x25000134 => 'kernelport',
1635     0x26000135 => 'kerneldhcp',
1636     0x22000136 => 'kernelkey',
1637     0x22000137 => 'imchivename',
1638     0x21000138 => 'imcdevice',
1639     0x25000139 => 'kernelbaudrate',
1640     0x22000140 => 'mfgmode',
1641     0x26000141 => 'event',
1642     0x25000142 => 'vsmlaunchtype',
1643     0x25000144 => 'hypervisorenforcedcodeintegrity',
1644     0x26000145 => 'enablebootdebugpolicy',
1645     0x26000146 => 'enablebootorderclean',
1646     0x26000147 => 'enabledeviceid',
1647     0x26000148 => 'enableffuloader',
1648     0x26000149 => 'enableiuloader',
1649     0x2600014a => 'enablemassstorage',
1650     0x2600014b => 'enablerpmbprovisioning',
1651     0x2600014c => 'enablesecurebootpolicy',
1652     0x2600014d => 'enablestartcharge',
1653     0x2600014e => 'enableresettpm',
1654     0x21000150 => 'systemdatadevice',
1655     0x21000151 => 'osarcdevice',
1656     0x21000153 => 'osdatadevice',
1657     0x21000154 => 'bspdevice',
1658     0x21000155 => 'bspfilepath',
1659     0x26000202 => 'skipffumode',
1660     0x26000203 => 'forceffumode',
1661     0x25000510 => 'chargethreshold',
1662     0x26000512 => 'offmodecharging',
1663     0x25000aaa => 'bootflow',
1664     0x35000001 => 'ramdiskimageoffset',
1665     0x35000002 => 'ramdisktftpclientport',
1666     0x31000003 => 'ramdisksdidevice',
1667     0x32000004 => 'ramdisksdipath',
1668     0x35000005 => 'ramdiskimagelength',
1669     0x36000006 => 'exportascd',
1670     0x35000007 => 'ramdisktftpblocksize',
1671     0x35000008 => 'ramdisktftpwindowsize',
1672     0x36000009 => 'ramdiskmcenabled',
1673     0x3600000a => 'ramdiskmctftpfallback',
1674     0x3600000b => 'ramdisktftpvarwindow',
1675     0x45000001 => 'devicetype',
1676     0x42000002 => 'applicationrelativepath',
1677     0x42000003 => 'ramdiskdevicerelativepath',
1678     0x46000004 => 'omitosloaderelements',
1679     0x47000006 => 'elementstomigrate',
1680     0x46000010 => 'recoveryos',
1681     );
1682    
1683     our %rbcde = reverse %bcde;
1684    
1685     sub dec_bcde_id($) {
1686     $bcde{$_[0]} // sprintf "custom:%08x", $_[0]
1687     }
1688    
1689     sub enc_bcde_id($) {
1690     $_[0] =~ /^custom:([0-9a-fA-F]{8}$)/
1691     ? hex $1
1692     : $rbcde{$_[0]}
1693     }
1694    
1695     # decode/encode bcd device element - the horror, no documentaion
1696     # whatsoever, supercomplex, superinconsistent.
1697    
1698     our @dev_type = qw(block type1 legacypartition serial udp boot partition vmbus locate);
1699     our @block_type = qw(harddisk floppy cdrom ramdisk type4 file vhd);
1700     our @part_type = qw(gpt mbr raw);
1701    
1702     our $NULL_DEVICE = "\x00" x 16;
1703    
1704     # biggest bitch to decode, ever
1705     # this decoded a device portion after the GUID
1706     sub dec_device_($);
1707     sub dec_device_($) {
1708     my ($device) = @_;
1709    
1710     my $res;
1711    
1712     my ($type, $flags, $length, $pad) = unpack "VVVV", substr $device, 0, 4 * 4, "";
1713    
1714     $pad == 0
1715     or die "non-zero reserved field in device descriptor\n";
1716    
1717     if ($length == 0 && $type == 0 && $flags == 0) {
1718     return ("null", $device);
1719     }
1720    
1721     $length >= 16
1722     or die "device element size too small ($length)\n";
1723    
1724     $type = $dev_type[$type] // die "$type: unknown device type\n";
1725     #d# warn "t<$type,$flags,$length,$pad>\n";#d#
1726    
1727     $res .= $type;
1728     $res .= sprintf "<%x>", $flags if $flags;
1729    
1730     my $tail = substr $device, $length - 4 * 4, 1e9, "";
1731    
1732     $length == 4 * 4 + length $device
1733     or die "device length mismatch ($length != " . (16 + length $device) . ")\n";
1734    
1735     my $dec_path = sub {
1736     my ($path, $error) = @_;
1737    
1738     $path =~ /^((?:..)*)\x00\x00\z/s
1739     or die "$error\n";
1740    
1741     $path = Encode::decode "UTF-16LE", $1;
1742    
1743     $path
1744     };
1745    
1746     if ($type eq "partition" or $type eq "legacypartition") {
1747     my $partdata = substr $device, 0, 16, "";
1748     my ($blocktype, $parttype) = unpack "VV", substr $device, 0, 4 * 2, "";
1749    
1750     $blocktype = $block_type[$blocktype] // die "unknown block device type '$blocktype'\n";
1751     $parttype = $part_type[$parttype] // die "unknown partition type\n";
1752    
1753     my $diskid = substr $device, 0, 16, "";
1754    
1755     $diskid = $parttype eq "gpt"
1756     ? dec_guid substr $diskid, 0, 16
1757     : sprintf "%08x", unpack "V", $diskid;
1758    
1759     my $partid = $parttype eq "gpt" ? dec_guid $partdata
1760     : $type eq "partition" ? unpack "Q<", $partdata # byte offset to partition start
1761     : unpack "L<", $partdata; # partition number, one-based
1762    
1763     (my $parent, $device) = dec_device_ $device;
1764    
1765     $res .= "=";
1766     $res .= "<$parent>";
1767     $res .= ",$blocktype,$parttype,$diskid,$partid";
1768    
1769     # PartitionType (gpt, mbr, raw)
1770     # guid | partsig | disknumber
1771    
1772     } elsif ($type eq "boot") {
1773     $device =~ s/^\x00{56}\z//
1774     or die "boot device type with extra data not supported\n";
1775    
1776     } elsif ($type eq "block") {
1777     my $blocktype = unpack "V", substr $device, 0, 4, "";
1778    
1779     $blocktype = $block_type[$blocktype] // die "unknown block device type '$blocktype'\n";
1780    
1781     # decode a "file path" structure
1782     my $dec_file = sub {
1783     my ($fver, $flen, $ftype) = unpack "VVV", substr $device, 0, 4 * 3, "";
1784    
1785     my $path = substr $device, 0, $flen - 12, "";
1786    
1787     $fver == 1
1788     or die "unsupported file descriptor version '$fver'\n";
1789    
1790     $ftype == 5
1791     or die "unsupported file descriptor path type '$type'\n";
1792    
1793     (my $parent, $path) = dec_device_ $path;
1794    
1795     $path = $dec_path->($path, "file device without path");
1796    
1797     ($parent, $path)
1798     };
1799    
1800     if ($blocktype eq "file") {
1801     my ($parent, $path) = $dec_file->();
1802    
1803     $res .= "=file,<$parent>,$path";
1804    
1805     } elsif ($blocktype eq "vhd") {
1806     $device =~ s/^\x00{20}//s
1807     or die "virtualdisk has non-zero fields I don't understand\n";
1808    
1809     (my $parent, $device) = dec_device_ $device;
1810    
1811     $res .= "=vhd,<$parent>";
1812    
1813     } elsif ($blocktype eq "ramdisk") {
1814     my ($base, $size, $offset) = unpack "Q< Q< L<", substr $device, 0, 8 + 8 + 4, "";
1815     my ($subdev, $path) = $dec_file->();
1816    
1817     $res .= "=ramdisk,<$subdev>,$base,$size,$offset,$path";
1818    
1819     } else {
1820     die "unsupported block type '$blocktype'\n";
1821     }
1822    
1823     } elsif ($type eq "locate") {
1824     # mode, bcde_id, unknown, string
1825     # we assume locate has _either_ an element id _or_ a path, but not both
1826    
1827     my ($mode, $elem, $parent) = unpack "VVV", substr $device, 0, 4 * 3, "";
1828    
1829     if ($parent) {
1830     # not sure why this is an offset - it must come after the path
1831     $parent = substr $device, $parent - 4 * 3 - 4 * 4, 1e9, "";
1832     ($parent, my $tail) = dec_device_ $parent;
1833     0 == length $tail
1834     or die "trailing data after locate device parent\n";
1835     } else {
1836     $parent = "null";
1837     }
1838    
1839     my $path = $device; $device = "";
1840     $path = $dec_path->($path, "device locate mode without path");
1841    
1842     $res .= "=<$parent>,";
1843    
1844     if ($mode == 0) { # "Element"
1845     !length $path
1846     or die "device locate mode 0 having non-empty path ($mode, $elem, $path)\n";
1847    
1848     $elem = dec_bcde_id $elem;
1849     $res .= "element,$elem";
1850    
1851     } elsif ($mode == 1) { # "String"
1852     !$elem
1853     or die "device locate mode 1 having non-zero element\n";
1854    
1855     $res .= "path,$path";
1856     } else {
1857     # mode 2 maybe called "ElementChild" with element and parent device? example needed
1858     die "device locate mode '$mode' not supported\n";
1859     }
1860    
1861     } elsif ($type eq "vmbus") {
1862     my $type = dec_guid substr $device, 0, 16, "";
1863     my $instance = dec_guid substr $device, 0, 16, "";
1864    
1865     $device =~ s/^\x00{24}\z//
1866     or die "vmbus has non-zero fields I don't understand\n";
1867    
1868     $res .= "=$type,$instance";
1869    
1870     } else {
1871     die "unsupported device type '$type'\n";
1872     }
1873    
1874     warn "unexpected trailing device data($res), " . unpack "H*",$device
1875     if length $device;
1876     #length $device
1877     # and die "unexpected trailing device data\n";
1878    
1879     ($res, $tail)
1880     }
1881    
1882     # decode a full binary BCD device descriptor
1883     sub dec_device($) {
1884     my ($device) = @_;
1885    
1886     $device = pack "H*", $device;
1887    
1888     my $guid = dec_guid substr $device, 0, 16, "";
1889     $guid = $guid eq "00000000-0000-0000-0000-000000000000"
1890     ? "" : "{$guid}";
1891    
1892     eval {
1893     my ($dev, $tail) = dec_device_ $device;
1894    
1895     $tail eq ""
1896     or die "unsupported trailing data after device descriptor\n";
1897    
1898     "$guid$dev"
1899     # } // scalar ((warn $@), "$guid$fallback")
1900     } // ($guid . "binary=" . unpack "H*", $device)
1901     }
1902    
1903     sub indexof($@) {
1904     my $value = shift;
1905    
1906     for (0 .. $#_) {
1907     $value eq $_[$_]
1908     and return $_;
1909     }
1910    
1911     undef
1912     }
1913    
1914     # encode the device portion after the GUID
1915     sub enc_device_;
1916     sub enc_device_ {
1917     my ($device) = @_;
1918    
1919     my $enc_path = sub {
1920     my $path = shift;
1921     $path =~ s/\//\\/g;
1922     (Encode::encode "UTF-16LE", $path) . "\x00\x00"
1923     };
1924    
1925     my $enc_file = sub {
1926     my ($parent, $path) = @_; # parent and path must already be encoded
1927    
1928     $path = $parent . $path;
1929    
1930     # fver 1, ftype 5
1931     pack "VVVa*", 1, 12 + length $path, 5, $path
1932     };
1933    
1934     my $parse_path = sub {
1935     s/^([\/\\][^<>"|?*\x00-\x1f]*)//
1936     or die "$_: invalid path\n";
1937    
1938     $enc_path->($1)
1939     };
1940    
1941     my $parse_parent = sub {
1942     my $parent;
1943    
1944     if (s/^<//) {
1945     ($parent, $_) = enc_device_ $_;
1946     s/^>//
1947     or die "$device: syntax error: parent device not followed by '>'\n";
1948     } else {
1949     $parent = $NULL_DEVICE;
1950     }
1951    
1952     $parent
1953     };
1954    
1955     for ($device) {
1956     s/^([a-z]+)//
1957     or die "$_: device does not start with type string\n";
1958    
1959     my $type = $1;
1960     my $flags = s/^<([0-9a-fA-F]+)>// ? hex $1 : 0;
1961     my $payload;
1962    
1963     if ($type eq "binary") {
1964     s/^=([0-9a-fA-F]+)//
1965     or die "binary type must have a hex string argument\n";
1966    
1967     $payload = pack "H*", $1;
1968    
1969     } elsif ($type eq "null") {
1970     return ($NULL_DEVICE, $_);
1971    
1972     } elsif ($type eq "boot") {
1973     $payload = "\x00" x 56;
1974    
1975     } elsif ($type eq "partition" or $type eq "legacypartition") {
1976     s/^=//
1977     or die "$_: missing '=' after $type\n";
1978    
1979     my $parent = $parse_parent->();
1980    
1981     s/^,//
1982     or die "$_: comma missing after partition parent device\n";
1983    
1984     s/^([a-z]+),//
1985     or die "$_: partition does not start with block type (e.g. hd or vhd)\n";
1986     my $blocktype = $1;
1987    
1988     s/^([a-z]+),//
1989     or die "$_: partition block type not followed by partiton type\n";
1990     my $parttype = $1;
1991    
1992     my ($partdata, $diskdata);
1993    
1994     if ($parttype eq "mbr") {
1995     s/^([0-9a-f]{8}),//i
1996     or die "$_: partition mbr disk id malformed (must be e.g. 1234abcd)\n";
1997     $diskdata = pack "Vx12", hex $1;
1998    
1999     s/^([0-9]+)//
2000     or die "$_: partition number or offset is missing or malformed (must be decimal)\n";
2001    
2002     # the following works for both 64 bit offset and 32 bit partno
2003     $partdata = pack "Q< x8", $1;
2004    
2005     } elsif ($parttype eq "gpt") {
2006     s/^($RE_GUID),//
2007     or die "$_: partition disk guid missing or malformed\n";
2008     $diskdata = enc_guid $1;
2009    
2010     s/^($RE_GUID)//
2011     or die "$_: partition guid missing or malformed\n";
2012     $partdata = enc_guid $1;
2013    
2014     } elsif ($parttype eq "raw") {
2015     s/^([0-9]+)//
2016     or die "$_: partition disk number missing or malformed (must be decimal)\n";
2017    
2018     $partdata = pack "L< x12", $1;
2019    
2020     } else {
2021     die "$parttype: partition type not supported\n";
2022     }
2023    
2024     $payload = pack "a16 L< L< a16 a*",
2025     $partdata,
2026     (indexof $blocktype, @block_type),
2027     (indexof $parttype, @part_type),
2028     $diskdata,
2029     $parent;
2030    
2031     } elsif ($type eq "locate") {
2032     s/^=//
2033     or die "$_: missing '=' after $type\n";
2034    
2035     my ($mode, $elem, $path);
2036    
2037     my $parent = $parse_parent->();
2038    
2039     s/^,//
2040     or die "$_: missing comma after locate parent device\n";
2041    
2042     if (s/^element,//) {
2043     s/^([0-9a-z]+)//i
2044     or die "$_ locate element must be either name or 8-digit hex id\n";
2045     $elem = enc_bcde_id $1;
2046     $mode = 0;
2047     $path = $enc_path->("");
2048    
2049     } elsif (s/^path,//) {
2050     $mode = 1;
2051     $path = $parse_path->();
2052    
2053     } else {
2054     die "$_ second locate argument must be subtype (either element or path)\n";
2055     }
2056    
2057     if ($parent ne $NULL_DEVICE) {
2058     ($parent, $path) = (4 * 4 + 4 * 3 + length $path, "$path$parent");
2059     } else {
2060     $parent = 0;
2061     }
2062    
2063     $payload = pack "VVVa*", $mode, $elem, $parent, $path;
2064    
2065     } elsif ($type eq "block") {
2066     s/^=//
2067     or die "$_: missing '=' after $type\n";
2068    
2069     s/^([a-z]+),//
2070     or die "$_: block device does not start with block type (e.g. disk)\n";
2071     my $blocktype = $1;
2072    
2073     my $blockdata;
2074    
2075     if ($blocktype eq "file") {
2076     my $parent = $parse_parent->();
2077     s/^,// or die "$_: comma missing after file block device parent\n";
2078     my $path = $parse_path->();
2079    
2080     $blockdata = $enc_file->($parent, $path);
2081    
2082     } elsif ($blocktype eq "vhd") {
2083     $blockdata = "\x00" x 20; # ENOTUNDERSTOOD
2084     $blockdata .= $parse_parent->();
2085    
2086     } elsif ($blocktype eq "ramdisk") {
2087     my $parent = $parse_parent->();
2088    
2089     s/^,(\d+),(\d+),(\d+),//a
2090     or die "$_: missing ramdisk base,size,offset after ramdisk parent device\n";
2091    
2092     my ($base, $size, $offset) = ($1, $2, $3);
2093    
2094     my $path = $parse_path->();
2095    
2096     $blockdata = pack "Q< Q< L< a*", $base, $size, $offset, $enc_file->($parent, $path);
2097    
2098     } elsif ($blocktype eq "cdrom" or $blocktype eq "floppy") {
2099     # this is guesswork
2100     s/^(\d+)//a
2101     or die "$_: missing device number for cdrom\n";
2102     $blockdata = pack "V", $1;
2103    
2104     } else {
2105     die "$blocktype: unsupported block type (must be file, vhd, ramdisk, floppy, cdrom)\n";
2106     }
2107    
2108     $payload = pack "Va*",
2109     (indexof $blocktype, @block_type),
2110     $blockdata;
2111    
2112     } elsif ($type eq "vmbus") {
2113     s/^=($RE_GUID)//
2114     or die "$_: malformed or missing vmbus interface type guid\n";
2115     my $type = enc_guid $1;
2116     s/^,($RE_GUID)//
2117     or die "$_: malformed or missing vmbus interface instance guid\n";
2118     my $instance = enc_guid $1;
2119    
2120     $payload = pack "a16a16x24", $type, $instance;
2121    
2122     } else {
2123     die "$type: not a supported device type (binary, null, boot, legacypartition, partition, block, locate)\n";
2124     }
2125    
2126     return (
2127     (pack "VVVVa*", (indexof $type, @dev_type), $flags, 16 + length $payload, 0, $payload),
2128     $_
2129     );
2130     }
2131     }
2132    
2133     # encode a full binary BCD device descriptor
2134     sub enc_device {
2135     my ($device) = @_;
2136    
2137     my $guid = "\x00" x 16;
2138    
2139     if ($device =~ s/^\{([A-Za-z0-9\-]+)\}//) {
2140     $guid = enc_guid $1
2141     or die "$device: does not start with valid guid\n";
2142     }
2143    
2144     my ($descriptor, $tail) = enc_device_ $device;
2145    
2146     length $tail
2147     and die "$device: garbage after device descriptor\n";
2148    
2149     unpack "H*", $guid . $descriptor
2150     }
2151    
2152     # decode a registry hive into the BCD structure used by pbcdedit
2153     sub bcd_decode {
2154     my ($hive) = @_;
2155    
2156     my %bcd;
2157    
2158     my $objects = $hive->[1][1]{Objects}[1];
2159    
2160     while (my ($k, $v) = each %$objects) {
2161     my %kv;
2162     $v = $v->[1];
2163    
2164     $k = $bcd_objects{$k} // $k;
2165    
2166     my $type = $v->{Description}[0]{Type}[1];
2167    
2168     if ($type != $bcd_object_types{$k}) {
2169     $type = $bcd_types{$type} // sprintf "0x%08x", $type;
2170     $kv{type} = $type;
2171     }
2172    
2173     my $elems = $v->{Elements}[1];
2174    
2175     while (my ($k, $v) = each %$elems) {
2176     my $k = hex $k;
2177    
2178     my $v = $bcde_dec{$k & BCDE_FORMAT}->($v->[0]{Element}[1]);
2179     my $k = dec_bcde_id $k;
2180    
2181     $kv{$k} = $v;
2182     }
2183    
2184     $bcd{$k} = \%kv;
2185     }
2186    
2187     $bcd{meta} = { version => $JSON_VERSION };
2188    
2189     \%bcd
2190     }
2191    
2192     # encode a pbcdedit structure into a registry hive
2193     sub bcd_encode {
2194     my ($bcd) = @_;
2195    
2196     if (my $meta = $bcd->{meta}) {
2197     $meta->{version} eq $JSON_VERSION
2198     or die "BCD meta version ($meta->{version}) does not match executable version ($JSON_VERSION)\n";
2199     }
2200    
2201     my %objects;
2202     my %rbcd_types = reverse %bcd_types;
2203    
2204     while (my ($k, $v) = each %$bcd) {
2205     my %kv;
2206    
2207     next if $k eq "meta";
2208    
2209     $k = lc $k; # I know you windows types!
2210    
2211     my $type = $v->{type};
2212    
2213     if ($type) {
2214     $type = $type =~ /^(?:0x)[0-9a-fA-F]+$/
2215     ? hex $type
2216     : $rbcd_types{$type} // die "$type: unable to parse bcd object type\n";
2217     }
2218    
2219     my $guid = enc_wguid $k
2220     or die "$k: invalid bcd object identifier\n";
2221    
2222     # default type if not given
2223     $type //= $bcd_object_types{dec_wguid $guid} // die "$k: unable to deduce bcd object type\n";
2224    
2225     my %elem;
2226    
2227     while (my ($k, $v) = each %$v) {
2228     next if $k eq "type";
2229    
2230     $k = (enc_bcde_id $k) // die "$k: invalid bcde element name or id\n";
2231     $elem{sprintf "%08x", $k} = [{
2232     Element => [ ($bcde_enc{$k & BCDE_FORMAT} // die "$k: unable to encode unknown bcd element type}")->($v)]
2233     }];
2234     }
2235    
2236     $guid = dec_guid $guid;
2237    
2238     $objects{"{$guid}"} = [undef, {
2239     Description => [{ Type => [dword => $type] }],
2240     Elements => [undef, \%elem],
2241     }];
2242     }
2243    
2244     [NewStoreRoot => [undef, {
2245     Description => [{
2246     KeyName => [sz => "BCD00000001"],
2247     System => [dword => 1],
2248     pbcdedit => [sz => $VERSION],
2249     # other values seen: GuidCache => ..., TreatAsSystem => 0x00000001
2250     }],
2251     Objects => [undef, \%objects],
2252     }]]
2253     }
2254    
2255     #############################################################################
2256 root 1.29 # edit instructions
2257 root 1.1
2258 root 1.6 sub bcd_edit_eval {
2259     package pbcdedit;
2260    
2261     our ($PATH, $BCD, $DEFAULT);
2262    
2263     eval shift;
2264     die "$@" if $@;
2265     }
2266    
2267     sub bcd_edit {
2268     my ($path, $bcd, @insns) = @_;
2269    
2270     my $default = $bcd->{"{bootmgr}"}{resumeobject};
2271    
2272     # prepare "officially visible" variables
2273     local $pbcdedit::PATH = $path;
2274     local $pbcdedit::BCD = $bcd;
2275     local $pbcdedit::DEFAULT = $default;
2276    
2277     while (@insns) {
2278     my $insn = shift @insns;
2279    
2280     if ($insn eq "get") {
2281     my $object = shift @insns;
2282     my $elem = shift @insns;
2283    
2284 root 1.15 $object = $object eq "{default}" ? $default : dec_wguid enc_wguid $object;
2285 root 1.6
2286     print $bcd->{$object}{$elem}, "\n";
2287    
2288     } elsif ($insn eq "set") {
2289     my $object = shift @insns;
2290     my $elem = shift @insns;
2291     my $value = shift @insns;
2292    
2293 root 1.15 $object = $object eq "{default}" ? $default : dec_wguid enc_wguid $object;
2294 root 1.6
2295     $bcd->{$object}{$elem} = $value;
2296    
2297     } elsif ($insn eq "eval") {
2298     bcd_edit_eval shift @insns;
2299    
2300     } elsif ($insn eq "do") {
2301     my $path = shift @insns;
2302     my $file = file_load $path;
2303     bcd_edit_eval "#line 1 '$path'\n$file";
2304    
2305     } else {
2306     die "$insn: not a recognized instruction for edit/parse\n";
2307     }
2308     }
2309    
2310     }
2311    
2312     #############################################################################
2313 root 1.29 # command line parser
2314 root 1.6
2315 root 1.1 # json to stdout
2316     sub prjson($) {
2317     print $json_coder->encode ($_[0]);
2318     }
2319    
2320     # json from stdin
2321     sub rdjson() {
2322     my $json;
2323     1 while read STDIN, $json, 65536, length $json;
2324     $json_coder->decode ($json)
2325     }
2326    
2327     # all subcommands
2328     our %CMD = (
2329     help => sub {
2330     require Pod::Usage;
2331     Pod::Usage::pod2usage (-verbose => 2);
2332     },
2333    
2334     objects => sub {
2335     my %rbcd_types = reverse %bcd_types;
2336     $_ = sprintf "%08x", $_ for values %rbcd_types;
2337    
2338     if ($_[0] eq "--json") {
2339     my %default_type = %bcd_object_types;
2340     $_ = sprintf "%08x", $_ for values %default_type;
2341    
2342     prjson {
2343     version => $JSON_VERSION,
2344     object_alias => \%bcd_objects,
2345     object_type => \%rbcd_types,
2346     object_default_type => \%default_type,
2347     };
2348     } else {
2349     my %rbcd_objects = reverse %bcd_objects;
2350    
2351     print "\n";
2352    
2353     printf "%-9s %s\n", "Type", "Alias";
2354     for my $tname (sort keys %rbcd_types) {
2355     printf "%-9s %s\n", $rbcd_types{$tname}, $tname;
2356     }
2357    
2358     print "\n";
2359    
2360     printf "%-39s %-23s %s\n", "Object GUID", "Alias", "(Hex) Default Type";
2361     for my $name (sort keys %rbcd_objects) {
2362     my $guid = $rbcd_objects{$name};
2363     my $type = $bcd_object_types{$name};
2364     my $tname = $bcd_types{$type};
2365    
2366     $type = $type ? sprintf "(%08x) %s", $type, $tname : "-";
2367    
2368     printf "%-39s %-23s %s\n", $guid, $name, $type;
2369     }
2370    
2371     print "\n";
2372     }
2373     },
2374    
2375     elements => sub {
2376     my $json = $_[0] eq "--json";
2377    
2378     my %format_name = (
2379     BCDE_FORMAT_DEVICE , "device",
2380     BCDE_FORMAT_STRING , "string",
2381     BCDE_FORMAT_GUID , "guid",
2382     BCDE_FORMAT_GUID_LIST , "guid list",
2383     BCDE_FORMAT_INTEGER , "integer",
2384     BCDE_FORMAT_BOOLEAN , "boolean",
2385     BCDE_FORMAT_INTEGER_LIST, "integer list",
2386     );
2387     my %rbcde = reverse %bcde;
2388     $_ = sprintf "%08x", $_ for values %rbcde;
2389    
2390     my %element;
2391    
2392     unless ($json) {
2393     print "\n";
2394     printf "%-9s %-12s %s\n", "Element", "Format", "Name Alias";
2395     }
2396     for my $name (sort keys %rbcde) {
2397     my $id = $rbcde{$name};
2398     my $format = $format_name{(hex $id) & BCDE_FORMAT};
2399    
2400     if ($json) {
2401     $element{$id} = [$format, $name];
2402     } else {
2403     printf "%-9s %-12s %s\n", $id, $format, $name;
2404     }
2405     }
2406     print "\n" unless $json;
2407    
2408     prjson {
2409     version => $JSON_VERSION,
2410     element => \%element,
2411     } if $json;
2412    
2413     },
2414    
2415     export => sub {
2416     prjson bcd_decode regf_load shift;
2417     },
2418    
2419     import => sub {
2420     regf_save shift, bcd_encode rdjson;
2421     },
2422    
2423 root 1.6 edit => sub {
2424     my $path = shift;
2425     my $bcd = bcd_decode regf_load $path;
2426     bcd_edit $path, $bcd, @_;
2427     regf_save $path, bcd_encode $bcd;
2428     },
2429    
2430     parse => sub {
2431     my $path = shift;
2432     my $bcd = bcd_decode regf_load $path;
2433     bcd_edit $path, $bcd, @_;
2434     },
2435    
2436 root 1.1 "export-regf" => sub {
2437     prjson regf_load shift;
2438    
2439     },
2440    
2441     "import-regf" => sub {
2442     regf_save shift, rdjson;
2443     },
2444    
2445     lsblk => sub {
2446     printf "%-10s %-8.8s %-6.6s %-3s %s\n", "DEVICE", "LABEL", "FSTYPE", "PT", "DEVICE DESCRIPTOR";
2447    
2448     my $lsblk = $json_coder->decode (scalar qx<lsblk --json -o PATH,KNAME,TYPE,PTTYPE,PTUUID,PARTUUID,LABEL,FSTYPE>);
2449    
2450     for my $dev (@{ $lsblk->{blockdevices} }) {
2451     my $pr = sub {
2452     printf "%-10s %-8.8s %-6.6s %-3s %s\n",
2453     $dev->{path}, $dev->{label}, $dev->{fstype}, $dev->{pttype}, $_[0];
2454     };
2455    
2456     if ($dev->{type} eq "part") {
2457     if ($dev->{pttype} eq "gpt") {
2458     $pr->("partition=<null>,harddisk,gpt,$dev->{ptuuid},$dev->{partuuid}");
2459     } elsif ($dev->{pttype} eq "dos") { # why not "mbr" :(
2460     if ($dev->{partuuid} =~ /^([0-9a-f]{8})-([0-9a-f]{2})\z/i) {
2461     my ($diskid, $partno) = ($1, hex $2);
2462     $pr->("legacypartition=<null>,harddisk,mbr,$diskid,$partno");
2463     if (open my $fh, "/sys/class/block/$dev->{kname}/start") {
2464     my $start = 512 * readline $fh;
2465     $pr->("partition=<null>,harddisk,mbr,$diskid,$start");
2466     }
2467     }
2468     }
2469     }
2470     }
2471     },
2472     );
2473    
2474     my $cmd = shift;
2475    
2476     unless (exists $CMD{$cmd}) {
2477     warn "Usage: $0 subcommand args...\nTry $0 help\n";
2478     exit 126;
2479     }
2480    
2481     $CMD{$cmd}->(@ARGV);
2482