ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/pbcdedit/pbcdedit
Revision: 1.25
Committed: Wed Aug 14 23:38:53 2019 UTC (4 years, 9 months ago) by root
Branch: MAIN
Changes since 1.24: +2 -1 lines
Log Message:
*** empty log message ***

File Contents

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